1389 Stimmen

Seltsames OutOfMemory-Problem beim Laden eines Bildes in ein Bitmap-Objekt

Ich habe eine ListView mit ein paar Bildschaltflächen in jeder Zeile. Wenn der Benutzer auf die Listenzeile klickt, wird eine neue Aktivität gestartet. Aufgrund eines Problems mit dem Kamera-Layout musste ich meine eigenen Registerkarten erstellen. Die Aktivität, die für das Ergebnis gestartet wird, ist eine Karte. Wenn ich auf meine Schaltfläche klicke, um die Bildvorschau zu starten (Laden eines Bildes von der SD-Karte), kehrt die Anwendung von der Aktivität zurück zur ListView Aktivität zum Ergebnis-Handler, um meine neue Aktivität, die nichts anderes als ein Bild-Widget ist, neu zu starten.

Die Bildvorschau auf der Seite ListView wird mit dem Cursor durchgeführt und ListAdapter . Dies macht es ziemlich einfach, aber ich bin mir nicht sicher, wie ich ein verkleinertes Bild (d.h. kleinere Bitgröße nicht Pixel als die src für die Bild-Schaltfläche im laufenden Betrieb. Also habe ich einfach die Größe des Bildes, das von der Handykamera kam, geändert.

Das Problem ist, dass ich eine OutOfMemoryError wenn er versucht, zurückzugehen und die 2. Aktivität erneut zu starten.

  • Gibt es eine Möglichkeit, den Listenadapter einfach zeilenweise zu erstellen, wobei ich die Größe während des Betriebs ändern kann ( bitweise )?

Dies wäre wünschenswert, da ich auch einige Änderungen an den Eigenschaften der Widgets/Elemente in jeder Zeile vornehmen muss, da ich aufgrund des Fokusproblems nicht in der Lage bin, eine Zeile mit dem Touchscreen auszuwählen. ( Ich kann Rollerball verwenden. )

  • Ich weiß, dass ich die Größe außerhalb des Bandes ändern und mein Bild speichern kann, aber das ist nicht wirklich das, was ich tun möchte, aber ein Beispielcode dafür wäre schön.

Sobald ich das Bild auf dem Bildschirm deaktiviert habe ListView hat es wieder gut funktioniert.

Zu Ihrer Information: So habe ich es gemacht:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
    DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
    DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
    R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

どこ R.id.imagefilename es un ButtonImage .

Hier ist mein LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

Ich habe auch einen neuen Fehler beim Anzeigen eines Bildes:

22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

9 Stimmen

Ich löste dies, indem ich Bitmap.decodeStream oder decodeFile vermied und die Methode BitmapFactory.decodeFileDescriptor verwendete.

2 Stimmen

Ich auch konfrontiert ähnliches Problem paar Wochen zurück und ich löste es durch Skalierung Bilder bis zu optimalen Punkt. Ich habe komplette Ansatz in meinem Blog geschrieben codingjunkiesforum.wordpress.com/2014/06/12/ und lud ein komplettes Beispielprojekt mit OOM-anfälligem Code im Vergleich zu OOM-Proof-Code aufhttps://github.com/shailendra123/BitmapHandlingDemo hoch.

2 Stimmen

Vollständige Lösung .. stackoverflow.com/a/24135283/294884

83voto

Fraggle Punkte 8397

Ich hatte das gleiche Problem und löste es, indem ich die Funktionen BitmapFactory.decodeStream oder decodeFile vermied und stattdessen BitmapFactory.decodeFileDescriptor

decodeFileDescriptor sieht so aus, als ob es andere native Methoden als decodeStream/decodeFile aufruft.

Wie auch immer, was funktionierte, war dies (beachten Sie, dass ich einige Optionen hinzugefügt, wie einige oben hatte, aber das ist nicht, was den Unterschied gemacht. Entscheidend ist der Aufruf von BitmapFactory.decodeFileDescriptor anstelle von decodeStream o decodeFile ):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

Ich glaube, es gibt ein Problem mit der in decodeStream/decodeFile verwendeten nativen Funktion. Ich habe bestätigt, dass eine andere native Methode aufgerufen wird, wenn decodeFileDescriptor verwendet wird. Außerdem habe ich gelesen, "dass Bilder (Bitmaps) nicht auf eine Standard-Java-Art zugewiesen werden, sondern über native Aufrufe; die Zuweisungen erfolgen außerhalb des virtuellen Heaps, sind aber gegen sie gezählt! "

2 Stimmen

Dasselbe Ergebnis aus dem Speicher, eigentlich ist es egal, welche Methode Sie verwenden, es hängt von der Anzahl der Bytes, die Sie halten, um die Daten zu lesen, die aus dem Speicher gibt.

75voto

coocood Punkte 1356

Ich denke, der beste Weg zur Vermeidung der OutOfMemoryError ist, sich ihr zu stellen und sie zu verstehen.

Ich habe eine app absichtlich verursachen OutOfMemoryError und überwachen die Speichernutzung.

Nachdem ich viele Experimente mit dieser App gemacht habe, bin ich zu folgenden Ergebnissen gekommen:

Ich werde zuerst über SDK-Versionen vor Honey Comb sprechen.

  1. Bitmap wird im nativen Heap gespeichert, aber es wird automatisch garbage collected, der Aufruf von recycle() ist unnötig.

  2. Wenn {VM Heap-Größe} + {zugeordneter nativer Heap-Speicher} >= {VM Heap-Größenlimit für das Gerät}, und Sie versuchen, eine Bitmap zu erstellen, wird ein OOM ausgelöst.

    HINWEIS: VM HEAP SIZE wird anstelle von VM ALLOCATED MEMORY gezählt.

  3. Die Größe des VM-Heap wird nach dem Erweitern nicht mehr verkleinert, selbst wenn der zugewiesene VM-Speicher verkleinert wird.

  4. Sie müssen also den VM-Spitzenspeicher so niedrig wie möglich halten, damit die Heap-Größe der VM nicht zu groß wird, um verfügbaren Speicher für Bitmaps zu sparen.

  5. Ein manueller Aufruf von System.gc() ist sinnlos, da das System diese Funktion zuerst aufruft, bevor es versucht, den Heap zu vergrößern.

  6. Die native Heap-Größe wird auch nie schrumpfen, aber sie wird nicht für OOM gezählt, so dass man sich darüber keine Sorgen machen muss.

Dann lassen Sie uns über SDK Starts von Honey Comb sprechen.

  1. Bitmap wird im VM-Heap gespeichert, Native Memory wird für OOM nicht berücksichtigt.

  2. Die Bedingung für OOM ist viel einfacher: {VM heap size} >= {VM Heap-Größenbegrenzung für das Gerät}.

  3. So haben Sie mehr verfügbaren Speicher zum Erstellen von Bitmaps mit der gleichen Heap-Größenbeschränkung, OOM ist weniger wahrscheinlich, geworfen werden.

Hier sind einige meiner Beobachtungen über Garbage Collection und Memory Leak.

Sie können es selbst in der App sehen. Wenn eine Activity eine AsyncTask ausführt, die noch läuft, nachdem die Activity zerstört wurde, wird die Activity erst dann Garbage Collected, wenn die AsyncTask beendet ist.

Das liegt daran, dass AsyncTask eine Instanz einer anonymen inneren Klasse ist, die einen Verweis auf die Activity enthält.

Durch den Aufruf von AsyncTask.cancel(true) wird die Ausführung nicht gestoppt, wenn die Aufgabe durch eine IO-Operation im Hintergrund-Thread blockiert ist.

Rückrufe sind auch anonyme innere Klassen, wenn also eine statische Instanz in Ihrem Projekt sie hält und nicht freigibt, würde Speicher verloren gehen.

Wenn Sie eine sich wiederholende oder verzögerte Aufgabe planen, z. B. einen Timer, und Sie cancel() und purge() nicht in onPause() aufrufen, würde Speicher verloren gehen.

46voto

Chrispix Punkte 17291

Ich habe die folgenden Schritte durchgeführt, um das Bild zu nehmen und die Größe spontan zu ändern. Hoffentlich hilft das

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);

