AsyncTask(onProgressUpdate) in String (UI-Thread) speichern und benutzen

  • 12 Antworten
  • Letztes Antwortdatum
A

aw29erac

Neues Mitglied
2
Hallo Leute,

ich hab einen Socketclient in einem AsyncTask realisiert. Senden und Empfangen klappt wunderbar. Ich lese den InputStream vom Server kontinuierlich aus und sobald etwas empfangen wurde werden die Daten an "onProgressUpdate" übergeben. Mit einer TextView kann ich diese dann schön anzeigen lassen.

Mein Problem ist jetzt aber, dass ich die Daten in einen String speichern möchte, um sie so im UI-Thread weiterverwenden zu können(plotten oder in TextView anzeigen lassen, je nach empfangenen Daten). Also den AsyncTask mit dem UI-Thread synchronisieren.
Und das wichtigste, dass ich im UI-Thread erkenne, wann Daten in diesem String stehen, damit ich dann damit weiter arbeiten kann.
Wenn ich das wie folgt mache, dann hängt sich die App auf^^

public class MainActivity extends Activity{
public String antwort;
TextView outputStream;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plot);

private static final String IP = "192.168.178.10";

outputStream = (TextView)findViewById(R.id.output);
antwort = null;

//Initialisieren
socketclient = new SocketClient();
socketclient.execute(IP);

/**** warten bis Socketverbindung steht ****/

int rettich = 0;
while(rettich == 0){// der rettich wird 1, wenn der AsyncTask äuft
if(socketclient.getStatus() == AsyncTask.Status.RUNNING){
socketclient.Send("Wie ist dein Name"); // Befehl der gesendet wird
rettich = 1;
}
}

while(rettich == 1){
if(antwort != null ){
outputStream.setText(antwort);
rettich = 2;
}
}
}
/*********** die Socketverbindung findet in einem AsyncTask statt ***********/


public class SocketClient extends AsyncTask<String, String, Boolean>{

private static final int PORT = 5025;

private static final int timeout = 0;// Timeout ist unendlich

Socket socket;
PrintWriter out;
BufferedReader in;

@Override
protected void onPreExecute(){
// Hier kommt rein, was direkt vor dem Aufbauen der Socket-Verbindung passieren soll
}

@Override
protected Boolean doInBackground(String... ipAdr) {
boolean result = false;
try {
SocketAddress addr = new InetSocketAddress(ipAdr[0],SCR_PORT);
socket = new Socket();
socket.connect( addr, timeout);
if (socket.isConnected()){
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
isReady = true;

while(true){ // InputStream wird dauerhaft ausgelesen, bis AsyncTask gecancelled wird
publishProgress(in.readLine());
}
}else{
Log.v("AsyncTaskError", "socket ist über den Timeout");
}
} catch (UnknownHostException e1) {
e1.printStackTrace();
result = true;
} catch (IOException e1) {
e1.printStackTrace();
result = true;
}catch(Exception e){
e.printStackTrace();
result = true;
}finally{
closeSocket();
}
return result;
}
// Methode schließt die Sockt-Verbindung
public void closeSocket(){
try {
in.close();
out.close();
socket.close();
} catch (IOException e2) {
e2.printStackTrace();
}catch(Exception e2){
e2.printStackTrace();
}
}

// Methode sendet ein Kommando
public void Send(String cmd){
out.println(cmd);//Befehl senden
out.flush();// runterspülen^^
}

// Diese Methode wird immer aufgerufen, wenn ein Input empfangen wird
@Override
public void onProgressUpdate(String... werte){
if (werte.length > 0) {// wenn Daten empfangen wurden
// antwort = werte[0];// Das bringt nichts leider

outputStream.setText(werte[0]);
}
}

// Methode wird aufgerufen, wenn der Task beendet wird
@Override
protected void onCancelled(){
outputStream.setText("Verbindung getrennt!");
}

@Override
protected void onPostExecute(Boolean result) {
if(result){
outputStream.setText("Verbindung beendet mit Fehler!");
}else{
outputStream.setText("Verbindung beendet ohne Fehler!");
}
}
}
}

Bin für jede Hilfe dankbar! Ist nur ein Auszug! (kleine Fehler können sich eingeschlichen haben, bitte nicht beachten)
 
Erst mal, bitte nutze die [ code ] tags für so viel Quellcode am Stück, das ist so vollkommen unleserlich.

Wenn ich dich richtig verstehe, musst du doch nur statt direkt:
publishProgress(in.readLine());
aufzurufen hier erst mal den Text den du empfängst zwischenspeichern.
In deinem AsyncTask kannst du das ja machen.
Und erst wenn der Text dort soweit ist machst du ein
publishProgress(deinGanzerString)

