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?
Einfache Antwort: Sie haben gerade
entdeckt 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
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
.
AsyncTask
in View
Ein korrekt implementiertes AsyncTask
, das ein AsyncTask
verwendet, könnte folgendermaßen aussehen:
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.
Das sieht ziemlich ähnlich aus, weil es eigentlich ziemlich ähnlich ist. Sie können es so in ImageView
oder Activity
verwenden:
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:
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.
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):
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:
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.
Tags und Links java android android-asynctask