27 Stimmen

Auf diese Weise wird die Bitmap skaliert. Aber es löst das OutOfMemory-Problem nicht, da die gesamte Bitmap ohnehin dekodiert wird.

6 Stimmen

Ich werde sehen, ob ich mir meinen alten Code ansehen kann, aber ich glaube, das hat meine Probleme mit dem fehlenden Speicher gelöst. Werde meinen alten Code noch einmal überprüfen.

3 Stimmen

Zumindest in diesem Beispiel sieht es so aus, als würden Sie den Verweis auf die vollständige Bitmap nicht behalten, wodurch Sie Speicherplatz sparen.

39voto

Himanshu Mori Punkte 823

leider Wenn keiner der oben genannten Punkte funktioniert, dann fügen Sie dies zu Ihrem Manifest Datei. Innerhalb von Anmeldung Tag

 <application
         android:largeHeap="true"

2 Stimmen

Können Sie erklären, was das eigentlich bedeutet? Den Leuten einfach zu sagen, dass sie dies hinzufügen sollen, hilft nicht weiter.

4 Stimmen

Dies ist eine sehr schlechte Lösung. Im Grunde versuchen Sie nicht, das Problem zu beheben. Stattdessen bitten Sie das Android-System, mehr Heap-Speicher für Ihre Anwendung zuzuweisen. Dies wird sehr schlechte Auswirkungen auf Ihre App haben, wie z.B. dass Ihre App viel Akkuleistung verbraucht, da GC durch den großen Heap-Speicher laufen muss, um den Speicher zu bereinigen, und auch die Leistung Ihrer App wird langsamer sein.

