Seltsames Speicherproblem beim Laden eines Bildes in ein Bitmap-Objekt

1289

Ich habe eine Listenansicht mit ein paar Bildschaltflächen in jeder Zeile. Wenn Sie auf die Listenzeile klicken, wird eine neue Aktivität gestartet. Ich musste wegen eines Problems mit dem Kamera-Layout 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 (ein Bild von der SD-Karte laden), kehrt die Anwendung von der Aktivität zurück zur listviewAktivität zum Ergebnishandler zurück, um meine neue Aktivität neu zu starten, die nichts anderes als ein Bild-Widget ist.

Die Bildvorschau in der Listenansicht erfolgt mit dem Cursor und ListAdapter. Das macht es ziemlich einfach, aber ich bin mir nicht sicher, wie ich ein Bild mit geänderter Größe einfügen kann (dh kleinere Bitgröße, nicht Pixel wie srcfür die Bildschaltfläche im laufenden Betrieb. Also habe ich nur die Größe des Bilds geändert, das von der Telefonkamera kam.

Das Problem ist, dass beim Versuch, die zweite Aktivität erneut zu starten, ein Fehler aufgrund von Speichermangel auftritt.

  • Gibt es eine Möglichkeit, den Listenadapter einfach Zeile für Zeile zu erstellen, wobei ich die Größe im laufenden Betrieb ändern kann ( bitweise )?

Dies wäre vorzuziehen, da ich auch einige Änderungen an den Eigenschaften der Widgets / Elemente in jeder Zeile vornehmen muss, da ich aufgrund des Fokusproblems keine Zeile mit dem Touchscreen auswählen kann. ( Ich kann Rollerball verwenden. )

  • Ich weiß, dass ich eine Out-of-Band-Größenänderung vornehmen 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 in der Listenansicht deaktiviert hatte, funktionierte es wieder einwandfrei.

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);

Wo R.id.imagefilenameist ein 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:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
Chrispix
quelle
8
Ich habe dieses Problem gelöst, indem ich Bitmap.decodeStream oder decodeFile vermieden und die BitmapFactory.decodeFileDescriptor-Methode verwendet habe.
Fraggle
1
Vor ein paar Wochen hatte ich auch ein ähnliches Problem und habe es gelöst, indem ich die Bilder auf den optimalen Punkt verkleinert habe. Ich habe einen vollständigen Ansatz in meinem Blog encodingjunkiesforum.wordpress.com/2014/06/12/… geschrieben und ein vollständiges Beispielprojekt mit OOM-anfälligem Code gegen OOM-Proof-Code hochgeladen: http://github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat
5
Die akzeptierte Antwort auf diese Frage wird auf diskutiert meta
rene
4
Dies passiert, wenn Sie die Android-Entwicklerhandbücher nicht lesen
Pedro Varela
2
Dies geschieht aufgrund einer schlechten Android-Architektur. Die Größe der Bilder sollte wie bei ios geändert werden, und UWP tut dies. Ich muss das Zeug nicht selbst machen. Android-Entwickler gewöhnen sich an diese Hölle und denken, dass es so funktioniert, wie es sollte.
Zugang verweigert

Antworten:

651

Die Android-Schulungsklasse " Bitmaps effizient anzeigen " bietet einige nützliche Informationen zum Verständnis und zum Umgang mit der Ausnahme java.lang.OutOfMemoryError: bitmap size exceeds VM budgetbeim Laden von Bitmaps.


Lesen Sie die Abmessungen und den Typ der Bitmap

Die BitmapFactoryKlasse stellt mehrere Decodierungsverfahren ( decodeByteArray(), decodeFile(), decodeResource(), etc.) für eine Erstellung von Bitmapaus verschiedenen Quellen. Wählen Sie die am besten geeignete Dekodierungsmethode basierend auf Ihrer Bilddatenquelle. Diese Methoden versuchen, Speicher für die erstellte Bitmap zuzuweisen, und können daher leicht zu einer OutOfMemoryAusnahme führen. Jede Art von Dekodierungsmethode verfügt über zusätzliche Signaturen, mit denen Sie Dekodierungsoptionen über die BitmapFactory.OptionsKlasse angeben können . Wenn Sie die inJustDecodeBoundsEigenschaft truewährend des Decodierens auf setzen, wird die Speicherzuweisung vermieden, und es wird nullfür das Bitmap-Objekt zurückgegeben, aber gesetzt outWidth, outHeightund outMimeType. Mit dieser Technik können Sie die Abmessungen und den Typ der Bilddaten vor der Erstellung (und Speicherzuweisung) der Bitmap lesen.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Um java.lang.OutOfMemoryAusnahmen zu vermeiden , überprüfen Sie die Abmessungen einer Bitmap, bevor Sie sie dekodieren, es sei denn, Sie vertrauen absolut darauf, dass die Quelle Ihnen Bilddaten mit vorhersehbarer Größe liefert, die bequem in den verfügbaren Speicher passen.


Laden Sie eine verkleinerte Version in den Speicher

Nachdem die Bildabmessungen bekannt sind, können sie verwendet werden, um zu entscheiden, ob das vollständige Bild in den Speicher geladen werden soll oder ob stattdessen eine unterabgetastete Version geladen werden soll. Hier sind einige Faktoren zu berücksichtigen:

  • Geschätzte Speichernutzung beim Laden des vollständigen Bildes in den Speicher.
  • Die Menge an Speicher, die Sie bereit sind, zum Laden dieses Bildes zu verpflichten, wenn andere Speicheranforderungen Ihrer Anwendung erfüllt sind.
  • Abmessungen der Ziel-ImageView- oder UI-Komponente, in die das Bild geladen werden soll.
  • Bildschirmgröße und Dichte des aktuellen Geräts.

