Ab und zu OutOfMemoryError beim erneuten Starten

L

Lucid85

Neues Mitglied
0
hallo,

ich habe ein android spiel programmiert und es läuft auch soweit ganz gut. nun habe ich aber das problem, dass ich manchmal beim erneuten starten des spiels einen OutOfMemoryError einfange und das spiel beendet wird. ich habe schon folgende vorkehrungen getroffen:

1. in meinem manifest file habe ich in der activity

android:launchMode= "singleTask"

ergänzt.

2. in der methode public void surfaceDestroyed(SurfaceHolder holder) meines SurfaceView (mit Surface.Callback-interface) habe ich mittels recycle() alle bitmaps wieder freigegeben.

kann mir bitte jemand noch tipps geben, was ich noch versuchen kann?

hier nochmal der error stack:

04-13 07:28:24.227: E/AndroidRuntime(1804): FATAL EXCEPTION: main
04-13 07:28:24.227: E/AndroidRuntime(1804): Process: de.lucid.mygame, PID: 1804
04-13 07:28:24.227: E/AndroidRuntime(1804): java.lang.OutOfMemoryError
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:575)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:410)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:433)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:463)
04-13 07:28:24.227: E/AndroidRuntime(1804): at de.lucid.mygame.views.PlayView$GameThread.<init>(PlayView.java:96)
04-13 07:28:24.227: E/AndroidRuntime(1804): at de.lucid.mygame.views.PlayView.restart(PlayView.java:346)
04-13 07:28:24.227: E/AndroidRuntime(1804): at de.lucid.mygame.activities.PlayActivity.onStart(PlayActivity.java:61)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1171)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.Activity.performStart(Activity.java:5253)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.Activity.performRestart(Activity.java:5309)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.Activity.performResume(Activity.java:5314)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2759)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2798)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1439)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.os.Handler.dispatchMessage(Handler.java:102)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.os.Looper.loop(Looper.java:137)
04-13 07:28:24.227: E/AndroidRuntime(1804): at android.app.ActivityThread.main(ActivityThread.java:4998)
04-13 07:28:24.227: E/AndroidRuntime(1804): at java.lang.reflect.Method.invokeNative(Native Method)
04-13 07:28:24.227: E/AndroidRuntime(1804): at java.lang.reflect.Method.invoke(Method.java:515)
04-13 07:28:24.227: E/AndroidRuntime(1804): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
04-13 07:28:24.227: E/AndroidRuntime(1804): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
04-13 07:28:24.227: E/AndroidRuntime(1804): at dalvik.system.NativeStart.main(Native Method)
 
Zuletzt bearbeitet:
Wenn die VM voll ist, ist sie voll. Du kannst nicht unendliche viele Bilder in eine VM laden, und die dann wundern, das die App abstürzt.

Du muss die Referenzen auf die Bilder auch wieder trennen, wenn du sich nicht mehr brauchst. ;)
 
hast du meinen beitrag richtig gelesen? ich recycle ja jedes bitmap beim beenden wieder. oder würde so was wie bitmap = null da noch was helfen? außerdem kommt der fehler ja nicht regelmäßig; mal kommt er beim zweiten neustart, dann wiederum zehnmal gar nicht... ?
 
Du hast ein Speicherleck. Das unregelmäßige Auftreten des Fehlers spricht dafür. Für dieses Problem gibt es keine Patentlösung, sauberes Programmieren wäre schon mal eine Voraussetzung.

Meist hilft schon, wenn man den Garbage Collector manuell auslöst, bevor man das nächste Bilder lädt (System.gc()). Auch die Bilder null zu setzen hilft manchmal.

Eine mögliche Lösung ist es auch, die Rotation auszuschalten.


Hier ein paar Links zu den Thema:

https://developer.android.com/tools/debugging/debugging-memory.html

Avoiding memory leaks | Android Developers Blog

https://developer.android.com/training/displaying-bitmaps/manage-memory.html


--------

Nachtrag:

Wenn du den Homscreen aufrufst, wird die App nicht beendet, sondern nur in der Hintergrund verfrachtet (Es wird die Methode onPause() aufgerufen). Beim Neustart geht es auch nicht onCreate(), sondern mit onResume() los. Das heißt, der Screen wird nicht jungfräulich neu aufgebaut. Und alte Bilder (Objekte) werden mit geschleppt. Das ist normalerweise kein Problem, aber Bilder sind relativ groß, und sprengen schnell mal den Speichergrenzen der VM.
 
Zuletzt bearbeitet:
welche rotation meinst du jetzt? die vom gerät? die hab ich schon ausgeschaltet... also lasse das spiel nur im "landscape" modus laufen. meinst du das?
 
Ja.

Aber schaue dir mal die Links an. Dort ist es aus ausführlicher erklärt. Sonst google einfach nach Memory Leak + Bitmap + Android. Du bist nicht der erste mit diesem Problem.
 
Das kommt wohl ein bisschen darauf an, wie du das Bild in deinem Code speicherst.

Beispiel.
Du packst alle bilder in eine Liste
In onStart() machst du sowas wie.

Liste.add(new Bitmap())
Liste.add(new Bitmap())...
etc..

selbst wenn du auf jedes der Bilder in der Liste recycle() anwendest werden die nicht von der GC weggeräumt solange noch die Referenz dazu in der Liste ist.

Bei dem Beispiel würdest du bei jedem Starten vom Game wieder onStart() aufrufen es würden neue Bilder angelegt aber die alten sind immer noch vorhanden.