3 Stimmen

Warum erlaubt uns Android dann, dieses Android:largeHeap="true" in unser Manifest einzufügen? Jetzt fordern Sie Android heraus.

37voto

RayHaque Punkte 171

Es scheint, dass dies ein sehr langwieriges Problem ist, für das es viele verschiedene Erklärungen gibt. Ich habe den Rat der beiden hier am häufigsten vorgestellten Antworten befolgt, aber keine von ihnen löste mein Problem, dass die VM behauptete, sie könne sich die Bytes für die Ausführung der Dekodierung Teil des Prozesses. Nach einigem Nachforschen habe ich herausgefunden, dass das eigentliche Problem hier der Dekodierungsprozess ist, der von der NATIVE Haufen.

Siehe hier: BitmapFactory OOM macht mich verrückt

Das führte mich zu einem anderen Diskussionsthread, in dem ich einige weitere Lösungen für dieses Problem fand. Eine ist der Aufruf von System.gc(); manuell, nachdem Ihr Bild angezeigt wurde. Dies führt jedoch dazu, dass Ihre Anwendung MEHR Speicher verbraucht, um den nativen Heap zu reduzieren. Die bessere Lösung seit der Veröffentlichung von 2.0 (Donut) ist die Verwendung der BitmapFactory-Option "inPurgeable". Also habe ich einfach hinzugefügt o2.inPurgeable=true; kurz nach o2.inSampleSize=scale; .

Mehr zu diesem Thema hier: Ist die Grenze für den Heap-Speicher nur 6M?

Nun, nachdem ich all das gesagt habe, bin ich auch ein kompletter Dummkopf in Sachen Java und Android. Wenn Sie also denken, dass dies ein schrecklicher Weg ist, dieses Problem zu lösen, haben Sie wahrscheinlich Recht ;-) Aber bei mir hat das Wunder gewirkt, und ich habe festgestellt, dass es jetzt unmöglich ist, die VM aus dem Heap-Cache laufen zu lassen. Der einzige Nachteil, den ich feststellen kann, ist, dass Sie Ihr zwischengespeichertes gezeichnetes Image löschen. Das heißt, wenn Sie RICHTIG zu diesem Bild zurückkehren, müssen Sie es jedes Mal neu zeichnen. In dem Fall, wie meine Anwendung funktioniert, ist das nicht wirklich ein Problem. Ihre Erfahrungen können variieren.

0 Stimmen

InPurgeable behebt OOM für mich.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X