Zum Beispiel lohnt es sich nicht, ein Bild mit 1024 x 768 Pixeln in den Speicher zu laden, wenn es irgendwann in einem Miniaturbild mit 128 x 96 Pixel in einem Bild angezeigt wird ImageView.

Um den Decoder zu sagen , das Bild unterabzutasten, eine kleinere Version in dem Speicher, Satz Laden inSampleSizezu truein Ihrem BitmapFactory.OptionsObjekt. Beispielsweise erzeugt ein Bild mit einer Auflösung von 2048 x 1536, das mit einer Auflösung inSampleSizevon 4 dekodiert wird, eine Bitmap von ungefähr 512 x 384. Das Laden in den Speicher verwendet 0,75 MB anstelle von 12 MB für das vollständige Image (unter der Annahme einer Bitmap-Konfiguration von ARGB_8888). Hier ist eine Methode zum Berechnen eines Stichprobengrößenwerts, der eine Zweierpotenz basierend auf einer Zielbreite und -höhe ist:

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Hinweis : Eine Zweierpotenz wird berechnet, da der Decoder einen Endwert verwendet, indem er gemäß der inSampleSizeDokumentation auf die nächste Zweierpotenz abrundet .

Um diese Methode zu verwenden, dekodieren Sie zuerst mit inJustDecodeBoundsset to true, übergeben Sie die Optionen und dekodieren Sie dann erneut mit dem neuen inSampleSizeWert und inJustDecodeBoundssetzen Sie auf false:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

Diese Methode erleichtert das Laden einer Bitmap beliebig großer Größe in eine Bitmap mit ImageVieweiner Miniaturansicht von 100 x 100 Pixel, wie im folgenden Beispielcode gezeigt:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Sie können einen ähnlichen Prozess ausführen, um Bitmaps aus anderen Quellen zu dekodieren, indem Sie die entsprechende BitmapFactory.decode*Methode nach Bedarf ersetzen .

Sazid
quelle
21
Diese Antwort wird auf diskutiert Meta
rene
9
Diese Antwort (mit Ausnahme der Informationen, die über den Link erreicht werden) bietet keine große Lösung für eine Antwort. Die wichtigen Teile des Links sollten in die Frage eingefügt werden.
FallenAngel
7
Diese Antwort ist wie die Frage und die anderen Antworten ein Community-Wiki. Dies kann die Community also durch Bearbeiten beheben, ohne dass ein Moderator eingreifen muss.
Martijn Pieters
Der aktuelle Link zu Inhalten und Kotlin-Support finden Sie unter: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr
891

Um den OutOfMemory-Fehler zu beheben, sollten Sie Folgendes tun:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Diese inSampleSizeOption reduziert den Speicherverbrauch.

Hier ist eine vollständige Methode. Zuerst wird die Bildgröße gelesen, ohne den Inhalt selbst zu dekodieren. Dann findet es den besten inSampleSizeWert, es sollte eine Potenz von 2 sein, und schließlich wird das Bild dekodiert.

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}
Fedor
quelle
31
Beachten Sie, dass 10 möglicherweise nicht der beste Wert für inSampleSize ist. In der Dokumentation wird jedoch die Verwendung von Potenzen von 2 vorgeschlagen.
Mirko N.
70
Ich stehe vor dem gleichen Problem wie Chrispix, aber ich denke nicht, dass die Lösung hier das Problem wirklich löst, sondern es umgeht. Durch Ändern der Stichprobengröße wird der verwendete Speicherplatz reduziert (auf Kosten der Bildqualität, was für eine Bildvorschau wahrscheinlich in Ordnung ist). Die Ausnahme wird jedoch nicht verhindert, wenn ein ausreichend großer Bildstrom dekodiert wird oder wenn mehrere Bildströme vorhanden sind entschlüsselt. Wenn ich eine bessere Lösung finde (und es möglicherweise keine gibt), werde ich hier eine Antwort posten.
Flynn81
4
Sie benötigen nur eine geeignete Größe, um dem Bildschirm in der Pixeldichte zu entsprechen. Zum Vergrößern können Sie eine Probe des Bildes mit einer höheren Dichte aufnehmen.
Stealthcopter
4
REQUIRED_SIZE ist die neue Größe, auf die Sie skalieren möchten.
Fedor
8
Diese Lösung hat mir geholfen, aber die Bildqualität ist schrecklich. Ich verwende einen Viewfilpper, um die Bilder anzuzeigen. Irgendwelche Vorschläge?
user1106888
373

Ich habe Fedors Code geringfügig verbessert. Es macht im Grunde das Gleiche, aber ohne die (meiner Meinung nach) hässliche while-Schleife und es ergibt sich immer eine Zweierpotenz. Ein großes Lob an Fedor für die ursprüngliche Lösung. Ich steckte fest, bis ich seine gefunden hatte, und dann konnte ich diese erstellen :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}
Thomas Vervest
quelle
40
Ja du hast recht während es nicht so schön ist. Ich habe nur versucht, es allen klar zu machen. Danke für deinen Code.
Fedor
10
@ Thomas Vervest - Es gibt ein großes Problem mit diesem Code. ^ erhöht 2 nicht zu einer Potenz, sondern xors 2 mit dem Ergebnis. Sie möchten Math.pow (2.0, ...). Ansonsten sieht das gut aus.
DougW
6
Oh, das ist sehr gut! Mein schlechtes, ich werde es sofort korrigieren, danke für die Antwort!
Thomas Vervest
8
Sie erstellen zwei neue FileInputStreams, einen für jeden Aufruf des BitmapFactory.decodeStream(). Müssen Sie nicht für jeden einen Verweis speichern, damit sie in einem finallyBlock geschlossen werden können ?
Matsev
1
@Babibu Die Dokumentation besagt nicht, dass der Stream für Sie geschlossen ist, daher gehe ich davon aus, dass er noch geschlossen sein sollte. Eine interessante und verwandte Diskussion finden Sie hier . Beachten Sie den Kommentar von Adrian Smith, der sich direkt auf unsere Debatte bezieht.
Thomas Vervest
233

