Ich habe eine Anwendung entwickelt, die viele Bilder auf Android verwendet.
Die App läuft einmal, füllt die Informationen auf dem Bildschirm ( Layouts
, Listviews
, Textviews
, ImageViews
usw.) und Benutzer liest die Informationen.
Es gibt keine Animation, keine Spezialeffekte oder irgendetwas, das den Speicher füllen kann. Manchmal können sich die Zeichen ändern. Einige sind Android-Ressourcen und andere sind Dateien, die in einem Ordner auf der SDCARD gespeichert sind.
Dann beendet der Benutzer (die onDestroy
Methode wird ausgeführt und die App bleibt von der VM im Speicher) und irgendwann tritt der Benutzer erneut ein.
Jedes Mal, wenn der Benutzer die App betritt, kann ich sehen, dass der Speicher immer größer wird, bis der Benutzer die App erhält java.lang.OutOfMemoryError
.
Was ist der beste / richtige Weg, um mit vielen Bildern umzugehen?
Sollte ich sie in statische Methoden einfügen, damit sie nicht ständig geladen werden? Muss ich das Layout oder die im Layout verwendeten Bilder auf besondere Weise bereinigen?
quelle
Antworten:
Es hört sich so an, als hätten Sie einen Speicherverlust. Das Problem besteht nicht darin, dass viele Bilder verarbeitet werden. Ihre Bilder werden nicht freigegeben, wenn Ihre Aktivität zerstört wird.
Es ist schwer zu sagen, warum dies so ist, ohne auf Ihren Code zu achten. Dieser Artikel enthält jedoch einige Tipps, die helfen könnten:
http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html
Insbesondere die Verwendung statischer Variablen kann die Situation eher verschlechtern als verbessern. Möglicherweise müssen Sie Code hinzufügen, der Rückrufe entfernt, wenn Ihre Anwendung neu gezeichnet wird. Auch hier gibt es nicht genügend Informationen, um dies sicher zu sagen.
quelle
Einer der häufigsten Fehler bei der Entwicklung von Android-Apps ist der Fehler "java.lang.OutOfMemoryError: Bitmap-Größe überschreitet VM-Budget". Ich habe diesen Fehler häufig bei Aktivitäten mit vielen Bitmaps festgestellt, nachdem ich die Ausrichtung geändert habe: Die Aktivität wird zerstört, erneut erstellt und die Layouts werden aus dem XML-Code "aufgeblasen", wodurch der für Bitmaps verfügbare VM-Speicher belegt wird.
Bitmaps im vorherigen Aktivitätslayout werden vom Garbage Collector nicht ordnungsgemäß freigegeben, da sie Verweise auf ihre Aktivität gekreuzt haben. Nach vielen Experimenten habe ich eine recht gute Lösung für dieses Problem gefunden.
Legen Sie zunächst das Attribut "id" in der übergeordneten Ansicht Ihres XML-Layouts fest:
onDestroy()
Rufen Sie dann in der Methode Ihrer Aktivität dieunbindDrawables()
Methode auf, die einen Verweis auf die übergeordnete Ansicht übergibt, und führen Sie dann a ausSystem.gc()
.Diese
unbindDrawables()
Methode untersucht den Ansichtsbaum rekursiv und:quelle
Um dieses Problem zu vermeiden, können Sie die native Methode verwenden,
Bitmap.recycle()
bevor Sie ein Objektnull
erstellenBitmap
(oder einen anderen Wert festlegen). Beispiel:Und als nächstes können Sie ohne
myBitmap
Anruf ändernSystem.gc()
wie:quelle
Ich bin genau auf dieses Problem gestoßen. Der Haufen ist ziemlich klein, so dass diese Bilder in Bezug auf den Speicher ziemlich schnell außer Kontrolle geraten können. Eine Möglichkeit besteht darin, dem Garbage Collector einen Hinweis zum Sammeln von Speicher auf einer Bitmap zu geben, indem die Recyclingmethode aufgerufen wird .
Es wird auch nicht garantiert, dass die onDestroy-Methode aufgerufen wird. Möglicherweise möchten Sie diese Logik / Bereinigung in die Aktivität onPause verschieben. Weitere Informationen finden Sie im Diagramm / in der Tabelle zum Aktivitätslebenszyklus auf dieser Seite .
quelle
onPause
Vorschlag. Ich verwende 4 Registerkarten mitBitmap
Bildern in allen, sodass die Zerstörung der Aktivität möglicherweise nicht zu früh erfolgt (wenn der Benutzer alle Registerkarten durchsucht).Diese Erklärung könnte hilfreich sein: http://code.google.com/p/android/issues/detail?id=8488#c80
"Schnelle Tipps:
1) Rufen Sie NIEMALS System.gc () selbst auf. Dies wurde hier als Fix propagiert und funktioniert nicht. TU es nicht. Wenn Sie in meiner Erklärung bemerkt haben, dass die JVM vor dem Abrufen eines OutOfMemoryError bereits eine Garbage Collection ausführt, gibt es keinen Grund, eine weitere durchzuführen (dies verlangsamt Ihr Programm). Wenn Sie am Ende Ihrer Aktivität eine machen, wird das Problem nur vertuscht. Es kann dazu führen, dass die Bitmap schneller in die Finalizer-Warteschlange gestellt wird, aber es gibt keinen Grund, warum Sie nicht stattdessen einfach auf jeder Bitmap recyceln können.
2) Rufen Sie bei Bitmaps, die Sie nicht mehr benötigen, immer recycle () auf. Zumindest gehen Sie im onDestroy Ihrer Aktivität durch und recyceln Sie alle von Ihnen verwendeten Bitmaps. Wenn Sie möchten, dass die Bitmap-Instanzen schneller vom Dalvik-Heap erfasst werden, schadet es auch nicht, Verweise auf die Bitmap zu löschen.
3) Wenn Sie recycle () und dann System.gc () aufrufen, wird die Bitmap möglicherweise immer noch nicht vom Dalvik-Heap entfernt. Seien Sie nicht besorgt darüber. recycle () hat seine Arbeit erledigt und den nativen Speicher freigegeben. Es wird nur einige Zeit dauern, bis die zuvor beschriebenen Schritte ausgeführt sind, um die Bitmap tatsächlich vom Dalvik-Heap zu entfernen. Dies ist keine große Sache, da der große Teil des nativen Speichers bereits frei ist!
4) Nehmen Sie immer an, dass zuletzt ein Fehler im Framework vorliegt. Dalvik macht genau das, was er tun soll. Es ist vielleicht nicht das, was Sie erwarten oder was Sie wollen, aber es funktioniert so. ""
quelle
Ich hatte genau das gleiche Problem. Nach einigen Tests stellte ich fest, dass dieser Fehler bei der Skalierung großer Bilder auftritt. Ich habe die Bildskalierung reduziert und das Problem ist verschwunden.
PS Zuerst habe ich versucht, die Bildgröße zu reduzieren, ohne das Bild zu verkleinern. Das hat den Fehler nicht gestoppt.
quelle
Die folgenden Punkte haben mir sehr geholfen. Es mag auch andere Punkte geben, aber diese sind sehr wichtig:
quelle
Ich schlage einen bequemen Weg vor, um dieses Problem zu lösen. Weisen Sie einfach das Attribut "android: configChanges" wie in der Mainfest.xml für Ihre fehlerhafte Aktivität angegeben zu. so was:
Die erste Lösung, die ich herausgab, hatte die Häufigkeit von OOM-Fehlern wirklich auf ein niedriges Niveau reduziert. Das Problem wurde jedoch nicht vollständig gelöst. Und dann werde ich die 2. Lösung herausgeben:
Wie im OOM detailliert beschrieben, habe ich zu viel Laufzeitspeicher verwendet. Also reduziere ich die Bildgröße in ~ / res / drawable meines Projekts. Ein überqualifiziertes Bild mit einer Auflösung von 128 x 128 könnte beispielsweise auf 64 x 64 verkleinert werden, was auch für meine Anwendung geeignet wäre. Und nachdem ich dies mit einem Stapel Bilder getan habe, tritt der OOM-Fehler nicht mehr auf.
quelle
Auch ich bin frustriert über den Outofmemory-Bug. Und ja, ich habe auch festgestellt, dass dieser Fehler beim Skalieren von Bildern häufig auftritt. Zuerst habe ich versucht, Bildgrößen für alle Dichten zu erstellen, aber ich habe festgestellt, dass dies die Größe meiner App erheblich vergrößert. Ich verwende jetzt nur ein Bild für alle Dichten und skaliere meine Bilder.
Meine Anwendung würde einen Outofmemory-Fehler auslösen, wenn der Benutzer von einer Aktivität zur nächsten wechselt. Das Setzen meiner Drawables auf null und das Aufrufen von System.gc () funktionierte nicht, ebenso wenig wie das Recycling meiner BitmapDrawables mit getBitMap (). Recycle (). Android würde beim ersten Ansatz weiterhin den Outofmemory-Fehler auslösen und bei jedem Versuch, eine recycelte Bitmap mit dem zweiten Ansatz zu verwenden, eine Canvas-Fehlermeldung auslösen.
Ich habe einen noch dritten Ansatz gewählt. Ich habe alle Ansichten auf null und den Hintergrund auf schwarz gesetzt. Ich mache diese Bereinigung in meiner onStop () -Methode. Dies ist die Methode, die aufgerufen wird, sobald die Aktivität nicht mehr sichtbar ist. Wenn Sie dies in der onPause () -Methode tun, sehen Benutzer einen schwarzen Hintergrund. Nicht ideal. Für die Methode onDestroy () gibt es keine Garantie, dass sie aufgerufen wird.
Um zu verhindern, dass ein schwarzer Bildschirm auftritt, wenn der Benutzer die Zurück-Taste auf dem Gerät drückt, lade ich die Aktivität in der onRestart () -Methode neu, indem ich die Methoden startActivity (getIntent ()) und finish () aufrufe.
Hinweis: Es ist nicht unbedingt erforderlich, den Hintergrund auf Schwarz zu ändern.
quelle
Die BitmapFactory.decode * -Methoden, die in der Lektion Effizientes Laden großer Bitmaps beschrieben werden , sollten nicht auf dem Haupt-UI-Thread ausgeführt werden, wenn die Quelldaten von der Festplatte oder einem Netzwerkspeicherort (oder einer anderen Quelle als dem Speicher) gelesen werden. Die Zeit, die diese Daten zum Laden benötigen, ist unvorhersehbar und hängt von einer Vielzahl von Faktoren ab (Lesegeschwindigkeit von der Festplatte oder vom Netzwerk, Größe des Images, Leistung der CPU usw.). Wenn eine dieser Aufgaben den UI-Thread blockiert, kennzeichnet das System Ihre Anwendung als nicht reagierend und der Benutzer hat die Möglichkeit, sie zu schließen (weitere Informationen finden Sie unter Entwerfen für Reaktionsfähigkeit).
quelle
Nun, ich habe alles versucht, was ich im Internet gefunden habe, und keiner von ihnen hat funktioniert. Das Aufrufen von System.gc () verringert nur die Geschwindigkeit der App. Das Recycling von Bitmaps in onDestroy hat auch bei mir nicht funktioniert.
Das einzige, was jetzt funktioniert, ist eine statische Liste aller Bitmaps, damit die Bitmaps nach einem Neustart überleben. Verwenden Sie einfach die gespeicherten Bitmaps, anstatt bei jedem Neustart der Aktivität neue zu erstellen.
In meinem Fall sieht der Code folgendermaßen aus:
quelle
Ich hatte das gleiche Problem nur beim Wechseln der Hintergrundbilder mit angemessenen Größen. Ich habe bessere Ergebnisse erzielt, wenn ich ImageView auf null gesetzt habe, bevor ich ein neues Bild eingefügt habe.
quelle
FWIW, hier ist ein leichter Bitmap-Cache, den ich codiert und seit einigen Monaten verwendet habe. Es ist nicht alles Schnickschnack, lesen Sie also den Code, bevor Sie ihn verwenden.
quelle