AsyncTask - onPostExecute

StefMa

StefMa

Dauergast
450
Hi,

ich hole mal für meine Frage etwas weiter aus:
Der Sinn hinter der OOP ist ja, dass man Klassen "universal" verwenden kann. Also ganz konkretes Beispiel eine Klasse, die Daten an einen Server sendet.
Da könnte man, in der "normalen" OOP, einfach eine klasse machen wie
Code:
public void setServerAdresse();
public void setFileToUpload();
public void setTextToUpload();
public void upload();
Was die einzelnen Methoden machen sollte verständlich sein.
Diese Klassen könnte man also schön als universale Upload-Klasse benutzen.

Das habe ich auch ähnlich gemacht. Ich habe mir in "reinem" Java eine API zu einem Server geschrieben. Dort sind ähnliche Klassen wie oben und noch weitere wie:
Code:
public void getFileUrl();
public void getFileTitle();
In einem "normalen" Java Programm läuft das auch Super. Da kann ich in einer anderen Klassen ganz gemütlich sagen:
Code:
APIClass api = new APIClass();
api.setServerAdress("http://coole.api.guru");
api.setTextToUpload("SuperText");
api.upload();
String url = api.getFileUrl();
Im String url bekomme ich dann wie gewünscht meine URL und ich bin super Happy.

Jetzt will ich das ganze als Android App portieren. Also habe ich mir einfach gedacht, dass ich den API Source-Code in mein src/-Verzeichniss packe und dann die APIClass genauso aufrufen kann. Funktioniert... bedingt.
(Kurz: in der methode upload() rufe ich ein HTTPUrlConnection auf).
Android meldet also, dass er gerne HTTP-Connections nicht im MainThread haben möchte. Auch kein Problem. Hier habe ich nun zwei Möglichkeiten:
1. Die API so umzubauen, dass upload() das zukünftige doInBackground() vom AsyncTask ist.
2. Den Aufruf von APIClass() gleich in einem AsyncTask ausführen.

Aber, und jetzt kommen wir langsam zu meiner Frage, gibt es immer und immer wieder ein Problem:
Die onPostExecute wird unter Android "total zugemüllt". Denn alles weitere, wie getFileUrl(), muss ich dort abarbeiten. Denn erst wenn der Upload komplett ist, kann ich mir die URL dazu holen.
Darausfolgt, dass, egal wo ich den AsyncTask "einrichte", diese nicht mehr Universal einsetzbar ist. Ich bin eigentlich gezwungen diese voll auf mein Projekt abzustimmen.
Vorallem bei ersterer Lösung wird es schwierig. Denn somit existiert nicht mehr eine "einheitliche" Java-API sondern nur eine Android-API. Zweitere Methode ist auch schwachsinnig. Weil ich dann im AsyncTask, um einen ähnlichen "einfachen" aufruf zur API zu erhalten, alle Methode von der APIClass erstellen muss. Diese müssten dann im onPostExecute aufgerufen werden und machen nichts weiter als wieder meine ClassAPI aufzurufen. Also sind die MEthoden total redundant.
Aufruf mit AsyncTask:
Code:
APIClassAsync apiAT = new APIClassAsync();
apiAt.setServerAdress("http://code.api.guru");
apiAt.setTextToUpload("Coole API!");
apiAt.getFileUrl();
apiAt.execute();
Sieht nicht nur beschruft aus, weil ich getFileUrl() noch VOR execute hole, sondern auch, weil ich in dieser Klasse keinen "rückgabewert" habe. In Form der URL.
Ich müsste dann im AsyncTask in der methode getFileUrl() so ein Algorythmus einbauen, dass ein String in der anderen Activity, die die AsynctAsk-Klasse aufruft, setzt. Ach idiotisch.

Also: Wie löst ihr so etwas? Nehmt ihr einfahc in Kauf, dass die OOP darunter verloren geht? Man könnte, wenn man erstere Methode verwendent und demnach nur eine Android-API hat, auch in der onPostExecute weiter Klassen bauen, die dann jeweils für den "bau" der UI zuständig sind. Aber das nur als meine bescheidene Lösung... :)

Danke für die Anregung und fürs lesen ;)

Gruß
 
Naja die Grundidee von OOP ist ja auch nicht zwingend, das Klassen universell einsetzbar sind, sondern eher, dass man damit ein "Abbild" der Wirklichkeit hat.
Ein Plan um ein Auto zu bauen ist in in OOP dann die Klasse (der Bauplan).
Daraus baust du dann echte Autos (Objekte).

Und wie im echten Leben hat jedes Auto seine eigenen Eigenschaften (Farbe, Geschwindigkeit Mitfahrer (die wiederum eigene Objekte sind etc.)).

