AsyncTask soll GUI verändern... Absturz

  • Antworten:13
Dennis Mro...
  • Forum-Beiträge: 19

18.11.2010, 09:16:47 via Website

Hi,

in meinem Programm soll ein Dienst im Hintergrund regelmäßig Daten abrufen und die GUI entsprechend aktualisieren. Jedoch stürzt das Programm ab, sobald mein Hintergrund-Task die GUI verändern will.

Hier der zusammengekürzte Quelltext:

1public class schaltmodul extends Activity implements OnClickListener
2{
3 boolean s1;
4 private static ToggleButton b1;
5
6 public static void setToggleButton1(boolean status)
7 {
8 b1.setChecked(status);
9 }
10
11 @Override
12 public void onCreate(Bundle savedInstanceState)
13 {
14 super.onCreate(savedInstanceState);
15 setContentView(R.layout.main);
16
17 b1 = (ToggleButton)findViewById(R.id.ToggleButton01);
18 b1.setOnClickListener(this);
19
20 setToggleButton1(true); // funktioniert
21
22 new AsyncTask()
23 {
24
25 @Override
26 protected Object doInBackground(Object... params)
27 {
28 setToggleButton1(true); // funktioniert nicht
29 return null;
30 }
31
32 }.execute();
33
34 }
35
36 public void onClick(View v)
37 {
38 s1 = b1.isChecked();
39 // ... DoSomething
40 }
41}

Wo liegt denn mein Fehler? Wie wird's richtig gemacht?

Danke,
Dennis

Antworten
Hendrik
  • Forum-Beiträge: 13

18.11.2010, 10:41:12 via Website

Bei einem AsycTask laufen die "onProgressUpdate" und "onPostExecute"-Methode im UI Thread, nicht die "doInBackground".

Also dein "setToggleButton1(true);" machst du sinnvollerweise direkt in dem "onClickListener" - uns setzt ihn dann im "onPostExecute" vom AsyncTask zurück :)

1private class DownloadFilesTask extends AsyncTask<xxx,yyy,zzz> {
2 protected xxx doInBackground(URL... urls) {
3 while(working){
4 publishProgress(...)
5 }
6 }
7 return xxx;
8 }
9
10 protected void onProgressUpdate(yyy... progress) {
11 //progress anzeigen
12 }
13
14 protected void onPostExecute(zzz result) {
15 //result anzeigen
16 }
17 }

Antworten
Dennis Mro...
  • Forum-Beiträge: 19

18.11.2010, 11:00:56 via Website

Hallo Hendrik,

dann muß ich etwas weiter aufholen, ich glaube, Du hast den Sinn meines Beispiels nicht verstanden (bzw. er war ja auch nicht so offensichtlich).

Ich habe das gleiche Programm in Java (mit swing) auf dem Computer programmiert und es läuft. Mein Programm stellt eine Gui mit Toggle-Tasten zur Verfügung. Klickt der User auf eine Taste wird ein Event ausgelöst, das wiederum eine Nachricht auf einen Socket im Netzwerk schickt. die ToggleTaste zeigt den aktuellen Zustand entsprechend an.
Gleichzeitig habe ich einen zweiten Thread laufen, der durchgehend im Hintergrund Informationen sammelt und die gui ggf. ändert bzw. die Buttons setzt. Der zweite Thread läuft als Endlosschleife.

Etwa so grob im Pseudo-Code:

Thread A:
while(true)
{
Buttons durch User gedrückt?
dann sende neuen Status ins Netz
}


Thread B:
while(true)
{
Frage an Netzwerk: Wie ist der aktuelle Status?
Button in der Gui entsprechend setzen
sleep
}

Das ganze wollte ich jetzt auf Android portieren.

also der gui-Thread (haupt-Thread), der auf Benutzereingaben reagiert und dann wenn jemand auf den Button tippt ein Event auslöst. Und ein Thread, der durchgehend im Hintergrund läuft (mit einer Endlosschleife) und Daten per Netzwerk einsammelt und die Tasten der Gui entsprechend setzt.

Sinn: Ein Server, der mit mehreren Clients bedient werden kann. Jeder Client soll am Server schalten können und den aktuellen Status in seiner Gui anzeigen.


Zum AsyncTask: Hier gibt es ja onPreExecute, onPostExecute (wird bei mir ja dank der Endlosschleife nie erreicht) und doInBackground (müsste doch das sein, was ausgeführt werden soll, ohne den MainThread zu stören). Also würde ich hier meine Endlosschleife in den doInBackground-Teil legen.


--> Mein Problem: Ich will einen Thread im Hintergrund laufen haben, durchgehend (solange mein Programm läuft), der die Gui ändert. Das muß doch gehen, sämtliche Börsenticker o. ä. sammeln doch auch im Hintergrund Daten ein und zeigen dann laufend aktualisiert den Stand in der Gui an. Oder?


