Ich habe 4 volle Tage damit verbracht, alles zu versuchen, um den Speicherverlust in einer App herauszufinden, die ich entwickle, aber die Dinge haben vor langer Zeit keinen Sinn mehr ergeben.
Die App, die ich entwickle, ist sozialer Natur. Denken Sie also an Profilaktivitäten (P) und listen Sie Aktivitäten mit Daten auf - zum Beispiel Abzeichen (B). Sie können vom Profil zu einer Ausweisliste zu anderen Profilen, zu anderen Listen usw. springen.
Stellen Sie sich also einen Fluss wie diesen vor: P1 -> B1 -> P2 -> B2 -> P3 -> B3 usw. Aus Gründen der Konsistenz lade ich Profile und Abzeichen desselben Benutzers, sodass jede P-Seite gleich ist und so ist jede B-Seite.
Der allgemeine Kern des Problems ist: Nach einigem Navigieren, abhängig von der Größe jeder Seite, wird an zufälligen Stellen - Bitmaps, Strings usw. - eine Ausnahme wegen Speichermangel angezeigt. Diese scheint nicht konsistent zu sein.
Nachdem ich alles Mögliche getan habe, um herauszufinden, warum mir das Gedächtnis ausgeht, habe ich mir nichts ausgedacht. Was ich nicht verstehe, ist, warum Android P1, B1 usw. nicht beendet, wenn beim Laden nicht genügend Speicherplatz zur Verfügung steht und stattdessen abstürzt. Ich würde erwarten, dass diese früheren Aktivitäten sterben und wiederbelebt werden, wenn ich jemals über onCreate () und onRestoreInstanceState () zu ihnen zurückkehre.
Geschweige denn - selbst wenn ich P1 -> B1 -> Zurück -> B1 -> Zurück -> B1 mache, bekomme ich immer noch einen Absturz. Dies deutet auf eine Art Speicherverlust hin, aber selbst nach dem Dumping von hprof und der Verwendung von MAT und JProfiler kann ich es nicht genau bestimmen.
Ich habe das Laden von Bildern aus dem Web deaktiviert (und die geladenen Testdaten erhöht, um dies auszugleichen und den Test fair zu gestalten) und sichergestellt, dass der Bildcache SoftReferences verwendet. Android versucht tatsächlich, die wenigen SoftReferences freizugeben, die es hat, aber kurz bevor es aus dem Speicher abstürzt.
Ausweisseiten rufen Daten aus dem Web ab, laden sie von einem BaseAdapter in ein Array von EntityData und geben sie an eine ListView weiter (ich verwende tatsächlich den ausgezeichneten MergeAdapter von CommonsWare , aber in dieser Ausweisaktivität gibt es sowieso nur 1 Adapter, aber ich wollte diese Tatsache so oder so erwähnen).
Ich habe den Code durchgesehen und konnte nichts finden, was auslaufen würde. Ich habe alles gelöscht und auf Null gesetzt, was ich finden konnte, und sogar System.gc () links und rechts, aber die App stürzt immer noch ab.
Ich verstehe immer noch nicht, warum inaktive Aktivitäten, die sich auf dem Stapel befinden, nicht geerntet werden, und ich würde das wirklich gerne herausfinden.
An dieser Stelle suche ich nach Hinweisen, Ratschlägen, Lösungen ... allem, was helfen könnte.
Danke dir.
quelle
outOfMemory
Fehlermeldung. Vielen Dank!Antworten:
So funktionieren die Dinge nicht. Die einzige Speicherverwaltung, die sich auf den Aktivitätslebenszyklus auswirkt, ist der globale Speicher über alle Prozesse hinweg, da Android entscheidet, dass der Speicher knapp wird und daher Hintergrundprozesse abgebrochen werden müssen, um einige zurückzubekommen.
Wenn Ihre Anwendung im Vordergrund steht und immer mehr Aktivitäten startet, tritt sie nie in den Hintergrund, sodass sie immer ihre lokale Prozessspeichergrenze erreicht, bevor das System jemals den Prozess beendet. (Und wenn es seinen Prozess abbricht, wird es den Prozess abbrechen, der alle Aktivitäten hostet, einschließlich dessen, was gerade im Vordergrund steht.)
Für mich klingt es so, als ob Ihr Grundproblem darin besteht, dass Sie zu viele Aktivitäten gleichzeitig ausführen lassen und / oder jede dieser Aktivitäten zu viele Ressourcen enthält.
Sie müssen lediglich Ihre Navigation neu gestalten, um nicht darauf angewiesen zu sein, eine beliebige Anzahl potenziell schwergewichtiger Aktivitäten zu stapeln. Wenn Sie in onStop () nicht viel tun (z. B. setContentView () aufrufen, um die Ansichtshierarchie der Aktivität zu löschen und Variablen zu löschen, an denen sie möglicherweise noch festhält), wird Ihnen nur der Speicher ausgehen.
Möglicherweise möchten Sie die neuen Fragment-APIs verwenden, um diesen beliebigen Stapel von Aktivitäten durch eine einzelne Aktivität zu ersetzen, die den Speicher enger verwaltet. Wenn Sie beispielsweise die Backstack-Funktionen von Fragmenten verwenden und ein Fragment auf dem Backstack abgelegt wird und nicht mehr sichtbar ist, wird die Methode onDestroyView () aufgerufen, um die Ansichtshierarchie vollständig zu entfernen und den Platzbedarf erheblich zu verringern.
Nun, soweit Sie in dem Fluss abstürzen, in dem Sie zurückdrücken, zu einer Aktivität gehen, zurückdrücken, zu einer anderen Aktivität gehen usw. und niemals einen tiefen Stapel haben, dann haben Sie ja nur ein Leck. Dieser Blog-Beitrag beschreibt das Debuggen von Lecks: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
quelle
Activity
JavaDocs vorhanden sind.Einige Hinweise:
Stellen Sie sicher, dass Sie keinen Leckaktivitätskontext haben.
Stellen Sie sicher, dass Sie keine Referenzen auf Bitmaps behalten. Bereinigen Sie alle Ihre ImageViews in Aktivität # onStop, ungefähr so:
Recyceln Sie Bitmaps, wenn Sie sie nicht mehr benötigen.
Wenn Sie einen Speichercache wie memory-lru verwenden, stellen Sie sicher, dass nicht zu viel Speicher verwendet wird.
Nicht nur Bilder beanspruchen viel Speicher, stellen Sie auch sicher, dass Sie nicht zu viele andere Daten im Speicher behalten. Dies kann leicht passieren, wenn Ihre App unendlich viele Listen enthält. Versuchen Sie, Daten in der Datenbank zwischenzuspeichern.
Unter Android 4.2 gibt es einen Fehler (Stackoverflow # 13754876) mit Hardwarebeschleunigung. Wenn Sie ihn also
hardwareAccelerated=true
in Ihrem Manifest verwenden, geht Speicher verloren.GLES20DisplayList
- Halten Sie weiterhin Referenzen, auch wenn Sie Schritt (2) ausgeführt haben und niemand anderes auf diese Bitmap verweist. Hier brauchen Sie:a) Deaktivieren Sie die Hardwarebeschleunigung für API 16/17.
oder
b) die Ansicht entfernen, die die Bitmap enthält
Für Android 3+ können Sie versuchen,
android:largeHeap="true"
in Ihrem zu verwendenAndroidManifest
. Aber es wird Ihre Gedächtnisprobleme nicht lösen, sondern nur verschieben.Wenn Sie eine unendliche Navigation benötigen, sollten Fragmente Ihre Wahl sein. Sie haben also 1 Aktivität, die nur zwischen Fragmenten wechselt. Auf diese Weise lösen Sie auch einige Speicherprobleme wie Nummer 4.
Verwenden Sie den Memory Analyzer, um die Ursache Ihres Speicherverlusts herauszufinden.
Hier ist ein sehr gutes Video von Google I / O 2011: Speicherverwaltung für Android Apps
Wenn Sie mit Bitmaps arbeiten, sollte dies ein Muss sein: Effizientes Anzeigen von Bitmaps
quelle
outOfMemory
Fehlermeldung. Vielen Dank!Bitmaps sind häufig der Schuldige für Speicherfehler unter Android, daher ist dies ein guter Bereich, um dies zu überprüfen.
quelle
outOfMemory
Fehlermeldung. Vielen Dank!Halten Sie einige Verweise auf jede Aktivität? AFAIK Dies ist ein Grund, der Android davon abhält, Aktivitäten vom Stapel zu löschen.
Können Sie diesen Fehler auch auf anderen Geräten reproduzieren? Ich habe ein seltsames Verhalten einiger Android-Geräte je nach ROM- und / oder Hardwarehersteller festgestellt.
quelle
Ich denke, das Problem, vielleicht eine Kombination vieler Faktoren, die hier in den Antworten angegeben sind, gibt Ihnen Probleme. Wie @Tim sagte, kann ein (statischer) Verweis auf eine Aktivität oder ein Element in dieser Aktivität dazu führen, dass der GC die Aktivität überspringt. Hier ist der Artikel über diese Facette. Ich würde denken, dass das wahrscheinliche Problem von etwas herrührt, das die Aktivität in einem "sichtbaren Prozess" oder höher hält, was so ziemlich garantiert, dass die Aktivität und die damit verbundenen Ressourcen niemals zurückgefordert werden.
Ich habe vor einiger Zeit das gegenteilige Problem mit einem Service durchlaufen, und das hat mich zu diesem Gedanken gebracht: Es gibt etwas, das Ihre Aktivität auf der Prozessprioritätsliste hoch hält, damit sie nicht dem System-GC unterliegt, wie z eine Referenz (@Tim) oder eine Schleife (@Alvaro). Die Schleife muss kein endloses oder lang laufendes Element sein, sondern nur etwas, das einer rekursiven Methode oder einer kaskadierten Schleife (oder etwas in dieser Richtung) sehr ähnlich ist.
BEARBEITEN: Soweit ich weiß, werden onPause und onStop von Android automatisch nach Bedarf aufgerufen. Die Methoden sind hauptsächlich für Sie da, damit Sie sich darum kümmern können, was Sie tun müssen, bevor der Hosting-Prozess gestoppt wird (Speichern von Variablen, manuelles Speichern des Status usw.). Beachten Sie jedoch, dass eindeutig angegeben ist, dass onStop (zusammen mit onDestroy) möglicherweise nicht in jedem Fall aufgerufen wird. Wenn der Hosting-Prozess auch eine Aktivität, einen Dienst usw. mit dem Status "Forground" oder "Visible" hostet, versucht das Betriebssystem möglicherweise nicht einmal, den Prozess / Thread zu stoppen. Beispiel: Eine Aktivität und ein Service werden beide im selben Prozess ausgeführt und der Service kehrt
START_STICKY
von zurückonStartCommand()
Der Prozess nimmt automatisch mindestens einen sichtbaren Status an. Dies könnte hier der Schlüssel sein. Versuchen Sie, einen neuen Prozess für die Aktivität zu deklarieren, und prüfen Sie, ob sich dadurch etwas ändert. Versuchen Sie, diese Zeile zur Deklaration Ihrer Aktivität im Manifest als: hinzuzufügen,android:process=":proc2"
und führen Sie die Tests erneut aus, wenn Ihre Aktivität einen Prozess mit etwas anderem teilt. Der Gedanke hier ist, dass, wenn Sie Ihre Aktivität aufgeräumt haben und ziemlich sicher sind , dass das Problem nicht Ihre Aktivität ist, etwas anderes das Problem ist und es Zeit ist, danach zu suchen.Ich kann mich auch nicht erinnern, wo ich es gesehen habe (wenn ich es überhaupt in den Android-Dokumenten gesehen habe), aber ich erinnere mich an etwas über das
PendingIntent
Verweisen auf eine Aktivität, das dazu führen kann, dass sich eine Aktivität so verhält.Hier ist ein Link für die
onStartCommand()
Seite mit einigen Einblicken in den Prozess der Nicht-Tötung.quelle
Das einzige, woran ich wirklich denken kann, ist, wenn Sie eine statische Variable haben, die direkt oder indirekt auf den Kontext verweist. Sogar etwas als Verweis auf einen Teil der Anwendung. Ich bin sicher, Sie haben es bereits versucht, aber ich werde es nur für den Fall vorschlagen, versuchen Sie einfach, ALLE Ihre statischen Variablen in onDestroy () auf Null zu setzen, um sicherzustellen, dass der Garbage Collector es erhält
quelle
Die größte Quelle für Speicherverluste, die ich gefunden habe, wurde durch einen globalen, hochrangigen oder langjährigen Verweis auf den Kontext verursacht. Wenn Sie "Kontext" irgendwo in einer Variablen speichern, kann es zu unvorhersehbaren Speicherlecks kommen.
quelle
Versuchen Sie, getApplicationContext () an alles zu übergeben, was einen Kontext benötigt. Möglicherweise haben Sie eine globale Variable, die einen Verweis auf Ihre Aktivitäten enthält und verhindert, dass diese durch Müll gesammelt werden.
quelle
Eines der Dinge, die dem Speicherproblem in meinem Fall wirklich geholfen haben, war, dass inPurgeable für meine Bitmaps auf true gesetzt wurde. Siehe Warum sollte ich jemals die inPurgeable-Option von BitmapFactory NICHT verwenden? und die Diskussion der Antwort für weitere Informationen.
Die Antwort von Dianne Hackborn und unsere anschließende Diskussion (auch danke, CommonsWare) haben dazu beigetragen, bestimmte Dinge zu klären, über die ich verwirrt war. Vielen Dank dafür.
quelle
Ich habe das gleiche Problem mit Ihnen festgestellt. Ich habe an einer Instant Messaging-App gearbeitet. Für denselben Kontakt ist es möglich, eine ProfileActivity in einer ChatActivity zu starten und umgekehrt. Ich füge der Absicht, eine andere Aktivität zu starten, einfach eine zusätzliche Zeichenfolge hinzu, die die Informationen des Klassentyps der Starteraktivität und die Benutzer-ID enthält. Beispiel: ProfileActivity startet eine ChatActivity. In ChatActivity.onCreate markiere ich den Aufruferklassentyp 'ProfileActivity' und die Benutzer-ID. Wenn eine Aktivität gestartet werden soll, prüfe ich, ob es sich um eine 'ProfileActivity' für den Benutzer handelt oder nicht . Wenn ja, rufen Sie einfach 'finish ()' auf und kehren Sie zur vorherigen ProfileActivity zurück, anstatt eine neue zu erstellen. Speicherverlust ist eine andere Sache.
quelle