Ich komme aus der iOS-Erfahrung und war frustriert, ein Problem mit etwas so Grundlegendem wie dem Laden und Anzeigen eines Bildes zu entdecken. Schließlich versucht jeder, der dieses Problem hat, Bilder in angemessener Größe anzuzeigen. Wie auch immer, hier sind die beiden Änderungen, die mein Problem behoben haben (und meine App sehr reaktionsschnell gemacht haben).

1) Stellen Sie jedes Mal BitmapFactory.decodeXYZ()sicher, dass Sie ein BitmapFactory.Optionswith inPurgeableset to true(und vorzugsweise with also inInputShareableset to true) übergeben.

2) NIEMALS verwenden Bitmap.createBitmap(width, height, Config.ARGB_8888). Ich meine NIE! Ich hatte noch nie das Ding, das nach wenigen Durchgängen keinen Speicherfehler auslöste. Kein Betrag von recycle(), System.gc(), was auch immer geholfen. Es wurde immer eine Ausnahme ausgelöst. Die andere Möglichkeit, die tatsächlich funktioniert, besteht darin, ein Dummy-Bild in Ihren Drawables (oder einer anderen Bitmap, die Sie mit Schritt 1 oben dekodiert haben) zu haben, es nach Belieben neu zu skalieren und dann die resultierende Bitmap zu bearbeiten (z. B. es an eine Leinwand weiterzugeben) für mehr Spaß). Was Sie stattdessen verwenden sollten, ist : Bitmap.createScaledBitmap(srcBitmap, width, height, false). Wenn Sie aus irgendeinem Grund die Brute-Force-Erstellungsmethode verwenden MÜSSEN, müssen Sie mindestens bestehen Config.ARGB_4444.

Dies spart Ihnen fast garantiert Stunden, wenn nicht Tage. Alles, was über das Skalieren des Bildes usw. spricht, funktioniert nicht wirklich (es sei denn, Sie halten eine falsche Größe oder ein verschlechtertes Bild für eine Lösung).

Ephraim
quelle
22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;und Bitmap.createScaledBitmap(srcBitmap, width, height, false);löste mein Problem, das ich mit Speichermangel auf Android 4.0.0 hatte. Danke Kumpel!
Jan-Terje Sørensen
5
Im Aufruf von Bitmap.createScaledBitmap () sollten Sie wahrscheinlich true als Flag-Parameter verwenden. Andernfalls ist die Bildqualität beim Skalieren nicht gleichmäßig. Überprüfen Sie diesen Thread stackoverflow.com/questions/2895065/…
rOrlig
11
Das ist wirklich ein großartiger Rat. Ich wünschte, ich könnte Ihnen eine zusätzliche +1 geben, wenn Sie Google für diesen erstaunlich verrückten Dink-Bug zur Verantwortung ziehen. Ich meine ... wenn es kein Fehler ist, muss die Dokumentation wirklich einige ernsthaft blinkende Leuchtreklamen mit der Aufschrift "SO VERARBEITEN SIE FOTOS" haben, weil ich seit 2 Jahren damit zu kämpfen habe und gerade diesen Beitrag gefunden habe. Toller Fund.
Jewgeni Simkin
Das Verkleinern Ihrer Bilder hilft auf jeden Fall, aber dies ist ein wichtiger Schritt und hat dieses Problem letztendlich für mich gelöst. Das Problem beim Skalieren Ihrer Bilder besteht darin, dass Sie immer noch auf dasselbe Problem stoßen, wenn Sie viele davon haben oder wenn die Quellbilder sehr groß sind. +1 zu dir Ephraim.
Dave
10
Ab Lollipop BitmapFactory.Options.inPurgeableund BitmapFactory.Options.inInputShareableveraltet. Developer.android.com/reference/android/graphics/…
Denis Kniazhev
93

Es ist ein bekannter Fehler , nicht wegen großer Dateien. Da Android die Drawables zwischenspeichert, geht nach Verwendung weniger Bilder der Speicherplatz aus. Aber ich habe einen alternativen Weg dafür gefunden, indem ich das Android-Standard-Cache-System übersprungen habe.

Lösung : Verschieben Sie die Bilder in den Ordner "Assets" und verwenden Sie die folgende Funktion, um BitmapDrawable zu erhalten:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}
Anto Binish Kaspar
quelle
79

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

decodeFileDescriptor Es sieht so aus, als würde es andere native Methoden als decodeStream / decodeFile aufrufen.