Dennis

Antworten
Hendrik
  • Forum-Beiträge: 13

19.11.2010, 10:45:12 via Website

Hi Dennis,

ich glaub du hast meine Antwort auch nicht verstanden ;-)

Im AsyncThread läuft:
- doInBackground - in einem eigenen Hintergrundthread
- onProgressUpdate - im UI-Thread
- onPostExecute - im UI-Thread

Demnach funktionieren Änderungen an der UI nicht unmittelbar im "doInBackground" - sondern du musst deine Ergebnisse (oder Zwischenergebnisse - wie du es nutzt ist ja egal) über publishProgress() an die "onProgressUpdate" Methode schicken und da deine Änderungen an deiner UI durchführen. Wenn das "toggleButton1" halt mehrfach aufgerufen wird machst du es nicht einmalig beim "onClick" sondern in dem "onProgressUpdate".

Grüße,
Hendrik

Antworten
Tobias Eckert
  • Forum-Beiträge: 155

20.11.2010, 21:10:02 via Website

Es gibt einen Trick um GUI Elemente von ausserhalb des GUI Threads zu verändern. Du musst dazu eine Klasse bauen die java.lang.Runnable implementiert. Z.B.:

1class ButtonChange implements java.lang.Runnable {
2 public void run() {
3 //Hier den code zum Button setzen einbauen
4 }
5}

Dann instanziierst Du eine Variable dieses Klassentyps

1ButtonChange buttonChange = new ButtonChange();

Und lässt die Methode vom GUI Thread ausführen, indem Du die post() Methode Deines Haupt-Layouts verwendest:

1layout.post(buttonChange);

Antworten
Dennis Mro...
  • Forum-Beiträge: 19

17.12.2010, 10:36:11 via Website

Hallo Tobias und Hendrik,

super, ich hab's verstanden und es funktioniert.

Danke!

Dennis

Antworten
Mike S.
  • Forum-Beiträge: 12

11.06.2012, 19:44:10 via Website

Hi,

ich weiß das Thema liegt schon Jahre zurück aber beschreibt mein aktuelles Problem sehr gut! Das mit der PostUpgrade hab ich mir heute zufällig auch gedacht, dass dieser Thread ja auf UI Ebene laufe müsste. Ich habe jedoch ein Problem mit zwei nebeneinanderlaufenden AsyncTask.

Wenn ich im Preusdocode sowas wie

class blub extends AsyncTask<Void,Void,Void>{doinBackground {while(true){mach irgendwas...}}}
class blub2 extends AsyncTask<Void,Void,Void>{doinBackground mach irgendwas ohne while-true...}}

und ich dann beide mittels
new blub().execute();
new blub2().execute();

aufrufe dann laufen zwar beide wenn ich sie einzeln laufen lassen. Wenn ich aber beide laufen lasse, dass komme ich zu keinem lauffähigen Programm... Teilweise springt er mir in irgendwelche Files im Debugger wo ich irg. zu THREAD_POOL_EXECUTOR angezeigt bekomme mit den Infos aber nix anfangen kann oder er führt den Code mal aus, kommt aber nie zur onPostExecute()... Wirklich informativ etwas sehen kann ich im Debugger da auch nichts... Mir bleibt meistens nur immer StepOver zu drücken und zu hoffen das ich mal wieder Code sehe...

Bitte helft mir. Ihr habt das bestimmt schon gelöst das muss es doch irgendeinen Trick geben...

Antworten
Stefan S.
  • Forum-Beiträge: 560

13.06.2012, 07:53:23 via Website

Mike S.
Hi,

ich weiß das Thema liegt schon Jahre zurück aber beschreibt mein aktuelles Problem sehr gut! Das mit der PostUpgrade hab ich mir heute zufällig auch gedacht, dass dieser Thread ja auf UI Ebene laufe müsste. Ich habe jedoch ein Problem mit zwei nebeneinanderlaufenden AsyncTask.

Wenn ich im Preusdocode sowas wie

class blub extends AsyncTask<Void,Void,Void>{doinBackground {while(true){mach irgendwas...}}}
class blub2 extends AsyncTask<Void,Void,Void>{doinBackground mach irgendwas ohne while-true...}}

und ich dann beide mittels
new blub().execute();
new blub2().execute();

aufrufe dann laufen zwar beide wenn ich sie einzeln laufen lassen. Wenn ich aber beide laufen lasse, dass komme ich zu keinem lauffähigen Programm... Teilweise springt er mir in irgendwelche Files im Debugger wo ich irg. zu THREAD_POOL_EXECUTOR angezeigt bekomme mit den Infos aber nix anfangen kann oder er führt den Code mal aus, kommt aber nie zur onPostExecute()... Wirklich informativ etwas sehen kann ich im Debugger da auch nichts... Mir bleibt meistens nur immer StepOver zu drücken und zu hoffen das ich mal wieder Code sehe...

