Ich habe dieses Problem seit Monaten untersucht und verschiedene Lösungen gefunden, mit denen ich nicht zufrieden bin, da es sich um massive Hacks handelt. Ich kann immer noch nicht glauben, dass eine Klasse, die im Design fehlerhaft war, es in das Framework geschafft hat und niemand darüber spricht, also muss mir wohl etwas fehlen.
Das Problem ist mit AsyncTask
. Laut Dokumentation ist es
"Ermöglicht das Ausführen von Hintergrundoperationen und das Veröffentlichen von Ergebnissen auf dem UI-Thread, ohne dass Threads und / oder Handler bearbeitet werden müssen."
Das Beispiel zeigt dann weiterhin, wie eine beispielhafte showDialog()
Methode aufgerufen wird onPostExecute()
. Dies scheint mir jedoch völlig erfunden zu sein, da das Context
Anzeigen eines Dialogfelds immer einen Verweis auf ein gültiges Objekt erfordert und eine AsyncTask niemals einen starken Verweis auf ein Kontextobjekt enthalten darf .
Der Grund liegt auf der Hand: Was passiert, wenn die Aktivität zerstört wird, die die Aufgabe ausgelöst hat? Dies kann die ganze Zeit passieren, z. B. weil Sie den Bildschirm umgedreht haben. Wenn die Aufgabe einen Verweis auf den Kontext enthält, der sie erstellt hat, halten Sie nicht nur an einem nutzlosen Kontextobjekt fest (das Fenster wurde zerstört und jede UI-Interaktion schlägt mit einer Ausnahme fehl!), Sondern Sie riskieren sogar das Erstellen eines Speicherleck.
Sofern meine Logik hier nicht fehlerhaft ist, bedeutet dies: onPostExecute()
ist völlig nutzlos, denn was nützt es, wenn diese Methode auf dem UI-Thread ausgeführt wird, wenn Sie keinen Zugriff auf einen Kontext haben? Sie können hier nichts Sinnvolles tun.
Eine Problemumgehung besteht darin, keine Kontextinstanzen an eine AsyncTask zu übergeben, sondern eine Handler
Instanz. Das funktioniert: Da ein Handler den Kontext und die Aufgabe lose bindet, können Sie Nachrichten zwischen ihnen austauschen, ohne ein Leck zu riskieren (richtig?). Dies würde jedoch bedeuten, dass die Prämisse von AsyncTask, dass Sie sich nicht um Handler kümmern müssen, falsch ist. Es scheint auch so, als würde man Handler missbrauchen, da Sie Nachrichten im selben Thread senden und empfangen (Sie erstellen sie im UI-Thread und senden sie in onPostExecute () durch, das auch im UI-Thread ausgeführt wird).
Um das Ganze abzurunden, haben Sie trotz dieser Problemumgehung immer noch das Problem, dass Sie bei Zerstörung des Kontexts keine Aufzeichnungen über die von ihm ausgelösten Aufgaben haben. Das bedeutet, dass Sie alle Aufgaben neu starten müssen, wenn Sie den Kontext neu erstellen, z. B. nach einer Änderung der Bildschirmausrichtung. Das ist langsam und verschwenderisch.
Meine Lösung hierfür (wie in der Droid-Fu-Bibliothek implementiert ) besteht darin, eine Zuordnung von WeakReference
s von Komponentennamen zu ihren aktuellen Instanzen auf dem eindeutigen Anwendungsobjekt beizubehalten . Jedes Mal, wenn eine AsyncTask gestartet wird, zeichnet sie den aufrufenden Kontext in dieser Zuordnung auf und ruft bei jedem Rückruf die aktuelle Kontextinstanz aus dieser Zuordnung ab. Dies stellt sicher, dass Sie niemals auf eine veraltete Kontextinstanz verweisen und in den Rückrufen immer Zugriff auf einen gültigen Kontext haben, damit Sie dort sinnvolle UI-Arbeiten ausführen können. Es leckt auch nicht, da die Referenzen schwach sind und gelöscht werden, wenn keine Instanz einer bestimmten Komponente mehr vorhanden ist.
Trotzdem ist es eine komplexe Problemumgehung und erfordert die Unterklasse einiger Droid-Fu-Bibliotheksklassen, was dies zu einem ziemlich aufdringlichen Ansatz macht.
Jetzt möchte ich einfach nur wissen: Vermisse ich nur massiv etwas oder ist AsyncTask wirklich völlig fehlerhaft? Wie arbeiten Ihre Erfahrungen damit? Wie haben Sie dieses Problem gelöst?
Danke für deinen Beitrag.
quelle
Antworten:
Wie wäre es mit so etwas:
quelle
Trennen Sie die Aktivität manuell von der
AsyncTask
inonDestroy()
. Ordnen Sie die neue Aktivität demAsyncTask
In manuell zuonCreate()
. Dies erfordert entweder eine statische innere Klasse oder eine Standard-Java-Klasse sowie möglicherweise 10 Codezeilen.quelle
Es sieht so aus, als wäre
AsyncTask
es ein bisschen mehr als nur konzeptionell fehlerhaft . Es ist auch durch Kompatibilitätsprobleme unbrauchbar. Die Android-Dokumente lauten:Bei der ersten Einführung wurden AsyncTasks seriell auf einem einzelnen Hintergrundthread ausgeführt. Beginnend mit DONUT wurde dies in einen Thread-Pool geändert, sodass mehrere Aufgaben gleichzeitig ausgeführt werden können. Beim Starten von HONEYCOMB werden Aufgaben wieder in einem einzelnen Thread ausgeführt, um häufige Anwendungsfehler zu vermeiden, die durch parallele Ausführung verursacht werden. Wenn Sie wirklich eine parallele Ausführung wünschen, können Sie die
executeOnExecutor(Executor, Params...)
Version dieser Methode mit verwendenTHREAD_POOL_EXECUTOR
. Im Kommentar finden Sie jedoch Warnungen zu seiner Verwendung.Beide
executeOnExecutor()
undTHREAD_POOL_EXECUTOR
werden in API Level 11 (Android 3.0.x, HONEYCOMB) hinzugefügt.Dies bedeutet, dass wenn Sie zwei
AsyncTask
s erstellen , um zwei Dateien herunterzuladen, der zweite Download erst beginnt, wenn der erste abgeschlossen ist. Wenn Sie über zwei Server chatten und der erste Server ausfällt, stellen Sie keine Verbindung zum zweiten Server her, bevor die Verbindung zum ersten Server unterbrochen wird. (Es sei denn, Sie verwenden natürlich die neuen API11-Funktionen, aber dadurch wird Ihr Code mit 2.x nicht kompatibel.)Und wenn Sie sowohl auf 2.x als auch auf 3.0+ abzielen möchten, wird das Zeug wirklich knifflig.
Darüber hinaus sagen die Dokumente :
Achtung: Ein weiteres Problem, das bei der Verwendung eines Arbeitsthreads auftreten kann, ist ein unerwarteter Neustart Ihrer Aktivität aufgrund einer Änderung der Laufzeitkonfiguration (z. B. wenn der Benutzer die Bildschirmausrichtung ändert), wodurch Ihr Arbeitsthread möglicherweise zerstört wird . Informationen dazu, wie Sie Ihre Aufgabe während eines dieser Neustarts beibehalten und die Aufgabe ordnungsgemäß abbrechen können, wenn die Aktivität zerstört wird, finden Sie im Quellcode der Shelves-Beispielanwendung.
quelle
Wahrscheinlich missbrauchen wir alle, einschließlich Google,
AsyncTask
aus MVC- Sicht.Eine Aktivität ist ein Controller , und der Controller sollte keine Vorgänge starten, die die Ansicht möglicherweise überleben . Das heißt, AsyncTasks sollten von Model aus einer Klasse verwendet werden, die nicht an den Aktivitätslebenszyklus gebunden ist. Denken Sie daran, dass Aktivitäten bei Rotation zerstört werden. (In Bezug auf die Ansicht programmieren Sie normalerweise keine Klassen, die beispielsweise von android.widget.Button abgeleitet sind, aber Sie können dies. Normalerweise ist das einzige, was Sie an der Ansicht tun, die XML.)
Mit anderen Worten, es ist falsch, AsyncTask-Derivate in die Methoden der Aktivitäten einzufügen. OTOH, wenn wir AsyncTasks nicht in Aktivitäten verwenden dürfen, verliert AsyncTask seine Attraktivität: Früher wurde es als schnelle und einfache Lösung beworben.
quelle
Ich bin nicht sicher, ob es wahr ist, dass Sie einen Speicherverlust mit einem Verweis auf einen Kontext aus einer AsyncTask riskieren.
Die übliche Art, sie zu implementieren, besteht darin, eine neue AsyncTask-Instanz im Rahmen einer der Methoden der Aktivität zu erstellen. Wenn die Aktivität zerstört wird, ist sie dann nach Abschluss der AsyncTask nicht unerreichbar und kann dann zur Speicherbereinigung verwendet werden? Der Verweis auf die Aktivität spielt also keine Rolle, da die AsyncTask selbst nicht herumhängt.
quelle
Es wäre robuster, eine Wochenreferenz über Ihre Aktivität zu führen:
quelle
Warum nicht einfach die
onPause()
Methode in der Besitzaktivität überschreiben und dieAsyncTask
von dort abbrechen ?quelle
onPause
dass er genauso schlecht ist wie irgendwo anders? Dh du könntest eine ANR bekommen?onPause
oder ein anderer), da wir das Risiko haben, eine ANR zu erhalten.Sie haben absolut Recht - deshalb gewinnt eine Abkehr von der Verwendung asynchroner Aufgaben / Lader in den Aktivitäten zum Abrufen von Daten an Dynamik. Eine der neuen Möglichkeiten ist die Verwendung eines Volley- Frameworks, das im Wesentlichen einen Rückruf bereitstellt, sobald die Daten bereit sind - viel konsistenter mit dem MVC-Modell. Volley wurde in Google I / O 2013 bevölkert. Ich bin mir nicht sicher, warum mehr Menschen dies nicht wissen.
quelle
Persönlich erweitere ich nur Thread und verwende eine Rückrufschnittstelle, um die Benutzeroberfläche zu aktualisieren. Ich könnte AsyncTask ohne FC-Probleme nie richtig zum Laufen bringen. Ich verwende auch eine nicht blockierende Warteschlange, um den Ausführungspool zu verwalten.
quelle
Ich dachte, Abbrechen funktioniert, aber es funktioniert nicht.
hier RTFMing darüber:
"" Wenn die Aufgabe bereits gestartet wurde, bestimmt der Parameter mayInterruptIfRunning, ob der Thread, der diese Aufgabe ausführt, unterbrochen werden soll, um die Aufgabe zu stoppen. "
Dies bedeutet jedoch nicht, dass der Thread unterbrechbar ist. Das ist eine Java-Sache, keine AsyncTask-Sache. "
http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d
quelle
Sie sollten sich AsyncTask besser als etwas vorstellen, das enger mit einer Aktivität, einem Kontext, einem ContextWrapper usw. verbunden ist. Es ist praktischer, wenn der Umfang vollständig verstanden wird.
Stellen Sie sicher, dass Sie in Ihrem Lebenszyklus eine Stornierungsrichtlinie haben, damit diese schließlich durch Müll gesammelt wird und keinen Verweis mehr auf Ihre Aktivität enthält und auch Müll gesammelt werden kann.
Wenn Sie Ihre AsyncTask nicht abbrechen, während Sie sich von Ihrem Kontext entfernen, treten Speicherlecks und NullPointerExceptions auf. Wenn Sie lediglich Feedback wie einen Toast-Dialog in einem einfachen Dialogfeld benötigen, kann ein Singleton Ihres Anwendungskontexts das NPE-Problem vermeiden.
AsyncTask ist nicht alles schlecht, aber es gibt definitiv viel Magie, die zu unvorhergesehenen Fallstricken führen kann.
quelle
In Bezug auf „Erfahrungen mit ihm arbeiten“: es ist möglich , um den Prozess zu töten zusammen mit allen AsyncTasks, Android wird die Aktivität Stapel neu zu erstellen , so dass der Benutzer nichts erwähnen.
quelle