Wie kann eine AsyncTask weiterhin eine Aktivität verwenden, wenn der Benutzer bereits von dieser entfernt ist?

7

Unter Android können Sie in einem separaten Thread arbeiten, indem Sie beispielsweise Runnable oder AsyncTask verwenden. In beiden Fällen müssen Sie nach Abschluss der Arbeit möglicherweise etwas arbeiten, z. B. durch Überschreiben von onPostExecute() in AsyncTask . Der Benutzer kann jedoch navigieren oder die App schließen, während die Arbeit im Hintergrund ausgeführt wird.

Meine Frage ist: Was passiert, wenn der Benutzer weg navigiert oder die App schließt, während ich noch einen Verweis auf den Activity habe, den der Benutzer gerade in meinem AsyncTask geschlossen hat?

Meine Vermutung ist, dass es zerstört werden sollte, sobald der Benutzer weg navigiert, aber wenn ich es aus irgendeinem Grund auf einem Gerät ausprobiere, kann ich immer noch Methoden auf dem Activity aufrufen, obwohl es bereits weg ist! Was geht hier vor sich?

    
Fattum 29.01.2016, 14:30
quelle

1 Antwort

24

Einfache Antwort: Sie haben gerade

entdeckt

Speicherlecks

Solange ein Teil der App wie ein AsyncTask immer noch einen Verweis auf Activity enthält, wird nicht zerstört . Es bleibt so lange bestehen, bis die AsyncTask fertig ist, oder gibt ihre Referenz auf andere Weise frei. Dies kann sehr schlimme Folgen haben, wenn Ihre App abstürzt, aber die schlimmsten Folgen sind die, die Sie nicht bemerken: Ihre App behält möglicherweise den Verweis auf Activities , der vor Jahren veröffentlicht werden sollte, und jedes Mal, wenn der Benutzer das macht co_de% Der Speicher auf dem Gerät wird möglicherweise immer voller, bis scheinbar aus dem Nichts Android Ihre App für zu viel Speicher verbraucht. Speicherlecks sind die häufigsten und schlimmsten Fehler, die ich in Android-Fragen zu Stack Overflow sehe

Die Lösung

Das Vermeiden von Speicherlecks ist jedoch sehr einfach: Ihr Activity sollte nie einen Verweis auf eine AsyncTask , Activity oder eine andere UI-Komponente haben.

Verwenden Sie stattdessen das Listener-Muster und verwenden Sie immer Service . Halte niemals starke Referenzen auf etwas außerhalb des WeakReference .

Einige Beispiele

Verweis auf AsyncTask in View

Ein korrekt implementiertes AsyncTask , das ein AsyncTask verwendet, könnte folgendermaßen aussehen:

%Vor%

Dies zeigt perfekt, was ein ImageView tut. WeakReference erlauben die WeakReferences , auf die sie verweisen, als Garbage Collection. In diesem Beispiel erstellen wir ein Object zu einem WeakReference im Konstruktor von ImageView . Dann in AsyncTask , was 10 Sekunden später aufgerufen wird, wenn onPostExecute() nicht mehr existiert, rufen wir ImageView auf get() auf, um zu sehen, ob WeakReference existiert. Solange das von ImageView zurückgegebene ImageView nicht null ist, wurde das get() nicht als Garbage Collection erfasst und wir können es daher ohne Bedenken verwenden! Sollte in der Zwischenzeit der Benutzer die App verlassen, wird die ImageView sofort für die Garbage Collection verfügbar und wenn die ImageView einige Zeit später beendet wird, sieht sie, dass die AsyncTask bereits verschwunden ist. Keine Speicherlecks, keine Probleme.

Verwenden eines Listeners

%Vor%

Das sieht ziemlich ähnlich aus, weil es eigentlich ziemlich ähnlich ist. Sie können es so in ImageView oder Activity verwenden:

%Vor%

Oder Sie können es wie folgt verwenden:

%Vor%

Fragment und seine Folgen bei Verwendung eines Listeners

Aber es gibt noch eine andere Sache, die Sie beachten müssen. Eine Konsequenz, nur einen WeakReference für den Listener zu haben. Stellen Sie sich vor, Sie implementieren die Listener-Schnittstelle wie folgt:

%Vor%

Eine ziemlich ungewöhnliche Möglichkeit dies zu tun - ich weiß - aber etwas ähnliches könnte sich irgendwo in deinen Code einschleichen, ohne dass du es weißt und die Konsequenzen können schwer zu debuggen sein. Ist Ihnen jetzt aufgefallen, was mit dem obigen Beispiel falsch sein könnte? Versuchen Sie herauszufinden, ansonsten lesen Sie weiter unten.

