Android AsyncTask für lange Betriebszeiten

90

Zitiert in der Dokumentation zu AsyncTask gefunden hier , heißt es:

AsyncTasks sollten idealerweise für kurze Vorgänge (höchstens einige Sekunden) verwendet werden. Wenn Sie Threads über einen längeren Zeitraum laufen lassen müssen, wird dringend empfohlen, die verschiedenen APIs zu verwenden, die vom Paket java.util.concurrent bereitgestellt werden, z Executor, ThreadPoolExecutor und FutureTask.

Nun stellt sich meine Frage: Warum? Die doInBackground()Funktion wird vom UI-Thread ausgeführt. Welchen Schaden kann es also haben, wenn hier eine lange Operation ausgeführt wird?

user1730789
quelle
2
Das Problem beim Bereitstellen einer App (die asyncTask verwendet) auf einem tatsächlichen Gerät bestand darin, dass die Langzeitfunktion doInBackgroundden Bildschirm einfriert, wenn kein Fortschrittsbalken verwendet wird.
VenkatKA
2
Da eine AsyncTask an die Aktivität gebunden ist, in der sie gestartet wird. Wenn die Aktivität beendet wird, kann auch Ihre AsyncTask-Instanz beendet werden.
IgorGanapolsky
Was passiert, wenn ich die AsyncTask innerhalb eines Dienstes erstelle? Würde das das Problem nicht lösen?
Wirbel
1
Verwenden Sie IntentService, die perfekte Lösung, um lange Vorgänge im Hintergrund auszuführen.
Keyur Thumar

Antworten:

120

Es ist eine sehr gute Frage, es braucht Zeit als Android-Programmierer, um das Problem vollständig zu verstehen. In der Tat haben AsyncTask zwei Hauptprobleme, die miteinander zusammenhängen:

  • Sie sind schlecht an den Aktivitätslebenszyklus gebunden
  • Sie verursachen sehr leicht Speicherlecks.

In der RoboSpice Motivations-App ( verfügbar bei Google Play ) beantworten wir diese Frage ausführlich. Es bietet einen detaillierten Überblick über AsyncTasks, Loader, deren Funktionen und Nachteile und stellt Ihnen eine alternative Lösung für Netzwerkanforderungen vor: RoboSpice. Netzwerkanforderungen sind eine häufige Anforderung in Android und werden von Natur aus lange ausgeführt. Hier ist ein Auszug aus der App:

Der Lebenszyklus von AsyncTask und Aktivität

AsyncTasks folgen nicht dem Lebenszyklus von Aktivitätsinstanzen. Wenn Sie eine AsyncTask innerhalb einer Aktivität starten und das Gerät drehen, wird die Aktivität zerstört und eine neue Instanz erstellt. Aber die AsyncTask wird nicht sterben. Es wird weiterleben, bis es fertig ist.

Wenn dies abgeschlossen ist, aktualisiert die AsyncTask die Benutzeroberfläche der neuen Aktivität nicht. In der Tat wird die frühere Instanz der Aktivität aktualisiert, die nicht mehr angezeigt wird. Dies kann zu einer Ausnahme vom Typ java.lang.IllegalArgumentException führen: Ansicht, die nicht an den Fenstermanager angehängt ist, wenn Sie beispielsweise findViewById verwenden, um eine Ansicht innerhalb der Aktivität abzurufen.

Speicherverlust Problem

Es ist sehr praktisch, AsyncTasks als innere Klassen Ihrer Aktivitäten zu erstellen. Da die AsyncTask die Ansichten der Aktivität bearbeiten muss, wenn die Aufgabe abgeschlossen ist oder ausgeführt wird, erscheint die Verwendung einer inneren Klasse der Aktivität praktisch: Innere Klassen können direkt auf jedes Feld der äußeren Klasse zugreifen.

Dies bedeutet jedoch, dass die innere Klasse einen unsichtbaren Verweis auf ihre äußere Klasseninstanz enthält: die Aktivität.

Auf lange Sicht führt dies zu einem Speicherverlust: Wenn die AsyncTask lange anhält, bleibt die Aktivität "am Leben", während Android sie gerne entfernen möchte, da sie nicht mehr angezeigt werden kann. Die Aktivität kann nicht durch Müll gesammelt werden. Dies ist ein zentraler Mechanismus für Android, um Ressourcen auf dem Gerät zu schonen.