Warum sich die App jetzt genau aufhängt weiß ich nicht, da ich bei dem Code ohne Code-Tags keine Lust hab mir den komplett durch zu lesen ;)
 
Ohne deinen Code durchzuschauen würd ich einen Handler verwenden. Damit kannst du ein Message-Object erzeugen und senden bzw. empfangen und somit z.B. deinen String übergeben oder dein Update starten oder was auch immer.
 
Hey,
danke schonmal für die Antworten :thumbsup:
@amfa: Sorry dafür, ich hab hie rnoch nie Code gepostet ^^
Und zu deiner Antwort: Ich glaub du hast mich leider falsch verstanden :sad: das was nich klappt, ist die Synchronisierung des AsyncTask mit dem UI-Thread.​

Ich versuche es noch einmal zu erklären( Tom99 hat es richtig verstanden):
Im AsyncTask läuft der Datenaustausch über die Socketverbindung. Im UI-Thread kann ich beliebig Kommandos senden. Das Problem ist, dass ich die Antwort nur über onProgressUpdate asynchron bekomme, also der UI-Thread nicht weiß, wann die ANtwort da ist. Der Nachteil ist, dass das eben asynchron läuft ^^ Ich hätte es gern synchron! Also Kommando wird gesendet und die "senden-Methode" gibt dann quasi die Antwort als return. Man kann aber Socketverbindungen nicht im Code einbauen, sondern muss das (so wie ich das verstanden habe) in einen AsyncTask ausgliedern, damit der UI-Thread nicht blockiert wird und die App sich aufhängt​


Ich hoffe das war verständlich. Ist eben ziemlich spezifisch. Sowas braucht normaler Weise kein Mensch^^
 
Ich glaube du hast nur ein Verständnisproblem. onProgressUpdate wird im UI-Thread ausgeführt. Das heisst wenn du in dieser Methode bist, dann bist du ja schon im UI-Thread und weisst das auch... :razz:

Was auch immer du im UI-Thread machen willst (plotten z.B.) kannst du in onProgressUpdate (oder onPostExecute) machen. Wenn es dir nur darum geht, dass der Code des plottens nicht im AsyncTask sein soll, kannst du natürlich auch einfach in onProgressUpdate eine Methode vom UI-Thread aufrufen, wo dein Code drin ist.


Tom299 schrieb:
Ohne deinen Code durchzuschauen würd ich einen Handler verwenden. Damit kannst du ein Message-Object erzeugen und senden bzw. empfangen und somit z.B. deinen String übergeben oder dein Update starten oder was auch immer.

Ein Handler in einem AsyncTask macht nicht wirklich Sinn, weil ein AsyncTask bereits eine Kombination von Thread und Handler ist. Es vereinfacht einfach die Verwendung (und hat dadurch auch einige Einschränkungen). Du kannst aber natürlich Handler verwenden, einfach in Kombination mit normalen Threads. Ist nicht ganz so komfortabel wie ein AsyncTask, aber du hast mehr Freiheiten.

Das ändert aber schlussendlich nichts daran, dass das ganze deswegen trotzdem asynchron bleibt.
 
Naja.. doch der UI Thread bekommt das dann halt über publishProgress mit (auch wenn das nicht zwingend dafür gedacht ist).

publishProgress wird ja im UI Thread ausgeführt.
Sobald du also da bist kannst du Dinge im UI Thread ausführen.

Dein problem mit dem aufhängen passiert dadurch, dass

Dein Problem ist ja, dass dein AsynTask eine Endloschleife enthält.

Wenn du also statt dauerhaft einfach
publishProgress(in.readLine());
machst.
erst mal IN dem AsyncTask solange Daten sammelst bis genug für eine Antwort da sind und erst dann publishProgress aufrufst (mit den bis dahin gesammelten Daten) sollte das meiner Meinung nach funktionieren.

Aufhängen tut sich deine App meiner Meinung nach wegen dem publishProgress in der while-Schleife.
Weil du damit einfach dauerhaft den UI Thread blockierst.
 
Danke für die ganzen Antworten :smile:

Ich versuche es noch mal ein bisschen zu abstrahieren:
Ich will eigentlich nur Befehle via Socketverbindung an den Server senden und mit Aufruf dieser Senden-Methode als return die Antwort des Servers. Nur soll die Socketverbindung solange offen bleiben, bis die App geschlossen wird.​

Was mit onProgressUpdate funktioniert ist:

Ich sende einen Befehl. Die Antwort wird dann mit publishProgress wieder an den UI-Thread übergeben. Dadurch kann man sie einfach in einer TextView anzeigen lassen. Ich kann aber die Antwort nicht da weiter verwenden, wo ich den Befehl abgeschickt habe(zumindest weiß ich ned wie das gehen soll)​

Mein Ansatz war:
Ich hab eine globale Variable String Antwort im UI-Thread. Wenn ich jetzt einen Befehl sende, dann überschreibe ich in onProgressUpdate den String Antwort. Und die nächste Zeile nach dem Aufruf der "senden" Methode ist dann eine while Schleife, die darauf wartet, dass in "Antwort" etwas drin steht. Das passiert aber nicht und deshalb hängt sich meiner Meinung nach die App auf.​

Eigentlich ist das total easy in Java ^^ nur in Android anscheinend nicht :confused2:
 
Dein Ansatz ist falsch :winki: Statt deinen Code schöner zu strukturieren versuchst du eine Synchronität zu erzwingen, damits besser in deinen Code passt. Es mag zwar auf einem normalen Computer in Java so funktionieren, aber es ist trotzdem extrem unschön und auch nicht benutzerfreundlich, wenn das Programm einfach einfriert, bis irgendwann die while-Schleife fertig ist.

Deshalb ist es super dass Android das nicht erlaubt. :thumbup: So sind die Entwickler wenigstens gezwungen, einen Teil der App schön zu implementieren als irgendwas zu basteln.

Wie gesagt musst du nur deinen Code etwas umstrukturieren. Ich habe deinen Code zwar auch nicht gelesen, aber als kleines Beispiel:

aus

Code:
public class MyActivity extends Activity {

    public void commandSendenUndAntwortVerarbeiten() {
        String antwort = sendeEtwas();
        
        //... mache etwas mit der Antwort ...
        Log.i("MeineAntwort", antwort);
    }
}

machst du

Code:
public class MyActivity extends Activity {

    public void sendeCommand() {
        sendeEtwas();
    }

    public void verarbeiteAntwort(String antwort) {
        //... mache etwas mit der Antwort ...
        Log.i("MeineAntwort", antwort);
    }
}

public class MyTask extends AsyncTask<String, String, Boolean> {

    public void onProgressUpdate(String... progress) {
        myActivity.verarbeiteAntwort(progress[0]);
    }

}

kurz erklärt: Statt in einer Methode einen Command zu senden und gleich die Antwort verarbeiten zu wollen, machst du zwei Methoden. Die erste sendet den Command und die zweite verarbeitet die Antwort. Die zweite Methode wird dann ausgeführt (aufgerufen von onProgressUpdate), sobald die Antwort da ist. Die App macht immernoch das gleiche (Command senden, auf Antwort warten, Antwort verarbeiten), nur anders strukturiert.

Du hast auch gleich noch mehr Vorteile:
  • sauberere Struktur
  • besser leserlicher Code
  • jede Methode hat nur eine Aufgabe
  • besser testbarer Code
  • ... gibt sicher noch mehr ...
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: amfa
Hey,
danke für deine Antwort. Das ist sehr umständlich für mich auf diese Art und Weise und ich hatte gehofft es mit dem Frage-Antwort-Spiel implementieren zu können. Dadurch hätte ich die Daten vom Server einfach sammeln können und zum Beispiel Einstellungen des Servers leichter vornehmen können.

Wenn ich auf diese Art mit dem Server kommuniziere muss ich ja dort wo die Antwort verarbeitet wird feststellen welche Art von Antwort das ist. Kommt da jetzt z.B. die Farbe zurück? Oder die Lautstärke?(nur als Beispiel)

Auf die andere Art hätte ich eben abfragen können: Wie ist die Farbe? Und der Server hätte zurückgegeben: Blau. Und dann hätte ich einfach dementsprechend den Hintergrund der App blau färben können.

So muss ich jetzt in der verarbeitungs-Methode alle Mögliche Arten von Informatonen abfragen ^^

Für diesen Zweck ziemlich umständlich und nervig, aber naja dann mach mers halt mal so

Euch auf jeden Fall vielen, vielen Dank!!!
 
Das ist aber sowieso die saubere Variante.
Du Stellst dem Server die Frage:
"Welche Farbe" der Server Antwortet mit "Farbe: blau"
Und nicht nur mit Blau.

Also klar definierte Antworten.
Eine Antwort wo nur "blau" drin steht ist einfach unschön.
Bau dir lieber ein schönes Protokoll oder was eigentlich sogar noch besser wäre nutze z.B. HTTP.
Da kannste dir dann Json Objekte durch die Gegend schieben, die relativ einfach in java Objecte gemappt werden können. (Google mal nach gson).