Das Klassen universell einsetzbar sind ist nicht das Ziel.
Das muss wenn ein eigenes Ziel sein (z.B. wenn man eine Library schreiben will).

Zu deinem Problem:
Du musst für AsyncTask halt etwas umdenken.
Wenn du AsyncTask nutzt bzw nutzen musst, dann funktioniert der funktionale Ansatz (eine Anweisung nach der anderen) nicht mehr.
Die Frage ist auch, wofür du die URL am Ende brauchst.
Evtl wäre ein "richtiger" Thread und ein handler die bessere Wahl.
Sobald du mit Threads arbeitest kommst du halt von der eher funktionalen Programmierung (innerhalb einer Methode) zu einer eher event basierten Art.

Heißt du agierst nicht mehr, sonder du reagierst darauf das dein Upload fertig ist.
Genau so funktionieren aber z.B. auch die OnClick Methoden, nach einem Klick reagierst du darauf.

Dein Problem ist eigentlich nur, dass deine Annahme falsch ist (zumindest meiner Meinung nach), dass OOP zwingend mit "universel" zu tun hat.
Das ist je nach fachlicher Anforderung oftmals gar nicht möglich, da viele Implementierungen sehr fachbezogen sind.
Auch in deinem Beispiel, das Hochladen eines Strings mag vielleicht noch als universell durchgehen, das Abfragen der URL hingegen ist schon spezifischer und da kommst du dann mit einem universellen Ansatz nicht mehr weit.
 
  • Danke
Reaktionen: StefMa
Ich hatte vor kurzem fast das gleiche Problem. Ob meine jetztige Lösung ideal ist, weiss ich nicht, aber ich erklär dir trotzdem mal, wie ich vorgegangen bin.

Als Erstes:
Auch in "normalem" Java solltest du Netzwerk-Dinge in einen eigenen Thread auslagern, sonst blockierst du den UI-Thread genauso wie bei Android. Nur wirst du halt im Gegensatz zu Android nicht dazu gezwungen.

Ich habe mich (im aktuellen Stand des Projekts) dazu entschieden, Callbacks zu verwenden. Aber ob das Observer-Pattern nicht vielleicht besser oder schöner wär, muss ich noch entscheiden. Vielleicht gibt es auch noch ein viel schöneres Design Pattern, mal schauen :winki:

Als kurzes Code-Beispiel sieht das bei mir momentan so aus:


Zuerst habe ich ein Callback (Event?) definiert. Die Methode soll aufgerufen werden, sobald ein Bild fertig geladen wurde.
Code:
public interface LoadImageCallback {
	void onImageLoaded(Image img);
}

Die Hauptklasse (oder sonst eine Klasse) implementiert dieses Interface. Die Methode onImageLoaded wird irgendwann in Zukunft (vielleicht) aufgerufen. Dort drin kann man jetzt darauf reagieren, wenn ein Bild geladen wurde. Wichtig zu wissen ist hier halt, dass die Methode irgendwann aufgerufen werden kann. Du weisst aber auch mit Sicherheit, dass zu diesem Zeitpunkt der AsyncTask fertig ist (weil der AsyncTask ganz am Schluss diese Methode aufrufen wird)
Code:
public class MyExampleClass implements LoadImageCallback{
	
	public MyExampleClass() {
		MyApi api = new MyApi(this);		
		api.loadNextImage();
	}

	@Override
	public void onImageLoaded(Image img) {
		System.out.println("New image loaded");
	}
}

Meine API-Klasse macht momentan nichts weiter, als für jeden Bilder-Download einen neuen DownloadTask zu instanzieren und das Callback mitzugeben
Code:
public class MyApi{
    private LoadImageCallback callback;
    
    public MyApi(LoadImageCallback callback) {
    	this.callback = callback;
    }
    
    public void loadNextImage() {
    	new DownloadTask(callback).execute();
    }
}

Der DownloadTask macht normal seine Arbeit, er lädt also das Bild. Wenn er fertig ist, ruft er einfach nur die Callback-Methode auf und gibt das Bild als Parameter mit. Damit wird der Hauptklasse mitgeteilt, dass der Download eines Bildes fertig ist
Code:
private class DownloadTask extends AsyncTask<...> {
    private LoadImageCallback callback;

    public DownloadTask(LoadImageCallback callback) {
        this.callback = callback;
    }

    @Override
    protected String doInBackground(...) {
        //download next image, save in img
        return img;
    }

    @Override
    protected void onPostExecute(...) {
         callback.onImageLoaded(img);
    }
}

Was noch fehlt:
Der DownloadTask ist momentan ein AsyncTask, das funktioniert auf dem Desktop natürlich nicht. Ich werde das vermutlich so regeln, dass die API irgendwo feststellt, welches System gerade verwendet wird und dann entsprechend den richtigen Task startet. Etwa in der Art:

Code:
if(ANDROID) {
    new AndroidDownloadTask(callback).execute();
} else {
    new JavaDownloadThread(callback).start();
}

Wobei ich das noch irgendwie kapseln will, damit ich eine einheitliche Schnittstelle habe. Dazu habe ich mir aber bisher noch nicht allzuviele Gedanken gemacht.
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: StefMa
Ich habe das ganze anfang letzten Jahres auch mal gemacht...
Kann nachher gerne mal was vom Code Posten bzw. es dir zukommen lassen.
Bin leider noch auf der Arbeit und hab es nicht hier...
Aber eigentlich kann man den Netzwerkverkehr mit dem Callback System ganz gut wegkapseln... und das ganze sogar wieder synchron machen, wenn man mag...

lg. Dagobert
 
Vielen dank euch beiden.
Frage mich aber ernsthaft warum bei OOP immer das Auto als beispiel genommen wird :D

Danke Zoopa! Dast ist eine Geniale Idee! Werde ich mal probieren und schauen ob ich das auch hinbekomme ;)

Mein Problem ist quasi nur folgendes:
Ich meinte nicht meine onPostExecute "voll müllen". Mein "Hauptklasse" soll nur schnell mal was hochladen und dann die die URL ausgeben. (Warum, wieso sei erstmal dahin gestellt).
Ich will dazu nicht die komplette Logik in den AsyncTask schieben. Denn dieser soll wirklich nur für den Upload zuständig sein. Damit ich diesen dann immer und überall egal wieviele Klassen ich habe oder inw elcher "Situation" ich mich befinde, aufrufen kann.

Aber mit dem Callback ist wirklich eine gute idee. Denn damit bleibt tatsächlich die Logik inerhalb der aufrufenden Klasse.

Danke und Gruß
 
Hmm warum nur das Rad immer neu erfinden ???

Ich nutze ION als Http-FrameWork

https://github.com/koush/ion

dort wird auch der CallBack-Methoden Ansatz konsequent genutzt.
Über die ganzen anderen netten Features will ich jetzt gar nicht reden.
 
Zoopa schrieb:
Zuerst habe ich ein Callback (Event?) definiert.

Im prinzip sind Callbacks meiner Meinung nach nichts anderes als Events.
Der einzige unterschied (wenn auch vorallem sprachlich zur Unterscheidung und weniger technisch) dürfte sein, dass man das den Callback als Solchen mitgibt und genau dieser dann aufgerufen (Das Callback-Objekt).

EventListener hingegen registrieren sich bei einem Objekt das Events "wirft" ohne das dieses Objekt das im Detail weiß.
Im Endeffekt läuft das natürlich ähnlich. (Das Objekt in dem der Event stattfindet ruft eine methode auf dem CallBack/Eventlistener Objekt auf).

Von der Grundidee sind aber beide eigentlich gleich.
Und zwar wird meine methode nicht von mir gesteuert aufgerufen sondern "extern".
 
@Zoopa:
Ich bin das eben mal durchgespielt und frage mich, warum ein Interface? Ist das notwendig?
Ich habe diesen Code:
Code:
public class TestAPI{

	public static void main(String[] args) {
		new TestAPI();

		System.out.println("Zeit ist um.3");
	}

	public TestAPI() {
		new TestThread(this).start();
	}

	public void onFinishTask() {
		// TODO Auto-generated method stub
		System.out.println("Really finished");
	}
}
Und der Thread dazu:
Code:
public class TestThread extends Thread {
	
	TestAPI callback;
	
	public TestThread(TestAPI callback) {
		this.callback = callback;
	}
	
	@Override
	public void run() {
		try {
			Thread.sleep(1000);
			System.out.println("XY-Z");
                        Thread.sleep(1000);
			callback.onFinishTask();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

Funktioniert ebenso. Ohne Interface... Also, wozu?

Danke und Gruß

// Edit: ahhh verstanden!! Klar. Sonst kann nur die klasse TestAPI übergeben werden. Wenn eine andere Klasse dann das Callback verwenden will geht das nicht... -> Selbst ist der man(n) ;)
 
Zuletzt bearbeitet:
Naja ich hatte damals 3 generische Interfaces geschrieben...
Eins kann ein einzelnens Result zurück geben, eines eine Liste von Results und eines einfach nur erfolgreich oder Fehlgeschlagen.

lg. Dagobert
 

Ähnliche Themen

M
Antworten
8
Aufrufe
1.685
swa00
swa00
lordzwieback
  • lordzwieback
Antworten
15
Aufrufe
1.151
lordzwieback
lordzwieback
Zurück
Oben Unten