Erst wenn du die alten Bilder in der Liste auf null setzt kann der Garbage Collector die einsammeln.

Laut Doku brauchst du recycle() normalerweise gar nicht.

public void recycle ()
Free the native object associated with this bitmap, and clear the reference to the pixel data. This will not free the pixel data synchronously; it simply allows it to be garbage collected if there are no other references. The bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing. This operation cannot be reversed, so it should only be called if you are sure there are no further uses for the bitmap. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.

Der letzte Satz ist interessant, da wir deinen Code nicht kenn, können wir nur raten wo genau du den Fehler machst.

Du kannst dir auch mal den Quellcode angucken:
http://grepcode.com/file/repository....4.2_r1/android/graphics/Bitmap.java?av=f#304
Da siehst du das der Java Befehl eigentlich nicht viel macht, ausser das recyclen an einen native Code weiterzugeben.
 
Zuletzt bearbeitet:
...aber der GC löscht doch objekte, auf welche nicht mehr referenziert wird.

das heißt doch eigentlich, dass wenn ich beim erneuten starten der app z.b.: backgroundImg = BitmapFactory.decodeResource(context.getResources(), R.drawable.background) erneut ausführe, würde doch auf das objekt, auf welches die variable backgroundImg voher gezeigt hat, nicht mehr referenziert werden, und somit vom gc weggeräumt werden - oder nicht?


davon abgesehen - müsste der error dann nicht regelmäßiger auftreten, wenns daran liegen würde?
 
Ja der GC müsste das alte Objekt wegräumen.

Aber nicht sofort.
Der GC läuft ja unregelmäßig.

Sagen wir du lädst Bilder und machst genau 50% Speicher damit voll.
du hast dann z.B.
image1
image2
image3
image4
brauchen jeweils 12,5% Speicher.

Bei erneuten Starten sind die alle noch vorhanden. und du lädst in alle 4 neue Bilder.
Da das relativ schnell geht sind die alten Objekte aber nicht nicht weggeräumt.

Somit hast du dann plötzlich 100% Speicher verbraucht.
Wenn jetzt irgendwas noch speicher braucht sei es auch nur ein int und der GC ist noch nicht gelaufen bekommst du den OutOfMemory.

Es könnte helfen wenn du beim beenden der Apps die images alle auf null setzt.
Aber eine Garantie gibt es dafür nicht.

Und unregelmäßig tritt der Fehler vermutlich deshalb auf, weil manchmal die GC schnell genug war und manchmal nicht.
 
thanx erstmal...

muss ich eigentlich in meiner activity onRestart() überschreiben oder reicht es wenn ich onStart() überschreibe. bei einem neustart der app werden irgendwie beide ausgeführt...?
 
onResume() ist die richtige Variante. Aber ohne Code sind das hier alles nur Vermutungen.

Normalerweise passt sich die VM dynamisch an den Speicherbedarf an. Sie hat ein Obergrenze, ohne OBB sind das, glaube ich, zwischen 40 und 50 MB.

Wenn man zu schnell zu viele Bilder oder zu große Bilder lädt, sprengt man die VM.
Ober deine Bilder haben mehrere Referenzen, die du aber nicht alle löschst. Wenn man es geschickt macht, kann man dabei sogar Inseln erschaffen, die völlig ohne Referenzen zum Hauptprogramm existieren.

Die erste Variante wäre den Code abzuklappern, und alle unsaubere Konstrukte entfernen.
Wenn nichts mehr geht, könntest du die ViewGroup deines Root View durchinternieren, und alle Views null setzen.


@CoffeeCode
WeakReference sind nur eine sinnvolle Variante, wenn man innere Klassen hast, die man aus den Speicher entfernen möchte. Sonst sind sie eher Gift für den Garabage Collector. Und widersprechen den Konzept von Java.
 
ja onResume() geht auch... aber wofür sind dann die anderen da? :)
 
kannst du mir die näher erläutern? sind onStart() und onRestart() nicht generell überflüssig, wenn onResume() ja offensichtlich ausreicht, wie bei mir? wofür sollte ich dann noch die anderen überschreiben? steige da noch nicht ganz durch :/
 
Kann man so pauschal nicht beantworten.
Kommt halt drauf an was du in deiner App machst.
Es gibt bestimmt Apps die beim restart anders reagieren müssen als beim start.

Ausserdem wird beispielsweise onStart() nicht aufgerufen, wenn sich z.b. ein Dialog Fenster öffnet, dann gehts nur nach onPause() und onResume().

Wenn deine App aber ganz im Hintergrund verschwindet, dann gehts über onPause() -> onStop() und über onRestart() wieder nach onStart()

Aber das ist alles wunderbar in dem Link erklärt. Eine bessere Doku findest du nicht.
 
Zwischen onResume() und onPause() ist alles sichtbar. Du siehst die Änderungen. Alles andere ist unsichtbar.

Die meisten Sachen davon braucht mal normalerweise nicht. Man sollte aber trotzdem wissen, was passiert, wenn die App vom z.B. ein Telefonanruf unterbrochen wird.
 

Ähnliche Themen

Laser5001
  • Laser5001
Antworten
2
Aufrufe
908
Laser5001
Laser5001
M
  • MikelKatzengreis
Antworten
5
Aufrufe
166
swa00
swa00
S
Antworten
8
Aufrufe
521
swa00
swa00
Zurück
Oben Unten