Es ist wirklich eine sehr, sehr schlechte Idee, AsyncTasks für lange laufende Vorgänge zu verwenden. Trotzdem eignen sie sich gut für kurzlebige Personen wie das Aktualisieren einer Ansicht nach 1 oder 2 Sekunden.

Ich ermutige Sie, die herunterzuladen empfehle RoboSpice Motivations-App Sie erklärt dies ausführlich und bietet Beispiele und Demonstrationen der verschiedenen Möglichkeiten, einige Hintergrundoperationen durchzuführen.

Snicolas
quelle
@Snicolas Hallo. Ich habe eine App, in der Daten von einem NFC-Tag gescannt und an den Server gesendet werden. Es funktioniert gut in guten Signalbereichen, aber wo kein Signal vorhanden ist, läuft die AsyncTask, die den Webcall auslöst, weiter. Das Dialogfeld "Fortschritt" wird beispielsweise minutenlang ausgeführt. Wenn es dann verschwindet, wird der Bildschirm schwarz und reagiert nicht mehr. Meine AsyncTask ist eine innere Klasse. Ich schreibe einen Handler, um die Aufgabe nach X Sekunden abzubrechen. Die App scheint alte Daten Stunden nach dem Scan an den Server zu senden. Könnte dies daran liegen, dass AsyncTask nicht beendet wird und möglicherweise Stunden später abgeschlossen wird? Ich würde mich über jeden Einblick freuen. danke
turtleboy
Verfolgen Sie, um zu sehen, was passiert. Aber ja, das ist durchaus möglich! Wenn Sie Ihre Asynctask gut planen, können Sie sie ziemlich richtig abbrechen. Dies wäre ein guter Ausgangspunkt, wenn Sie nicht auf RS oder einen Dienst migrieren möchten ...
Snicolas
@Snicolas Danke für die Antwort. Ich habe gestern einen Beitrag auf SO verfasst, in dem ich mein Problem skizzierte und den Handler-Code zeigte, den ich geschrieben habe, um zu versuchen, die AsyncTask nach 8 Sekunden zu stoppen. Würde es Ihnen etwas ausmachen, einen Blick darauf zu werfen, wenn Sie Zeit haben? Bricht der Aufruf von AsyncTask.cancel (true) von einem Handler die Aufgabe ordnungsgemäß ab? Ich weiß, dass ich den Wert von iscancelled () in meinem doInBackgroud regelmäßig überprüfen sollte, aber ich denke nicht, dass dies auf meine Umstände zutrifft, da ich nur einen einzeiligen Webcall HttpPost mache und keine Updates auf der Benutzeroberfläche veröffentliche. Gibt es dort Alternativen zu AsyncTask zB ist es möglich, einen HttPost aus einem IntentService
turtleboy
Hier ist der Link stackoverflow.com/questions/17725767/…
Turtleboy
Sie sollten RoboSpice (auf Github) ausprobieren. ;)
Snicolas
38

Warum ?

Weil AsyncTaskstandardmäßig ein Thread-Pool verwendet wird , den Sie nicht erstellt haben . Binden Sie niemals Ressourcen aus einem Pool, den Sie nicht erstellt haben, da Sie die Anforderungen dieses Pools nicht kennen. Binden Sie niemals Ressourcen aus einem Pool, den Sie nicht erstellt haben, wenn die Dokumentation für diesen Pool Sie dazu auffordert, dies nicht zu tun, wie dies hier der Fall ist.

Insbesondere ab Android 3.2 enthält der AsyncTaskstandardmäßig verwendete Thread-Pool (für Apps mit einer android:targetSdkVersionEinstellung von 13 oder höher) nur einen Thread. Wenn Sie diesen Thread auf unbestimmte Zeit binden, wird keine Ihrer anderen Aufgaben ausgeführt.

