Android App aus Speicherproblemen - alles ausprobiert und immer noch ratlos

87

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.

Artem Russakovskii
quelle
Wenn ich also in den letzten 20 Sekunden 15 Aktivitäten geöffnet habe (der Benutzer geht sehr schnell durch), könnte das das Problem sein? Welche Codezeile sollte ich hinzufügen, um die Aktivität zu löschen, nachdem sie angezeigt wurde? Ich bekomme eine outOfMemoryFehlermeldung. Vielen Dank!
Ruchir Baronia

Antworten:

109

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.

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

Hackbod
quelle
47
Zur Verteidigung des OP ist dies nicht das, was die Dokumentation vorschlägt. Zitieren von developer.android.com/guide/topics/fundamentals/… "(Wenn eine Aktivität gestoppt wird), ist sie für den Benutzer nicht mehr sichtbar und kann vom System beendet werden, wenn an anderer Stelle Speicher benötigt wird." "Wenn eine Aktivität angehalten oder gestoppt wird, kann das System sie aus dem Speicher löschen ... indem es sie zum Beenden auffordert (Aufruf der Methode finish ())" "(onDestroy () wird aufgerufen), weil das System diese Instanz von vorübergehend zerstört die Aktivität, um Platz zu sparen. "
CommonsWare
42
Auf derselben Seite: "Wenn das System jedoch eine Aktivität zerstört, um Speicher wiederherzustellen". Sie geben an, dass Android niemals Aktivitäten zerstört, um Speicher zurückzugewinnen, sondern nur den Vorgang dazu beendet. In diesem Fall muss diese Seite ernsthaft neu geschrieben werden, da wiederholt darauf hingewiesen wird , dass Android eine Aktivität zerstört, um Speicher zurückzugewinnen. Beachten Sie auch, dass viele der zitierten Passagen auch in den ActivityJavaDocs vorhanden sind.
CommonsWare
6
Ich dachte, ich hätte dies hier eingestellt, aber ich habe eine Anfrage zur Aktualisierung der Dokumentation dafür veröffentlicht: code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller
15
Es ist 2013 und die Dokumente haben den falschen Punkt nur deutlicher dargestellt: developer.android.com/training/basics/activity-lifecycle/… , "Sobald Ihre Aktivität gestoppt ist, kann das System die Instanz zerstören, wenn sie wiederhergestellt werden muss Systemspeicher. In EXTREMEN Fällen kann das System einfach Ihren App-Prozess
beenden
2
Na Mist. Ich habe definitiv immer gedacht (aufgrund der Dokumente), dass das System die gestoppten Aktivitäten in Ihrem Prozess verwaltet und sie nach Bedarf serialisiert und deserialisiert (zerstört und erstellt).
dcow
21