Was auch immer funktioniert hat, war dies (beachten Sie, dass ich einige Optionen hinzugefügt habe, wie einige oben, aber das hat den Unterschied nicht ausgemacht. Entscheidend ist der Aufruf von BitmapFactory.decodeFileDescriptor anstelle von decodeStream oder 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 denke, es gibt ein Problem mit der nativen Funktion, die in decodeStream / decodeFile verwendet wird. Ich habe bestätigt, dass bei Verwendung von decodeFileDescriptor eine andere native Methode aufgerufen wird. Ich habe auch gelesen, "dass Bilder (Bitmaps) nicht auf Standard-Java-Weise, sondern über native Aufrufe zugewiesen werden; die Zuweisungen erfolgen außerhalb des virtuellen Heaps, werden aber dagegen gezählt! "

Fraggle
quelle
1
Das gleiche Ergebnis aus dem Speicher, eigentlich spielt es keine Rolle, welche Methode Sie verwenden, hängt von der Anzahl der Bytes ab, die Sie zum Lesen der Daten halten, die nicht genügend Speicher haben.
PiyushMishra
72

Ich denke, der beste Weg, dies zu vermeiden, OutOfMemoryErrorbesteht darin, sich ihm zu stellen und ihn zu verstehen.

Ich habe eine App erstellt, um absichtlich OutOfMemoryErrordie Speichernutzung zu verursachen und zu überwachen.

Nachdem ich viele Experimente mit dieser App durchgeführt habe, habe ich die folgenden Schlussfolgerungen gezogen:

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

  1. Bitmap wird auf einem nativen Heap gespeichert, aber der Müll wird automatisch gesammelt. Der Aufruf von recycle () ist unnötig.

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

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

  3. Die Größe des VM-Heapspeichers wird nach dem Vergrößern niemals verkleinert, selbst wenn der zugewiesene VM-Speicher verkleinert wird.

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

  5. Das manuelle Aufrufen von System.gc () ist bedeutungslos. Das System ruft es zuerst auf, bevor es versucht, die Größe des Heapspeichers zu erhöhen.

  6. Die native Heap-Größe wird auch nie kleiner, aber sie wird nicht für OOM gezählt, sodass Sie sich darüber keine Sorgen machen müssen.

Lassen Sie uns dann über SDK Starts from Honey Comb sprechen.

  1. Bitmap wird im VM-Heap gespeichert, nativer Speicher wird für OOM nicht gezählt.

  2. Die Bedingung für OOM ist viel einfacher: {VM-Heap-Größe}> = {Beschränkung der VM-Heap-Größe für das Gerät}.

  3. Sie haben also mehr verfügbaren Speicher, um eine Bitmap mit derselben Heap-Größenbeschränkung zu erstellen. Es ist weniger wahrscheinlich, dass OOM ausgelöst wird.

Hier sind einige meiner Beobachtungen zu Garbage Collection und Memory Leak.

Sie können es selbst in der App sehen. Wenn eine Aktivität eine AsyncTask ausgeführt hat, die nach der Zerstörung der Aktivität noch ausgeführt wurde, wird in der Aktivität erst nach Abschluss der AsyncTask Müll gesammelt.

Dies liegt daran, dass AsyncTask eine Instanz einer anonymen inneren Klasse ist und einen Verweis auf die Aktivität enthält.

Durch Aufrufen von AsyncTask.cancel (true) wird die Ausführung nicht gestoppt, wenn die Task in einer E / A-Operation im Hintergrundthread blockiert wird.

Rückrufe sind ebenfalls anonyme innere Klassen. Wenn eine statische Instanz in Ihrem Projekt sie enthält und sie nicht freigibt, geht Speicher verloren.

Wenn Sie eine sich wiederholende oder verzögerte Aufgabe, z. B. einen Timer, geplant haben und in onPause () nicht cancel () und purge () aufrufen, geht Speicher verloren.

coocood
quelle
AsyncTask muss nicht unbedingt "eine Instanz einer anonymen inneren Klasse" sein, und das gilt auch für Callbackks. Sie können eine neue öffentliche Klasse in einer eigenen Datei erstellen, die AsyncTask erweitert, oder sogar eine private static classin derselben Klasse. Sie werden keinen Hinweis auf die Aktivität enthalten (es sei denn, Sie geben ihnen natürlich eine)
Simon Forsberg
65

Ich habe in letzter Zeit viele Fragen zu OOM-Ausnahmen und Caching gesehen. Das Entwicklerhandbuch enthält einen wirklich guten Artikel dazu, aber einige scheitern daran, ihn auf geeignete Weise zu implementieren.

Aus diesem Grund habe ich eine Beispielanwendung geschrieben, die das Caching in einer Android-Umgebung demonstriert. Diese Implementierung hat noch keine OOM erhalten.

Am Ende dieser Antwort finden Sie einen Link zum Quellcode.

Bedarf:

  • Android API 2.1 oder höher (Ich konnte es einfach nicht schaffen, den verfügbaren Speicher für eine Anwendung in API 1.6 abzurufen - das ist der einzige Code, der in API 1.6 nicht funktioniert.)
  • Android-Support-Paket

Bildschirmfoto

Eigenschaften:

  • Behält den Cache bei, wenn sich die Ausrichtung mithilfe eines Singletons ändert
  • Verwenden Sie ein Achtel des zugewiesenen Anwendungsspeichers für den Cache (ändern Sie ihn, wenn Sie möchten).
  • Große Bitmaps werden skaliert (Sie können die maximalen Pixel definieren, die Sie zulassen möchten).
  • Steuert, ob vor dem Herunterladen der Bitmaps eine Internetverbindung verfügbar ist
  • Stellt sicher, dass Sie nur eine Aufgabe pro Zeile instanziieren
  • Wenn Sie schleudert die ListViewweg, wird es einfach nicht laden Sie die Bitmaps zwischen

Dies beinhaltet nicht:

  • Festplatten-Caching. Dies sollte ohnehin einfach zu implementieren sein - zeigen Sie einfach auf eine andere Aufgabe, bei der die Bitmaps von der Festplatte abgerufen werden

Beispielcode:

Die Bilder, die heruntergeladen werden, sind Bilder (75 x 75) von Flickr. Geben Sie jedoch die Bild-URLs ein, die verarbeitet werden sollen, und die Anwendung verkleinert sie, wenn sie das Maximum überschreitet. In dieser Anwendung befinden sich die URLs einfach in einem StringArray.

Das LruCachehat eine gute Möglichkeit, mit Bitmaps umzugehen. In dieser Anwendung habe ich jedoch eine Instanz einer LruCacheanderen Cache-Klasse eingefügt, die ich erstellt habe, um die Anwendung praktikabler zu machen.

Das kritische Zeug von Cache.java (die loadBitmap()Methode ist die wichtigste):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

Sie sollten nichts in der Datei Cache.java bearbeiten müssen, es sei denn, Sie möchten das Festplatten-Caching implementieren.

Die kritischen Dinge von MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView()wird sehr oft angerufen. Es ist normalerweise keine gute Idee, Bilder dort herunterzuladen, wenn wir keine Überprüfung implementiert haben, die sicherstellt, dass wir nicht unendlich viele Threads pro Zeile starten. Cache.java prüft, ob sich das rowObject.mBitmapUrlbereits in einer Aufgabe befindet, und wenn dies der Fall ist, wird keine weitere gestartet. Daher überschreiten wir höchstwahrscheinlich nicht die Einschränkung der Arbeitswarteschlange aus demAsyncTask Pool.

Herunterladen:

Sie können den Quellcode von https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip herunterladen .


Letzte Worte:

Ich habe dies jetzt seit ein paar Wochen getestet, ich habe noch keine einzige OOM-Ausnahme erhalten. Ich habe dies auf dem Emulator, auf meinem Nexus One und auf meinem Nexus S getestet. Ich habe Bild-URLs getestet, die Bilder in HD-Qualität enthalten. Der einzige Engpass besteht darin, dass das Herunterladen länger dauert.

Es gibt nur ein mögliches Szenario, in dem ich mir vorstellen kann, dass das OOM angezeigt wird. Wenn wir viele wirklich große Bilder herunterladen und bevor sie skaliert und in den Cache gestellt werden, wird gleichzeitig mehr Speicherplatz beansprucht und ein OOM verursacht. Aber das ist sowieso nicht einmal eine ideale Situation und es wird höchstwahrscheinlich nicht möglich sein, sie auf eine praktikablere Weise zu lösen.

Fehler in den Kommentaren melden! :-)