So eine dauerhafte Serververbindung ist für das was du vorhast (so wie ich das raushöre) sowieso keine gute Idee.

Du musst überlegen, dass ein Smartphone oft unterwegs ist und im Funkloch bricht deine Serververbindung dann zusammen.
Besser wäre es daher, wenn du pro Information die du haben willst eine Anfrage an den Server machst, der antwortet danach brauchst du erst mal keine Verbindung mehr.

Du hast dann zwar (vorallem mit HTTP) einen gewissen overhead, allerdings wird deine Variante mit dauerhafter Verbindung, früher oder später schiefgehen (Funkloch) und braucht dazu noch sehr viel mehr Strom.
 
  • Danke
Reaktionen: Zoopa
Ich schreib grad meine BA und hab keinen Einfluss auf den Server^^ und der Server ist auch lokal, also ich verbinde mich direkt über einen Access Point über W-LAN mit dem.

Also alles nicht so einfach und eben sehr spezifisch:winki:
 
Außerdem antwortet der Server nicht mit Farbe: Blau, sondern nur mit blau. das ist ja gerade das Problem^^
Aber ich hab es jetzt fast hinbekommen
Gesendet von meinem HTC One V mit der Android-Hilfe.de App
 
aw29erac schrieb:
Auf die andere Art hätte ich eben abfragen können: Wie ist die Farbe? Und der Server hätte zurückgegeben: Blau. Und dann hätte ich einfach dementsprechend den Hintergrund der App blau färben können.

ah ok, jetzt sehe ich dein Problem :winki: Du hast damit natürlich recht. Wenn du in der App erst herausfinden musst, was der Server überhaupt gesendet hat, ist das nicht ideal. In dem Fall ist mein Beispiel nicht das Beste :smile:

Aber ich bin trotzdem sicher, dass man durch etwas umstrukturieren von deinem Code etwas Sauberes hinbekommt.

Du brauchst also eine Lösung, wo du beim senden der Frage schon weisst, wie du die Antwort behandeln musst. Ich könnte mir da etwas in Richtung des Observer-Patterns vorstellen.

Das wäre dann ähnlich wie Buttons in Android. Dort kannst du beliebig viele Buttons erstellen, aber jedem ein eigenes "Verhalten" geben (Mit Hilfe eines Listeners).

Bei dir wäre das eigentlich dasselbe mit deinem AsyncTask. Je nachdem, welche Frage du dem Server stellst, willst du ein anderes "Verhalten" haben (Frage: Welche Farbe? / Verhalten: ändere Hintergrund). Aber natürlich soll der AsyncTask dabei immer die gleiche Logik ausführen (Sende String an Server und empfange anderen String)

In Kombination mit amfas Vorschlag, für jede Anfrage eine neue Serververbindung aufzubauen, bringst du dann meiner Meinung nach eine schöne Lösung hin.

Etwa so könnte das dann aussehen:

Code:
public class MyActivity extends Activity {
	
	public void doSomething() {
		//Farbe vom Server anfragen
		MyAsyncTask colourTask = new MyAsyncTask();
		colourTask.setResponseListener(new BackgroundColourChangeListener());
		colourTask.execute("IP", "Server, gib mir eine Farbe");
		
		//Lautstärke vom Server anfragen
		MyAsyncTask volumeTask = new MyAsyncTask();
		volumeTask.setResponseListener(new VolumeChangeListener());
		volumeTask.execute("IP", "Server, gib mir eine Lautstärke");
	}
	
	class BackgroundColourChangeListener implements ResponseListener {

		@Override
		public void handleResponse(String response) {
			//Hintergrund anpassen. In "response" steht jetzt z.B. "blau"
		}
	}
	
	class VolumeChangeListener implements ResponseListener {

		@Override
		public void handleResponse(String response) {
			//Lautstärke anpassen. In "response" steht jetzt z.B. "25%"
		}
	}
}

Code:
public class MyAsyncTask extends AsyncTask<String, Void, String>{
	
	private ResponseListener listener;
	
	public void setResponseListener(ResponseListener listener) {
		this.listener = listener;
	}
	
	protected String doInBackground(String... args) {
		//sende Anfrage an Server. args[0] = IP, args[1] = Frage
               return "antwort vom server";
	}
	
	protected void onPostExecute(String response) {
		if(listener != null) {
			listener.handleResponse(response);
		}
	}
}

Code:
interface ResponseListener {
	void handleResponse(String response);
}
 
Zuletzt bearbeitet:
Zurück
Oben Unten