Einige Hinweise:

  1. Stellen Sie sicher, dass Sie keinen Leckaktivitätskontext haben.

  2. Stellen Sie sicher, dass Sie keine Referenzen auf Bitmaps behalten. Bereinigen Sie alle Ihre ImageViews in Aktivität # onStop, ungefähr so:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
  3. Recyceln Sie Bitmaps, wenn Sie sie nicht mehr benötigen.

  4. Wenn Sie einen Speichercache wie memory-lru verwenden, stellen Sie sicher, dass nicht zu viel Speicher verwendet wird.

  5. 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.

  6. Unter Android 4.2 gibt es einen Fehler (Stackoverflow # 13754876) mit Hardwarebeschleunigung. Wenn Sie ihn also hardwareAccelerated=truein 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

  7. Für Android 3+ können Sie versuchen, android:largeHeap="true"in Ihrem zu verwenden AndroidManifest. Aber es wird Ihre Gedächtnisprobleme nicht lösen, sondern nur verschieben.

  8. 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.

  9. 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

vovkab
quelle
Wenn ich also in den letzten 20 Sekunden 15 Aktivitäten geöffnet habe (der Benutzer geht sehr schnell durch), könnte das das Problem sein? Welche Codezeile sollte ich hinzufügen, um die Aktivität zu löschen, nachdem sie angezeigt wurde? Ich bekomme eine outOfMemoryFehlermeldung. Vielen Dank!
Ruchir Baronia
4

Bitmaps sind häufig der Schuldige für Speicherfehler unter Android, daher ist dies ein guter Bereich, um dies zu überprüfen.

Ed Burnette
quelle
Wenn ich also in den letzten 20 Sekunden 15 Aktivitäten geöffnet habe (der Benutzer geht sehr schnell durch), könnte das das Problem sein? Welche Codezeile sollte ich hinzufügen, um die Aktivität zu löschen, nachdem sie angezeigt wurde? Ich bekomme eine outOfMemoryFehlermeldung. Vielen Dank!
Ruchir Baronia
2

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.

NewProggie
quelle
Ich konnte dies auf einem Droid mit CM7 reproduzieren, wobei der maximale Heap auf 16 MB eingestellt war. Dies ist der gleiche Wert, den ich auf dem Emulator teste.
Artem Russakovskii
Sie können auf etwas sein. Wenn die zweite Aktivität beginnt, wird die erste onPause-> onStop oder nur onPause ausführen? Weil ich alles auf ******* Lebenszyklusaufrufen drucke und onPause -> onCreate ohne onStop sehe. Und einer der Crash-Dumps sagte tatsächlich etwas wie onPause = true oder onStop = false für etwa 3 Aktivitäten, die er tötete.
Artem Russakovskii
OnStop sollte aufgerufen werden, wenn die Aktivität den Bildschirm verlässt, jedoch möglicherweise nicht, wenn sie vom System vorzeitig zurückgefordert wird.
Uhr
Es wird nicht zurückgefordert, da onCreate und onRestoreInstanceState nicht aufgerufen werden, wenn ich zurückklicke.
Artem
Je nach Lebenszyklus werden diese möglicherweise nie aufgerufen. Überprüfen Sie meine Antwort, die einen Link zum offiziellen Entwickler-Blog enthält. Es ist mehr als wahrscheinlich, wie Sie Ihre Bitmaps weitergeben
Uhr
2

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_STICKYvon 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 PendingIntentVerweisen 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.

Kingsolmn
quelle
Basierend auf dem Link, den Sie bereitgestellt haben (danke), kann eine Aktivität beendet werden, wenn ihr onStop aufgerufen wurde. In meinem Fall verhindert jedoch etwas, dass onStop ausgeführt wird. Ich werde auf jeden Fall untersuchen, warum. Es heißt jedoch auch, dass Aktivitäten mit nur onPause ebenfalls beendet werden können, was in meinem Fall nicht der Fall ist (ich sehe, dass onPause aufgerufen wird, aber nicht onStop).
Artem Russakovskii
1

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

Tim Fenton - VenumX
quelle
1

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.

Dirk Conrad Coetsee
quelle
Ja, ich habe das oft gehört und gelesen, aber ich hatte Probleme, es festzuhalten. Wenn ich nur zuverlässig herausfinden könnte, wie ich diese verfolgen kann.
Artem Russakovskii
1
In meinem Fall wurde dies in eine beliebige Variable auf Klassenebene übersetzt, die gleich dem Kontext gesetzt wurde (z. B. Class1.variable = getContext ();). Im Allgemeinen löste das Ersetzen jeder Verwendung von "Kontext" in meiner App durch einen neuen Aufruf von "getContext" oder ähnlichem meine größten Speicherprobleme. Aber meine waren instabil und unberechenbar, nicht vorhersehbar wie in Ihrem Fall, also möglicherweise etwas anderes.
Dirk Conrad Coetsee
1

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.

vee
quelle
1

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.

Artem Russakovskii
quelle
Ich weiß, dass dies ein altes Thema ist, aber ich sehe, dass dieser Thread bei vielen Suchanfragen zu finden scheint. Ich möchte ein großartiges Tutorial verlinken, von dem ich so viel gelernt habe, dass Sie es hier finden können: developer.android.com/training/displaying-bitmaps/index.html
Codeversed
0

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.

qiuping345
quelle