Wroclai
quelle
43

Ich habe Folgendes getan, um das Bild aufzunehmen und die Größe im laufenden Betrieb zu ändern. Hoffe das hilft

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
Chrispix
quelle
26
Dieser Ansatz skaliert die Bitmap. Das OutOfMemory-Problem wird dadurch jedoch nicht gelöst, da die vollständige Bitmap ohnehin dekodiert wird.
Fedor
5
Ich werde sehen, ob ich meinen alten Code sehen kann, aber ich denke, er hat meine Probleme mit nicht genügend Speicher gelöst. Überprüft meinen alten Code.
Chrispix
2
Zumindest in diesem Beispiel sieht es so aus, als würden Sie den Verweis auf die vollständige Bitmap nicht beibehalten, was zu einer Speicherersparnis führt.
NoBugs
Für mich hat es das Speicherproblem gelöst, aber die Qualität der Bilder verringert.
Pamela Sillah
35

Es scheint, dass dies ein sehr lang anhaltendes Problem ist, mit vielen unterschiedlichen Erklärungen. Ich habe den Rat der beiden am häufigsten vorgestellten Antworten befolgt, aber keine dieser Antworten hat meine Probleme gelöst, da die VM behauptete, sie könne sich die Bytes nicht leisten, um den Dekodierungsteil des Prozesses durchzuführen . Nach einigem Graben habe ich erfahren, dass das eigentliche Problem hier der Dekodierungsprozess ist, der vom NATIVE- Heap entfernt wird.

Siehe hier: BitmapFactory OOM macht mich verrückt

Das führte mich zu einem weiteren Diskussionsthread, in dem ich einige weitere Lösungen für dieses Problem fand. Eine Möglichkeit besteht darin, System.gc();manuell anzurufen, nachdem Ihr Bild angezeigt wurde. Dadurch verwendet Ihre App jedoch MEHR Speicher, um den nativen Heap zu reduzieren. Die bessere Lösung ab Version 2.0 (Donut) ist die Verwendung der BitmapFactory-Option "inPurgeable". Also habe ich einfach o2.inPurgeable=true;gleich danach hinzugefügto2.inSampleSize=scale; .

Mehr zu diesem Thema hier: Ist die Grenze des Speicherheaps nur 6M?

Nachdem ich das alles gesagt habe, bin ich auch mit Java und Android ein Vollidiot. Wenn Sie also der Meinung sind, dass dies ein schrecklicher Weg ist, um dieses Problem zu lösen, haben Sie wahrscheinlich Recht. ;-) Aber das hat Wunder für mich gewirkt und ich fand es unmöglich, die VM jetzt aus dem Heap-Cache heraus laufen zu lassen. Der einzige Nachteil, den ich feststellen kann, ist, dass Sie Ihr zwischengespeichertes gezeichnetes Bild in den Papierkorb werfen. Das heißt, wenn Sie direkt zu diesem Bild zurückkehren, zeichnen Sie es jedes Mal neu. Wenn meine Anwendung funktioniert, ist das kein wirkliches Problem. Ihr Kilometerstand kann variieren.

RayHaque
quelle
inPurgeable feste OOM für mich.
Artem Russakovskii
35

Wenn keine der oben genannten Funktionen funktioniert, fügen Sie diese leider Ihrer Manifest- Datei hinzu. Innerhalb des Anwendungs- Tags

 <application
         android:largeHeap="true"