Das Problem ist einfach: Sie erstellen eine Instanz von WeakReference , die Ihren Verweis auf ExampleListener enthält. Dann übergeben Sie es in die ImageView und starten Sie die Aufgabe. Und dann endet die ExampleTask -Methode, sodass alle lokalen Variablen für die Garbage-Collection infrage kommen. Es gibt keinen starken Verweis auf die doSomething() -Instanz, die Sie an die ExampleListener weitergegeben haben, es gibt nur eine ExampleTask . Die WeakReference wird also als Garbage Collection gesammelt und wenn die ExampleListener beendet ist passiert nichts. Wenn der ExampleTask schnell genug ausgeführt wird, hat der Garbage Collector die ExampleTask Instanz möglicherweise noch nicht erfasst, so dass er möglicherweise zeitweise oder überhaupt nicht funktioniert. Und Debugging-Probleme wie diese können ein Albtraum sein. Die Moral der Geschichte lautet also: Seien Sie sich Ihrer starken und schwachen Referenzen immer bewusst, und wenn Objekte für die Garbage Collection geeignet sind.

Verschachtelte Klassen und Verwendung von ExampleListener

Eine weitere Sache, die wahrscheinlich die Ursache für die meisten Speicherlecks ist, sehe ich in Stack Overflow-Leuten, die verschachtelte Klassen falsch benutzen. Sehen Sie sich das folgende Beispiel an und versuchen Sie im folgenden Beispiel herauszufinden, was zu einem Speicherverlust führt:

%Vor%

Siehst du es? Hier ist ein anderes Beispiel mit genau dem gleichen Problem, es sieht einfach anders aus:

%Vor%

Haben Sie herausgefunden, was das Problem ist? Ich sehe täglich Leute unvorsichtigerweise Dinge wie oben implementieren, wahrscheinlich ohne zu wissen, was sie falsch machen. Das Problem ist eine Konsequenz davon, wie Java auf einem grundlegenden Merkmal von Java basiert - es gibt keine Entschuldigung, Leute, die Dinge wie oben implementieren, sind entweder betrunken oder wissen nichts über Java. Lassen Sie uns das Problem vereinfachen:

Stellen Sie sich vor, Sie hätten eine geschachtelte Klasse wie folgt:

%Vor%

Wenn Sie das tun, können Sie innerhalb der Klasse static auf Mitglieder der Klasse A zugreifen. So kann B doIt() drucken, hat Zugriff auf alle Mitglieder von mSomeText , auch private.
Der Grund dafür ist, dass Java, wenn Sie Klassen verschachteln, implizit einen Verweis auf A innerhalb von A erstellt. Aufgrund dieser Referenz und nichts anderem haben Sie Zugriff auf alle Mitglieder von B innerhalb von A .Jedoch im Zusammenhang mit Speicherlecks, die wieder ein Problem darstellen, wenn Sie nicht wissen, was Sie tun. Betrachten Sie das erste Beispiel (ich streiche alle Teile ab, die in dem Beispiel keine Rolle spielen):

%Vor%

Also haben wir eine B als verschachtelte Klasse innerhalb eines AsyncTask . Da die geschachtelte Klasse nicht statisch ist, können wir auf Mitglieder von Activity innerhalb von ExampleActivity zugreifen. Es spielt keine Rolle, dass ExampleTask tatsächlich nicht auf Member von ExampleTask zugreift, da es sich um eine nicht statische geschachtelte Klasse handelt Java erstellt implizit einen Verweis auf Activity in Activity und so mit scheinbar keine sichtbare Ursache, wir haben ein Speicherleck. Wie können wir das beheben? Sehr einfach. Wir müssen nur ein Wort hinzufügen und das ist statisch:

%Vor%

Gerade dieses eine fehlende Schlüsselwort in einer einfachen geschachtelten Klasse ist der Unterschied zwischen einem Speicherleck und vollständigem Feincode. Versuchen Sie wirklich, das Problem hier zu verstehen, denn es ist der Kern dessen, wie Java funktioniert, und dies zu verstehen ist entscheidend.

Und wie für das andere Beispiel mit dem ExampleTask ? Das genau gleiche Problem, anonyme Klassen wie diese sind auch nur nicht-statische verschachtelte Klassen und sofort ein Speicherleck. Aber es ist tatsächlich eine Million mal schlimmer. Aus jedem Blickwinkel schaust du darauf, dass Thread nur ein schrecklicher Code ist. Um jeden Preis vermeiden.

Ich hoffe, dieses Beispiel hat Ihnen geholfen, das Problem zu verstehen und Code frei von Speicherlecks zu schreiben. Wenn Sie weitere Fragen haben, zögern Sie nicht zu fragen.

    
Xaver Kapeller 29.01.2016, 14:43
quelle

Tags und Links