CommonsWare
quelle
Vielen Dank für diese Erklärung. Ich konnte nichts wirklich Fehlerhaftes an der Verwendung von AsyncTasks für langfristige Vorgänge feststellen (obwohl ich diese normalerweise selbst an Services delegiere), aber Ihr Argument, diesen ThreadPool zu binden (wofür Sie dies tatsächlich können keine Annahmen über seine Größe machen) scheint genau richtig. Als Ergänzung zu Anups Beitrag über die Verwendung eines Dienstes: Dieser Dienst sollte die Aufgabe selbst auch in einem Hintergrundthread ausführen und nicht seinen eigenen Hauptthread blockieren. Eine Option kann ein IntentService sein oder bei komplexeren Anforderungen an die Parallelität Ihre eigene Multithreading-Strategie anwenden.
Baske
Ist es das gleiche, auch wenn ich die AsyncTask innerhalb eines Dienstes starte? Ich meine, wäre der Langzeitbetrieb immer noch ein Problem?
Wirbel
1
@eddy: Ja, da dies die Art des Thread-Pools nicht ändert. ServiceVerwenden Sie für a einfach a Threadoder a ThreadPoolExecutor.
CommonsWare
Vielen Dank an @CommonsWare, nur die letzte Frage: Haben TimerTasks dieselben Nachteile wie AsyncTasks? oder ist es eine ganz andere Sache?
Wirbel
1
@eddy: TimerTaskist von Standard Java, nicht Android. TimerTaskwurde weitgehend zugunsten von aufgegeben ScheduledExecutorService(das trotz seines Namens Teil von Standard-Java ist). Beide sind nicht an Android gebunden, sodass Sie immer noch einen Dienst benötigen, wenn Sie erwarten, dass diese Dinge im Hintergrund ausgeführt werden. Und Sie sollten wirklich in Betracht ziehen AlarmManager, von Android aus, damit Sie keinen Dienst benötigen, der nur die Uhr tickt.
CommonsWare
4

Aysnc-Aufgaben sind spezialisierte Threads, die weiterhin für die Benutzeroberfläche Ihrer Apps verwendet werden sollen, aber gleichzeitig die ressourcenintensiven Aufgaben des UI-Threads beibehalten. Wenn Sie beispielsweise beim Aktualisieren von Listen, Ändern Ihrer Ansichten usw. einige Abruf- oder Aktualisierungsvorgänge ausführen müssen, sollten Sie asynchrone Aufgaben verwenden, damit Sie diese Vorgänge vom UI-Thread fernhalten können. Beachten Sie jedoch, dass diese Vorgänge weiterhin irgendwie mit der UI verbunden sind .

Für länger laufende Aufgaben, für die keine Aktualisierung der Benutzeroberfläche erforderlich ist, können Sie stattdessen Dienste verwenden, da diese auch ohne Benutzeroberfläche ausgeführt werden können.

Verwenden Sie für kurze Aufgaben asynchrone Aufgaben, da diese vom Betriebssystem beendet werden können, nachdem Ihre Laichaktivität beendet ist (normalerweise stirbt sie nicht während des Betriebs, sondern erledigt ihre Aufgabe). Verwenden Sie stattdessen Dienste für lange und sich wiederholende Aufgaben.

Weitere Informationen finden Sie unter Themen:

AsyncTask länger als ein paar Sekunden?

und

AsyncTask wird auch dann nicht gestoppt, wenn die Aktivität zerstört wurde

Anup Cowkur
quelle
1

Das Problem mit AsyncTask besteht darin, dass, wenn es als nicht statische innere Klasse der Aktivität definiert ist, ein Verweis auf die Aktivität vorhanden ist. In dem Szenario, in dem die Aktivität des Containers der asynchronen Aufgabe beendet wird, die Hintergrundarbeit in AsyncTask jedoch fortgesetzt wird, wird das Aktivitätsobjekt nicht durch Müll gesammelt, da ein Verweis darauf vorhanden ist. Dies führt zu einem Speicherverlust.

Die Lösung, um dies zu beheben, besteht darin, eine asynchrone Aufgabe als statische innere Aktivitätsklasse zu definieren und einen schwachen Verweis auf den Kontext zu verwenden.

Trotzdem ist es eine gute Idee, es für einfache und schnelle Hintergrundaufgaben zu verwenden. Um eine App mit sauberem Code zu entwickeln, ist es besser, RxJava zu verwenden, um komplexe Hintergrundaufgaben auszuführen und die Benutzeroberfläche mit den daraus resultierenden Ergebnissen zu aktualisieren.

Arnav Rao
quelle