Himanshu Mori
quelle
1
Können Sie erklären, was dies tatsächlich tut? Es hilft nicht, den Leuten einfach zu sagen, dass sie dies hinzufügen sollen.
Stealth Rabbi
1
Dies ist eine sehr schlechte Lösung. Grundsätzlich versuchen Sie nicht, das Problem zu beheben. Bitten Sie stattdessen das Android-System, mehr Heap-Speicherplatz für Ihre Anwendung zuzuweisen. Dies hat sehr schlimme Auswirkungen auf Ihre App, da Ihre App viel Batteriestrom verbraucht, da GC einen großen Heap-Speicherplatz durchlaufen muss, um den Speicher zu bereinigen, und auch die Leistung Ihrer App langsamer wird.
Prakash
2
Warum erlaubt uns Android dann, dieses Android hinzuzufügen: largeHeap = "true" in unserem Manifest? Jetzt fordern Sie Android heraus.
Himanshu Mori
32

Verwenden Sie dies. bitmap.recycle();Dies hilft ohne Probleme mit der Bildqualität.

Arsalan
quelle
9
Laut API ist der Aufruf von recycle () nicht erforderlich.
Artem
28

Ich habe eine viel effektivere Lösung, die keinerlei Skalierung benötigt. Dekodieren Sie Ihre Bitmap einfach nur einmal und speichern Sie sie dann in einer Karte gegen ihren Namen. Rufen Sie dann einfach die Bitmap anhand des Namens ab und legen Sie sie in der ImageView fest. Es muss nichts mehr getan werden.

Dies funktioniert, da die tatsächlichen Binärdaten der decodierten Bitmap nicht im Dalvik-VM-Heap gespeichert sind. Es wird extern gespeichert. Jedes Mal, wenn Sie eine Bitmap dekodieren, wird Speicher außerhalb des VM-Heaps zugewiesen, der von GC niemals zurückgefordert wird

Stellen Sie sich vor, Sie haben Ihr Bild im Zeichenordner gespeichert, damit Sie dies besser einschätzen können. Sie erhalten das Bild einfach, indem Sie getResources (). GetDrwable (R.drawable.) Ausführen. Dadurch wird Ihr Image NICHT jedes Mal dekodiert, sondern jedes Mal, wenn Sie es aufrufen, wird eine bereits dekodierte Instanz erneut verwendet. Im Wesentlichen wird es also zwischengespeichert.

Da sich Ihr Image jetzt irgendwo in einer Datei befindet (oder möglicherweise sogar von einem externen Server stammt), liegt es in Ihrer Verantwortung, die decodierte Bitmap-Instanz zwischenzuspeichern, um sie dort wiederzuverwenden, wo sie benötigt wird.

Hoffe das hilft.

Parth Mehta
quelle
4
"und zwischenspeichern Sie es dann in einer Karte gegen seinen Namen." Wie genau zwischenspeichern Sie Ihre Bilder?
Vincent
3
Hast du das tatsächlich versucht? Obwohl die Pixeldaten nicht tatsächlich im Dalvik-Heap gespeichert sind, wird ihre Größe im nativen Speicher an die VM gemeldet und auf den verfügbaren Speicher angerechnet.
ErikR
3
@ Vincent Ich denke, es ist nicht schwer, sie in einer Karte zu speichern. Ich würde so etwas wie eine HashMap <KEY, Bitmap> -Karte vorschlagen, bei der der Schlüssel eine Zeichenfolge der Quelle sein kann oder alles, was für Sie sinnvoll ist. Nehmen wir an, Sie nehmen einen Pfad als SCHLÜSSEL, speichern ihn als map.put (Pfad, Bitmap) und erhalten ihn über map.get (Pfad)
Rafael T
3
Sie möchten wahrscheinlich HashMap <String, SoftReference <Bitmap>> verwenden, wenn Sie einen Image-Cache implementieren, da Ihnen sonst ohnehin der Speicher ausgeht - ich glaube auch nicht, dass "Speicher außerhalb des VM-Heaps zugewiesen wird, der von GC niemals zurückgefordert wird "ist wahr, die Erinnerung wird zurückgefordert, da ich verstehe, dass es sich möglicherweise um eine Verzögerung handelt, wofür bitmap.recycle () gedacht ist, um das Mem frühzeitig zurückzugewinnen ...
Dori
28

Ich habe das gleiche Problem auf folgende Weise gelöst.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);
Prerna
quelle
das ist in Ordnung, aber ich verwende mehrere Bitmaps zum Zeichnen von Kreisen in OnCreate und Aktivitätsaufruf 4-5 Mal, um Bitmap zu löschen und Bitmap zu entfernen und zum zweiten Mal neu zu erstellen, wenn Aktivität 0nCreate ..
ckpatel
27

Hier gibt es zwei Probleme ...

  • Der Bitmap-Speicher befindet sich nicht im VM-Heap, sondern im nativen Heap - siehe BitmapFactory OOM, das mich verrückt macht
  • Die Garbage Collection für den nativen Heap ist fauler als der VM-Heap. Daher müssen Sie bei jedem Durchlaufen von onPause oder onDestroy einer Aktivität ziemlich aggressiv sein, wenn Sie bitmap.recycle und bitmap = null ausführen
Torid
quelle
Es ist in VM Heap seit Android 2.3+
FindOut_Quran
27

Das hat bei mir funktioniert!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}
Luke Taylor
quelle
20