Bitte helft mir. Ihr habt das bestimmt schon gelöst das muss es doch irgendeinen Trick geben...

Müssen diese nacheinander laufen?

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

13.06.2012, 11:14:52 via Website

Zeig mal den Code.
Hast du am onPostExecute die @Override annotation?
Wenn nein, merkst Du nämlich garnicht, falls der Parameter garnicht mit dem aus dem Generic in der Klassendefinition übereinstimmt was dazu führt, dass die Methode nicht als überschrieben erkannt wird und folglich auch nie ausgeführt.

Antworten
Mike S.
  • Forum-Beiträge: 12

13.06.2012, 22:11:29 via Website

1package de.notionfactory.Testprojekt;
2
3import android.app.Activity;
4import android.os.AsyncTask;
5import android.os.Bundle;
6import android.util.Log;
7
8public class Main extends Activity {
9 /** Called when the activity is first created. */
10 @Override
11 public void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.main);
14
15 new send_message().execute();
16 new server().execute();
17 new send_message().execute();
18 }
19
20 public class server extends AsyncTask<String,Integer,String>
21 {
22 @Override
23 protected String doInBackground(String... params)
24 {
25 Log.d("test", "servertask gestartet");
26 // Create a Serversocket
27 while(true)
28 {
29 // Wait for incoming messages with serversocket.accept(); and work with the message
30 }
31 }
32 }
33
34 public class send_message extends AsyncTask<String,Integer,String>
35 {
36
37 @Override
38 protected String doInBackground(String... params)
39 {
40 // send some message over socket to the other server
41 return null;
42 }
43
44 @Override
45 protected void onPostExecute(String result)
46 {
47 Log.d("test", "send_message erfolgreich beendet");
48 super.onPostExecute(result);
49 }
50
51 }
52}

Ergebnis:

Die send_message wird ausgeführt
Der server wird ausgeführt
-----
Die send_message wird nicht mehr ausgeführt!

Theoretisch müsste das Android-Betriebssystem das doch aber parallel abhandeln oder nicht? Mir kommt es fast so vor als ob die AsyncTasks in einer Reihe abgehandelt werden und wenn ein AsyncTask nicht abgeschlossen wird, werden die nachfolgenden Tasks auch nicht beendet... Oder hab ich irgendeinen Denkfehler in meiner Servertask?

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.06.2012, 12:30:45 via Website

AsyncTask.execute() blockiert NICHT bis zum Ende der Ausführung, also die Tasks laufen tatsächlich soweit wie möglich parallel ab.

Ich vermute er bleibt beim zweiten mal im doInBackground() hängen.
Kann auch sein, dass du irgendwo in den inneren Klassen gemeinsame Variablen verwendest und dadurch in Problem mit Synchronisierung bekommst.
Das sieht man aber an dem bruchstückhaften Code nicht.
Da würde ich mit dem Debugger ran. Nur so findest du raus wo es de genau hakt.

Setz dir einfach die Brechpunkte an den Stellen, die dich interessieren, step dort durch und sobald der in kompilierten Code springt lässt du einfach weiterlaufen bis zum nächsten Brechpunkt.

Antworten
Mike S.
  • Forum-Beiträge: 12

14.06.2012, 16:21:26 via Website

Aber selbst dieser Beispielcode erzeugt ja den besagten Fehler ohne das ich irg. gemeinsamen Daten nutze. Kopier den Code und führ ihn aus wirst meine besagte Ausgabe kriegen...

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

14.06.2012, 16:31:52 via Website

Ah OK, dann ist die while(true) Schleife das Problem.

Lies mal die Doku zu diesen beiden Methoden:

http://developer.android.com/reference/android/os/AsyncTask.html#executeOnExecutor%28java.util.concurrent.Executor,%20Params...%29
http://developer.android.com/reference/android/os/AsyncTask.html#execute%28Params...%29

Seit Honeycomb wurde die serielle Ausführung wieder zum default gemacht und die Schleife blockiert den Thread.

— geändert am 14.06.2012, 16:32:19

Antworten
Mike S.
  • Forum-Beiträge: 12

14.06.2012, 20:58:14 via Website

Mhhh das mit Executor überfordert mich grad n bissi kannst mir kurz codetechnisch nen Ansatz geben wie ich das nun mit der while(true) schleife am besten mach. Es muss jetzt primär mal nicht um konsistente Daten gehen es soll einfach beides gleichzeitig ausgeführt werden

EDIT: Habs geschafft habs immer mit new task1().executeOnExecutor(THREAD_POOL_EXECUTOR); versucht und er kannte THREAD_POOL_EXECUTOR nicht google hat mich dann auf new task1().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); damit funktionierts nun auch ich danke recht herzlich :)

— geändert am 14.06.2012, 21:24:32

Antworten