Keine der oben genannten Antworten hat für mich funktioniert, aber ich habe eine schrecklich hässliche Problemumgehung gefunden, die das Problem gelöst hat. Ich habe meinem Projekt ein sehr kleines 1x1-Pixel-Bild als Ressource hinzugefügt und es in meine ImageView geladen, bevor ich die Garbage Collection aufgerufen habe. Ich denke, es könnte sein, dass ImageView die Bitmap nicht veröffentlicht hat, also hat GC sie nie aufgegriffen. Es ist hässlich, aber es scheint vorerst zu funktionieren.

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
Mike
quelle
Es sieht so aus, als würde imageView die Bitmap wirklich nicht selbst recyceln. Hat mir geholfen, danke
Dmitry Zaytsev
@Mike kannst du den kompletten Code von imageloader hinzufügen oder mir den Link zum Laden von Bitmap-Bildern geben. Wenn ich auf Bitmap recyceln verwende, wird meine gesamte Listenansicht angezeigt, aber alle Elemente werden leer angezeigt
TNR
@ Mike Kannst du sagen, ob ich imageView = null mache, bevor ich in die Garbage Collection rufe? Ist es gut oder nicht?
Youddh
@TNR Ich denke, was Sie hier vermissen, ist, dass bitmapim obigen Code das vorherige bereits angezeigte Bild ist. Sie müssen es recyceln, alle Verweise darauf löschen und es auch imageViewvergessen, indem Sie einen winzigen Ersatz gc()festlegen. und nach all dem: Laden Sie Ihr NEUES Bild in das oben angegebene Code bitmapund zeigen Sie es an ....
TWiStErRob
Das ist falsch. Sie sollten Ihren imageView-Inhalt immer löschen, bevor Sie die Bitmap recyceln (anstatt sie tatsächlich anzuzeigen und zu verwenden).
FindOut_Quran
20

Tolle Antworten hier, aber ich wollte eine voll nutzbare Klasse , um dieses Problem anzugehen. Also habe ich eine gemacht.

Hier ist meine BitmapHelper-Klasse , die OutOfMemoryError- geprüft ist :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}
Pascal
quelle
Für alle, die dies verwenden: Ich habe gerade einen Fehler behoben: "int scaledBitmapHeight = scaledBitmap.getWidth ();" ist falsch (offensichtlich. Ich habe es durch "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal
19

Das funktioniert bei mir.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

und das ist auf C # Monodroid. Sie können den Pfad des Bildes leicht ändern. Was hier wichtig ist, sind die einzustellenden Optionen.

Dobermaxx99
quelle
16

Dies scheint der geeignete Ort zu sein, um meine Utility-Klasse zum Laden und Verarbeiten von Bildern für die Community freizugeben. Sie können sie gerne verwenden und frei ändern.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}
Emil Davtyan
quelle
16

In einer meiner Bewerbungen muss ich entweder ein Bild von machen Camera/Gallery. Wenn der Benutzer auf ein Bild von der Kamera klickt (möglicherweise 2 MP, 5 MP oder 8 MP), variiert die Bildgröße von kBs bis MBs. Wenn die Bildgröße kleiner (oder bis zu 1-2 MB) über dem Code liegt, funktioniert dies einwandfrei. Wenn ich jedoch ein Bild mit einer Größe über 4 MB oder 5 MB habe, wird der folgende OOMFrame angezeigt :(

dann habe ich daran gearbeitet, dieses Problem zu lösen und schließlich habe ich die folgende Verbesserung des Fedor-Codes (All Credit to Fedor für die Erstellung einer so schönen Lösung) vorgenommen :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.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

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

Ich hoffe, das wird den Kumpels helfen, die vor dem gleichen Problem stehen!

Weitere Informationen finden Sie hier

Rupesh Yadav
quelle
14

Ich bin gerade vor ein paar Minuten auf dieses Problem gestoßen. Ich habe es gelöst, indem ich meinen Listenansichtsadapter besser verwaltet habe. Ich dachte, es sei ein Problem mit den Hunderten von 50x50px-Bildern, die ich verwendet habe. Es stellte sich heraus, dass ich jedes Mal, wenn die Zeile angezeigt wurde, versuchte, meine benutzerdefinierte Ansicht aufzublähen. Durch einfaches Testen, ob die Zeile aufgeblasen wurde, habe ich diesen Fehler beseitigt und verwende Hunderte von Bitmaps. Dies ist eigentlich für einen Spinner, aber der Basisadapter funktioniert für eine ListView trotzdem. Diese einfache Lösung hat auch die Leistung des Adapters erheblich verbessert.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...
BajaBob
quelle
3
Ich kann dir nicht genug dafür danken! Ich habe das falsche Problem verfolgt, bevor ich das gesehen habe. Frage an Sie: Da jede oder meine Listenzeilen einen eindeutigen Namen und ein eindeutiges Foto haben, musste ich ein convertView-Array verwenden, um die Werte der einzelnen Zeilen beizubehalten. Ich konnte nicht sehen, wie Sie dies mit einer einzelnen Variablen tun können. Vermisse ich etwas
PeteH
13

Ich habe den ganzen Tag damit verbracht, diese Lösungen zu testen, und das einzige, was für mich funktioniert hat, sind die oben genannten Ansätze zum Abrufen des Bildes und zum manuellen Aufrufen des GC, von denen ich weiß, dass sie nicht erforderlich sein sollten, aber es ist das einzige, was funktioniert hat wenn ich meine App einem Schwerlasttest unterziehe, um zwischen Aktivitäten zu wechseln. Meine App verfügt über eine Liste von Miniaturbildern in einer Listenansicht in (z. B. Aktivität A). Wenn Sie auf eines dieser Bilder klicken, gelangen Sie zu einer anderen Aktivität (z. B. Aktivität B), in der ein Hauptbild für dieses Element angezeigt wird. Wenn ich zwischen den beiden Aktivitäten hin und her wechseln würde, würde ich schließlich den OOM-Fehler erhalten und die App würde das Schließen erzwingen.

Wenn ich auf halbem Weg in der Listenansicht wäre, würde es abstürzen.

Wenn ich jetzt das Folgende in Aktivität B implementiere, kann ich die gesamte Listenansicht ohne Probleme durchgehen und weitermachen und weitermachen und weitermachen ... und es ist schnell genug.

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
Jesse
quelle
Liebe deine Lösung! Ich habe auch Stunden damit verbracht, diesen Fehler zu beheben, also nervig! Bearbeiten: Leider besteht das Problem immer noch, wenn ich meine Bildschirmausrichtung im
Querformat ändere
Dies half mir schließlich zusammen mit: - BitmapFactory.Options options = new BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732
13

Dieses Problem tritt nur in Android-Emulatoren auf. Ich hatte dieses Problem auch in einem Emulator, aber als ich ein Gerät eincheckte, funktionierte es einwandfrei.

Also bitte ein Gerät einchecken. Es kann im Gerät ausgeführt werden.

Hackjutsu
quelle
12

Meine 2 Cent: Ich habe meine OOM-Fehler mit Bitmaps gelöst durch:

a) Skaliere meine Bilder um den Faktor 2

b) Verwenden der Picasso- Bibliothek in meinem benutzerdefinierten Adapter für eine ListView mit einem einzigen Aufruf in getView wie folgt:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

Matsoftware
quelle
Ich bin froh, dass Sie Picasso erwähnt haben, da dies das Laden von Bildern erheblich erleichtert. Besonders ferngesteuerte.
Chrispix
12

Verwenden Sie diesen Code für jedes Bild, das in SdCard ausgewählt oder gezeichnet werden kann, um ein Bitmap-Objekt zu konvertieren.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

Verwenden Sie Ihren Bildpfad instend von ImageData_Path.get (img_pos) .getPath () .

Gaurav Pansheriya
quelle
12

Im Allgemeinen beträgt die Heap-Größe des Android-Geräts nur 16 MB (variiert je nach Gerät / Betriebssystem, siehe Beitrag Heap-Größen ). Wenn Sie die Bilder laden und die Größe von 16 MB überschreiten, wird eine Speicherausnahme ausgelöst, anstatt die Bitmap zum Laden zu verwenden Bilder von der SD-Karte oder von Ressourcen oder sogar vom Netzwerk versuchen, getImageUri zu verwenden. Das Laden der Bitmap erfordert mehr Speicher, oder Sie können die Bitmap auf null setzen, wenn Sie mit dieser Bitmap arbeiten.

Rohit Sharma
quelle
1
Und wenn setImageURI immer noch eine Ausnahme bekommt, dann verweisen Sie
Mahesh
11

Für alle hier aufgeführten Lösungen muss IMAGE_MAX_SIZE festgelegt werden. Dies schränkt Geräte mit leistungsfähigerer Hardware ein. Wenn die Bildgröße zu niedrig ist, sieht sie auf dem HD-Bildschirm hässlich aus.

Ich habe eine Lösung herausgebracht, die mit meinem Samsung Galaxy S3 und mehreren anderen Geräten funktioniert, einschließlich weniger leistungsfähiger Geräte, mit besserer Bildqualität, wenn ein leistungsfähigeres Gerät verwendet wird.

Der Kern davon besteht darin, den maximalen Speicher zu berechnen, der für die App auf einem bestimmten Gerät zugewiesen ist, und dann die Skala so niedrig wie möglich einzustellen, ohne diesen Speicher zu überschreiten. Hier ist der Code:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

Ich habe den maximalen Speicher, der von dieser Bitmap verwendet wird, auf 25% des maximal zugewiesenen Speichers festgelegt. Möglicherweise müssen Sie diesen an Ihre Bedürfnisse anpassen und sicherstellen, dass diese Bitmap bereinigt wird und nicht im Speicher bleibt, wenn Sie sie nicht mehr verwenden. Normalerweise verwende ich diesen Code, um eine Bildrotation durchzuführen (Quell- und Ziel-Bitmap), sodass meine App 2 Bitmaps gleichzeitig in den Speicher laden muss. 25% geben mir einen guten Puffer, ohne dass mir bei der Bildrotation der Speicher ausgeht.

Hoffe das hilft jemandem da draußen ..

Bruce
quelle
11

Dies OutofMemoryExceptionkann nicht vollständig gelöst werden, indem man das System.gc()und so weiter anruft .

Unter Bezugnahme auf den Aktivitätslebenszyklus

Die Aktivitätszustände werden vom Betriebssystem selbst abhängig von der Speichernutzung für jeden Prozess und der Priorität jedes Prozesses bestimmt.

Sie können die Größe und die Auflösung für jedes der verwendeten Bitmap-Bilder berücksichtigen. Ich empfehle, die Größe zu reduzieren, auf niedrigere Auflösung neu abzutasten und sich auf das Design der Galerien zu beziehen (ein kleines Bild-PNG und ein Originalbild).

Raju Gujarati
quelle
11

Dieser Code hilft beim Laden einer großen Bitmap aus Drawable

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
Weinstock
quelle
Schön, denken Sie, es wäre besser, einen Loader anstelle einer asynchronen Aufgabe zu verwenden?
Chrispix
Wie wäre es mit Bitmap.Config.ARGB_565 ? Wenn hohe Qualität nicht kritisch ist.
Hamzeh Soboh