web-dev-qa-db-de.com

Merkwürdiges Speicherproblem beim Laden eines Bildes in ein Bitmap-Objekt

Ich habe eine Listenansicht mit einigen 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 Aktivität listview zum Ergebnis-Handler, um meine neue Aktivität, die nichts anderes als eine ist, neu zu starten Bild-Widget.

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 verkleinertes Bild einfügen kann (das heißt, eine kleinere Bitgröße, kein Pixel wie src für die Bildschaltfläche im laufenden Betrieb) Handy-Kamera.

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

  • Gibt es eine Möglichkeit, wie ich den Listenadapter einfach zeilenweise erstellen kann, wobei ich die Größe im Handumdrehen ä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 Roller Ball benutzen.)

  • Ich weiß, dass ich eine Out-of-Band-Größenänderung und -Speicherung meines Bildes durchführen 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 habe, hat es wieder 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);

Wobei R.id.imagefilename ein ButtonImage ist.

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
1229
Chrispix

Die Android Training -Klasse " Bitmaps effizient anzeigen " bietet einige nützliche Informationen zum Verstehen und Behandeln der Ausnahme Java.lang.OutOfMemoryError: bitmap size exceeds VM budget beim Laden von Bitmaps.


Lesen Sie Bitmap-Abmessungen und -Typ

Die Klasse BitmapFactory bietet verschiedene Dekodierungsmethoden (decodeByteArray(), decodeFile(), decodeResource() usw.) zum Erstellen eines Bitmap aus verschiedenen Quellen. Wählen Sie die am besten geeignete Decodierungsmethode basierend auf Ihrer Bilddatenquelle. Diese Methoden versuchen, Speicher für die erstellte Bitmap zu reservieren, und können daher leicht zu einer OutOfMemory-Ausnahme führen. Jede Art von Dekodierungsmethode verfügt über zusätzliche Signaturen, mit denen Sie Dekodierungsoptionen über die Klasse BitmapFactory.Options angeben können. Durch das Festlegen der Eigenschaft inJustDecodeBounds auf true während der Dekodierung wird die Speicherzuordnung vermieden. Es wird null für das Bitmap-Objekt zurückgegeben, aber outWidth, outHeight und 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.OutOfMemory Ausnahmen zu vermeiden, überprüfen Sie die Abmessungen einer Bitmap, bevor Sie sie dekodieren, es sei denn, Sie vertrauen absolut darauf, dass die Quelle Ihnen vorhersagbar große Bilddaten 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 gesamten Bilds im Speicher.
  • Die Menge an Speicher, die Sie bereit sind, um dieses Bild zu laden, wenn andere Speicheranforderungen Ihrer Anwendung vorliegen.
  • Abmessungen der ImageView- oder UI-Zielkomponente, in die das Image geladen werden soll.
  • Bildschirmgröße und Dichte des aktuellen Geräts.

Es lohnt sich beispielsweise nicht, ein Bild mit einer Auflösung von 1024 x 768 Pixel in den Speicher zu laden, wenn es schließlich in einem ImageView in einer Miniaturansicht mit 128 x 96 Pixel angezeigt wird.

Um den Decoder anzuweisen, das Bild zu subsampeln und eine kleinere Version in den Speicher zu laden, setzen Sie inSampleSize in Ihrem Objekt BitmapFactory.Options auf true. Ein Bild mit einer Auflösung von 2048 x 1536, das mit einem Wert von inSampleSize von 4 decodiert wurde, erzeugt beispielsweise eine Bitmap von ungefähr 512 x 384. Das Laden in den Speicher benötigt 0,75 MB anstatt 12 MB für das gesamte Bild (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 : Ein Zweierpotenzwert wird berechnet, da der Decoder einen Endwert verwendet, indem er gemäß der Dokumentation zu inSampleSize auf die nächste Zweierpotenz aufgerundet wird.

Um diese Methode zu verwenden, dekodieren Sie zuerst mit inJustDecodeBounds auf true, übergeben Sie die Optionen und dekodieren Sie dann erneut mit dem neuen Wert inSampleSize und inJustDecodeBounds 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);
}

Mit dieser Methode können Sie auf einfache Weise eine Bitmap beliebig großer Größe in ein ImageView laden, das eine Miniaturansicht mit 100 x 100 Pixeln anzeigt, wie im folgenden Beispielcode gezeigt:

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

Sie können auf ähnliche Weise Bitmaps aus anderen Quellen dekodieren, indem Sie die entsprechende BitmapFactory.decode* -Methode nach Bedarf einsetzen.

619
AdamK

Um den OutOfMemory-Fehler zu beheben, sollten Sie folgendermaßen vorgehen:

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

Diese inSampleSize Option reduziert den Speicherverbrauch.

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

// 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;
}
882
Fedor

Ich habe Fedors Code ein wenig verbessert. Es macht im Grunde das Gleiche, aber ohne die (meiner Meinung nach) hässliche while-Schleife und es ergibt sich immer eine Potenz von zwei. Ein großes Lob an Fedor für die Herstellung der ursprünglichen Lösung, ich blieb stecken, bis ich seine fand, und dann konnte ich diese machen :)

 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;
}
364
Thomas Vervest

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 versuchen alle, die dieses Problem haben, Bilder mit 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, wenn Sie BitmapFactory.decodeXYZ() ausführen, sicher, dass Sie einen BitmapFactory.Options mit inPurgeable auf true (und vorzugsweise auch mit inInputShareable auf true).

2) NIEMALS Bitmap.createBitmap(width, height, Config.ARGB_8888) verwenden. Ich meine NIE! Ich hatte noch nie das Ding, das Speicherfehler nicht nach wenigen Durchgängen auslöst. Keine Menge von recycle(), System.gc(), was auch immer geholfen hat. Es hat immer eine Ausnahme ausgelöst. Die andere Möglichkeit, die tatsächlich funktioniert, besteht darin, ein Dummy-Bild in Ihren Drawables zu haben (oder eine andere Bitmap, die Sie in Schritt 1 oben dekodiert haben), diese nach Belieben neu zu skalieren und die resultierende Bitmap dann zu bearbeiten (z. B. an eine Zeichenfläche weiterzuleiten) für mehr Spaß). Verwenden Sie stattdessen: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Wenn Sie aus irgendeinem Grund die Brute-Force-Erstellungsmethode verwenden MÜSSEN, übergeben Sie mindestens Config.ARGB_4444.

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

227
Ephraim

Es ist ein bekannter Fehler , es liegt nicht an großen Dateien. Da Android die Drawables zwischenspeichert, ist nach Verwendung weniger Bilder nicht mehr genügend Speicher verfügbar. Aber ich habe einen alternativen Weg 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 abzurufen:

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

Ich hatte das gleiche Problem und habe es gelöst, indem ich die Funktionen BitmapFactory.decodeStream oder decodeFile vermieden und stattdessen BitmapFactory.decodeFileDescriptor verwendet habe.

decodeFileDescriptor ruft offenbar andere native Methoden auf als decodeStream/decodeFile.

Wie auch immer, das hat funktioniert (beachten Sie, dass ich einige Optionen hinzugefügt habe, wie oben beschrieben, aber das hat nicht den Unterschied gemacht. 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 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 "Images (Bitmaps) nicht standardmäßig Java, sondern über native Aufrufe zugewiesen werden. Die Zuweisungen erfolgen außerhalb des virtuellen Heaps, sind jedoch hat dagegen gezählt! "

74
Fraggle

Ich denke, der beste Weg, um das OutOfMemoryError zu vermeiden, besteht darin, sich ihm zu stellen und ihn zu verstehen.

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

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

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

  1. Bitmap wird im nativen Heap gespeichert, aber es wird automatisch Müll 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-Heap-Speichers wird nach der Vergrößerung niemals kleiner, selbst wenn der zugewiesene VM -Speicher verkleinert wird.

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

  5. Manuelles 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 Größe des nativen Heapspeichers wird nie kleiner, wird jedoch nicht für OOM gezählt, sodass Sie sich darüber keine Gedanken machen müssen.

Sprechen wir dann über SDK Starts from Honey Comb.

  1. Bitmap wird in VM Heap gespeichert. Der native Speicher wird für OOM nicht gezählt.

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

  3. Damit Sie mehr Arbeitsspeicher zum Erstellen von Bitmaps mit derselben Heap-Größenbeschränkung haben, wird OOM mit geringerer Wahrscheinlichkeit ausgelöst.

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 ausführt, die noch ausgeführt wurde, nachdem die Aktivität zerstört wurde, wird die Aktivität erst nach Abschluss der AsyncTask gelöscht.

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

Der Aufruf von AsyncTask.cancel (true) stoppt die Ausführung nicht, wenn die Task in einem IO Vorgang im Hintergrundthread blockiert wird.

Rückrufe sind auch anonyme innere Klassen. Wenn sie also in einer statischen Instanz Ihres Projekts gespeichert und nicht freigegeben werden, 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, ist ein Speicherverlust aufgetreten.

68
coocood

Ich habe in letzter Zeit viele Fragen zu OOM-Ausnahmen und Caching gesehen. Das Entwicklerhandbuch enthält einen wirklich guten Artikel zu diesem Thema, aber einige neigen dazu, es nicht in geeigneter Weise zu implementieren.

Aus diesem Grund habe ich eine Beispielanwendung geschrieben, die das Cachen 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 zu erhalten - das ist der einzige Code, der in API 1.6 nicht funktioniert.)
  • Android-Supportpaket

Screenshot

Eigenschaften:

  • Behält den Cache bei, wenn sich die Ausrichtung ändert ​​mit einem Singleton
  • Verwenden Sie ein Achtel des zugewiesenen Anwendungsspeichers für den Cache (ändern Sie ihn, wenn Sie möchten)
  • Große Bitmaps wird skaliert ​​(Sie können die maximalen Pixel definieren, die Sie zulassen möchten)
  • Steuert dass eine Internetverbindung verfügbar ist ​​vor dem Herunterladen der Bitmaps
  • Stellen Sie sicher, dass Sie nur eine Aufgabe pro Zeile instanziieren
  • Wenn Sie schleudern das ListView entfernt, werden die Bitmaps zwischen einfach nicht heruntergeladen

Dies beinhaltet nicht:

  • Festplatten-Caching. Dies sollte sowieso einfach zu implementieren sein - zeigen Sie einfach auf eine andere Aufgabe, die die Bitmaps von der Festplatte abruft

Beispielcode:

Die Bilder, die heruntergeladen werden, sind Bilder (75x75) von Flickr. Fügen Sie jedoch die Bild-URLs ein, die Sie verarbeiten möchten, und die Anwendung verkleinert sie, wenn sie das Maximum überschreitet. In dieser Anwendung befinden sich die URLs einfach in einem String-Array.

Das LruCache hat eine gute Möglichkeit, mit Bitmaps umzugehen. In dieser Anwendung habe ich jedoch eine Instanz von LruCache in eine andere Cache-Klasse eingefügt, die ich erstellt habe, um die Anwendung praktikabler zu machen.

Kritische Dinge in 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 Cache.Java-Datei bearbeiten müssen, es sei denn, Sie möchten die Datenträger-Zwischenspeicherung implementieren.

MainActivity.Javas kritisches Zeug:

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 aufgerufen. Es ist normalerweise keine gute Idee, Bilder dorthin 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 rowObject.mBitmapUrl bereits in einer Aufgabe befindet und startet keine weitere Aufgabe, wenn dies der Fall ist. Daher überschreiten wir höchstwahrscheinlich nicht die Einschränkung für die Arbeitswarteschlange aus dem Pool AsyncTask.

Herunterladen:

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


Letzte Worte:

Ich habe dies nun seit ein paar Wochen getestet, ich habe noch keine einzige OOM-Ausnahme. Ich habe dies auf dem Emulator, meinem Nexus One und meinem Nexus S getestet. Ich habe Bild-URLs getestet, die Bilder in HD-Qualität enthalten. Der einzige Engpass ist, 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, belegen sie gleichzeitig mehr Speicher und verursachen ein OOM. Aber das ist ohnehin nicht einmal eine ideale Situation, und es wird höchstwahrscheinlich nicht möglich sein, sie auf eine realisierbare Weise zu lösen.

Fehler in den Kommentaren melden! :-)

61
Wroclai

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

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

leider Wenn keines der oben genannten Verfahren funktioniert, fügen Sie dies zu Ihrer Manifest -Datei hinzu. Innerhalb des Anwendungs-Tags

 <application
         Android:largeHeap="true"
33
Himanshu Mori

Es scheint, dass dies ein sehr langwieriges Problem ist, mit vielen unterschiedlichen Erklärungen. Ich habe den Rat der beiden hier am häufigsten vorgestellten Antworten befolgt, aber keine dieser Antworten hat meine Probleme mit dem VM gelöst und behauptet, es könne sich nicht leisten, die Bytes für die Ausführung des zu verwenden. Dekodierung Teil des Prozesses. Nach einigem Graben habe ich erfahren, dass das eigentliche Problem hier der Entschlüsselungsprozess ist, der aus dem NATIVEN Haufen entfernt wird.

Siehe hier: BitmapFactory OOM macht mich verrückt

Das hat mich zu einem anderen Diskussionsthread geführt, in dem ich ein paar weitere Lösungen für dieses Problem gefunden habe. Zum einen müssen Sie System.gc(); manuell aufrufen, nachdem Ihr Bild angezeigt wurde. Dies führt jedoch dazu, dass Ihre App MEHR Speicher verwendet, 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; direkt nach o2.inSampleSize=scale; hinzugefügt.

Mehr zu diesem Thema hier: Ist das Limit des Speicherheaps nur 6M?

Jetzt, nachdem ich das alles gesagt habe, bin ich ein absoluter Idiot mit Java und Android. 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, den VM jetzt ohne Heap-Cache auszuführen. Der einzige Nachteil, den ich feststellen kann, ist, dass Sie Ihr zwischengespeichertes gezeichnetes Bild verwerfen. Das heißt, wenn Sie RECHTS zu diesem Bild zurückkehren, zeichnen Sie es jedes Mal neu. In dem Fall, wie meine Anwendung funktioniert, ist das kein wirkliches Problem. Ihr Kilometerstand kann variieren.

33
RayHaque

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

29
Arsalan

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

Ich habe eine viel effektivere Lösung, die keinerlei Skalierung erfordert. Dekodieren Sie Ihre Bitmap einfach nur einmal und speichern Sie sie dann in einer Map gegen ihren Namen. Rufen Sie dann einfach die Bitmap mit dem Namen ab und stellen Sie sie in der ImageView ein. Es ist nichts mehr zu tun.

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

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

Da sich Ihr Bild nun 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.

27
Parth Mehta

Hier gibt es zwei Probleme ....

  • Der Bitmap-Speicher befindet sich nicht im VM -Heap, sondern im nativen Heap - siehe BitmapFactory OOM macht mich verrückt
  • Die Garbage Collection für den nativen Heap ist langsamer als der VM -Heap. Sie müssen also ziemlich aggressiv bei der Ausführung von bitmap.recycle und bitmap = null vorgehen, wenn Sie die Aktivität onPause oder onDestroy durchlaufen
26
Torid

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());
    }
}
26
Luke Taylor

Keine der obigen Antworten hat für mich funktioniert, aber ich habe eine schrecklich hässliche Umgehung gefunden, die das Problem gelöst hat. Ich habe meinem Projekt ein sehr kleines 1x1-Pixel-Bild als Ressource hinzugefügt und es in 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.
19
Mike

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

Hier ist meine BitmapHelper Klasse das ist der OutOfMemoryError Beweis :-)

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 [email protected]
            // 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;
    }

}
19
Pascal

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.

18
Dobermaxx99

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;
    }
}
14
Emil Davtyan

In einer meiner Anwendungen muss ich entweder ein Bild von Camera/Gallery machen. Wenn der Benutzer auf ein Bild von der Kamera klickt (kann 2MP, 5MP oder 8MP sein), variiert die Bildgröße von kBs bis MBs. Wenn die Bildgröße weniger (oder bis zu 1-2 MB) über dem Code liegt, aber wenn ich ein Bild mit einer Größe von mehr als 4 MB oder 5 MB habe, wird OOM im Frame angezeigt :(

dann habe ich gearbeitet, um dieses Problem zu lösen und schließlich habe ich die folgende Verbesserung an Fedors (All Credit to Fedor für die Erstellung einer solchen Nizza-Lösung) Code 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 unter this

14
Rupesh Yadav

Ich bin vor ein paar Minuten auf dieses Problem gestoßen. Ich habe das Problem gelöst, indem ich meinen Listview-Adapter besser verwaltet habe. Ich dachte, es handele sich um ein Problem mit den Hunderten von 50x50px-Bildern, die ich verwendet habe. Es stellte sich heraus, dass ich versuchte, meine benutzerdefinierte Ansicht jedes Mal aufzublähen, wenn die Zeile angezeigt wurde. Durch einfaches Testen, um festzustellen, ob die Zeile aufgeblasen wurde, habe ich diesen Fehler behoben und verwende Hunderte von Bitmaps. Dies ist eigentlich für einen Spinner, aber der Basisadapter funktioniert für eine ListView gleichermaßen. 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);
    }
...
13
BajaBob

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.

12
Android Dev

Ich habe den ganzen Tag damit verbracht, diese Lösungen zu testen, und das Einzige, was für mich funktioniert hat, sind die oben beschriebenen Ansätze zum Abrufen des Images und zum manuellen Aufrufen des GC wenn ich meine App unter Last teste, wechsle ich zwischen Aktivitäten. Meine App enthält eine Liste von Miniaturbildern in einer Listenansicht in (sagen wir Aktivität A). Wenn Sie auf eines dieser Bilder klicken, gelangen Sie zu einer anderen Aktivität (sagen wir Aktivität B), in der ein Hauptbild für dieses Element angezeigt wird. Wenn ich zwischen den beiden Aktivitäten hin und her wechsle, erhalte ich irgendwann den OOM-Fehler und die App wird geschlossen.

Wenn ich die Listenansicht halb heruntergefahren hätte, wäre es abgestürzt.

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

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
12
Jesse

verwenden Sie diesen Code für jedes Bild, um es von der SdCard auszuwählen oder 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 den Bildpfad von ImageData_Path.get (img_pos) .getPath ().

11

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

11
matsoftware

Im Allgemeinen Android beträgt die Größe des Geräte-Heapspeichers nur 16 MB (variiert von Gerät/Betriebssystem, siehe Beitrag Heap-Größen ). Wenn Sie die Bilder laden und die Größe von 16 MB überschreiten, werden sie gelöscht Speicherausnahme, anstatt die Bitmap für zu verwenden, das Laden von Bildern von der SD-Karte oder von Ressourcen oder sogar vom Netzwerk, versuchen Sie, getImageUri zu verwenden, das Laden der Bitmap erfordert mehr Speicher oder Sie können die Bitmap auf null setzen, wenn Sie mit dieser Bitmap gearbeitet haben.

10
Mahesh

Ein solches OutofMemoryException kann nicht vollständig durch Aufrufen der System.gc() und so weiter gelöst werden.

Unter Bezugnahme auf Aktivitätslebenszyklus

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

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 eine niedrigere Auflösung zu resampeln und auf das Design der Galerien Bezug zu nehmen (ein kleines PNG-Bild und ein Originalbild).

10
Raju yourPepe

Mit diesem Code können Sie große Bitmaps von Drawable laden

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;
    }
}
10
vineet

Für alle hier aufgeführten Lösungen muss IMAGE_MAX_SIZE festgelegt werden. Dies schränkt Geräte mit leistungsfähigerer Hardware ein und wenn die Bildgröße zu gering ist, sieht es 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, darunter auch mit weniger leistungsstarken Geräten. Bei Verwendung eines leistungsstärkeren Geräts ist die Bildqualität besser.

Das Wesentliche dabei ist, den für die App auf einem bestimmten Gerät zugewiesenen maximalen Speicher zu berechnen und dann die Skalierung 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 von dieser Bitmap verwendeten maximalen Speicher auf 25% des maximal zugewiesenen Speichers festgelegt. Möglicherweise müssen Sie diesen an Ihre Anforderungen anpassen und sicherstellen, dass diese Bitmap bereinigt wird und nicht im Speicher verbleibt, wenn Sie sie nicht mehr verwenden. In der Regel verwende ich diesen Code, um die Bilddrehung (Quell- und Ziel-Bitmap) auszuführen, sodass meine App gleichzeitig 2 Bitmaps in den Speicher laden muss, und 25% geben mir einen guten Puffer, ohne dass beim Ausführen der Bilddrehung der Speicher ausgeht.

Hoffe das hilft jemandem da draußen ..

10
Bruce

Es scheint, dass die von Ihnen verwendeten Bilder sehr groß sind. Einige ältere Geräte stürzen ab, weil der Heap-Speicher voll ist. Versuchen Sie es mit älteren Geräten (Honey Comb oder ICS oder ein beliebiges Low-End-Modell)) benutzen Android:largeHeap="true" in der Manifestdatei unter dem Anwendungs-Tag oder verringern Sie die Größe der Bitmap mithilfe des folgenden Codes.

Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

sie können auch 4 oder 12 oder 16 eingeben, um die Bitmap-Größe zu verringern

5
user1602915
BitmapFactory.Options options = new Options();
options.inSampleSize = 32;
//img = BitmapFactory.decodeFile(imageids[position], options);

Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);
Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);
theImage.recycle();
theImage = null;
System.gc();
//ivlogdp.setImageBitmap(img);
Runtime.getRuntime().gc();
5
Exceptional

Ich habe Thomas Vervest's Ansatz ausprobiert, aber es gibt eine Skala von 1 für die Bildgröße 2592x1944 zurück, wenn IMAGE_MAX_SIZE 2048 ist.

Diese Version funktionierte für mich basierend auf allen anderen Kommentaren anderer:

private 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 ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // 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;
}
4
Eric Woodruff

Um OutOfMemory zu reparieren, versuchen Sie bitte diesen Code

public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {
                Bitmap bitmap = null;
                InputStream in = null;
                options.inSampleSize=4;
                try {
                    in = OpenHttpConnection(URL);
                    Log.e("In====>", in+"");
                    bitmap = BitmapFactory.decodeStream(in, null, options);
                    Log.e("URL====>", bitmap+"");

                    in.close();
                } catch (IOException e1) {
                }
                return bitmap;
            }

und

try {
                    BitmapFactory.Options bmOptions;
                    bmOptions = new BitmapFactory.Options();
                    bmOptions.inSampleSize = 1;
                    if(studentImage != null){
                        galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);    
                    }

                    galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());
                    Log.e("Image_Url==>",IMAGE_URL+studentImage+"");

                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
2
MukeshThakur

Hallo, bitte besuchen Sie den Link http://developer.Android.com/training/displaying-bitmaps/index.html

oder versuchen Sie einfach, eine Bitmap mit der angegebenen Funktion abzurufen

private Bitmap decodeBitmapFile (File f) {
    Bitmap bitmap = 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 ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // Decode with input-stram SampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options ();
        o2.inSampleSize = scale;
        fis = new FileInputStream (f);
        try {
            bitmap  = BitmapFactory.decodeStream (fis, null, o2);
        } finally {
            fis.close ();
        }
    } catch (IOException e) {
    }
    return bitmap ;
}
2
Jitesh Upadhyay

verwenden Sie dieses Konzept, dies wird Ihnen helfen, danach setzen Sie die Bildbitmap auf Bildansicht

public static Bitmap convertBitmap(String path)   {

        Bitmap bitmap=null;
        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) {
            e.printStackTrace();
        }

        try {
            if(fs!=null)
            {
                bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
            }
            } catch (IOException e) {

            e.printStackTrace();
        } finally{ 
            if(fs!=null) {
                try {
                    fs.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }

        return bitmap;
    }

Wenn Sie aus einem großen Bild ein kleines Bild mit einer Höhe und Breite wie 60 und 60 erstellen und die Listenansicht schnell durchblättern möchten, verwenden Sie dieses Konzept

public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
            int reqHeight) {

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

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

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(path, options);
        return bmp;
        }

    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {

        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) reqWidth);
             }
         }
         return inSampleSize;
        }

Ich hoffe es wird dir sehr helfen.

Sie können Hilfe von der Entwicklerseite erhalten hier

2
Sunil Kumar

Nachdem ich alle Antworten durchgesehen hatte, war ich überrascht, dass niemand die Glide-API für den Umgang mit Bildern erwähnte. Hervorragende Bibliothek, die die Komplexität des Bitmap-Managements zusammenfasst. Mit dieser Bibliothek und einer einzigen Codezeile können Sie Bilder schnell laden und deren Größe ändern.

     Glide.with(this).load(yourImageResource).into(imageview);

Sie können das Repository hier herunterladen: https://github.com/bumptech/glide

1
CBMurphy

Fügen Sie Ihrer manifest.xml-Datei die folgenden Zeilen hinzu:

<application

    Android:hardwareAccelerated="false"
    Android:largeHeap="true"

    <activity>
    </activity>

</application>
0
Deepya

Bewährte Methoden zur Vermeidung von Speicherverlusten oder OOM für Bitmaps

  1. Behalten Sie keine Bitmap-Verweise für langlebige Kontexte/Aktivitäten bei.
  2. Wenn Sie eine große Bitmap als Hintergrund oder etwas in Ihrer Anwendung verwenden, ziehen Sie nicht das gesamte Bild in den Hauptspeicher. Sie können die insample size -Eigenschaft von bitmap verwenden, um die für Ihren Bildschirm erforderliche Größe zu ermitteln.
  3. Saubere Bitmap-Referenz wird nicht mehr verwendet.
0
Sanjay Bhalani

Wenn du faul bist wie ich. Sie können die Picasso-Bibliothek zum Laden von Bildern verwenden. http://square.github.io/picasso/

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1); Picasso.with(context).load("file:///Android_asset/DvpvklR.png").into(imageView2); Picasso.with(context).load(new File(...)).into(imageView3);

0
Prakash

Ich habe den Decode File Descriptor verwendet, der für mich funktioniert hat:

 FileInputStream  fileInputStream = null;
        try {
            fileInputStream  = new FileInputStream(file);
             FileDescriptor fd = fileInputStream.getFD();
            Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
                    816);
            imageView.setImageBitmap(imageBitmap);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

Code zum Dekodieren der abgetasteten Bitmap aus dem Dateideskriptor:

 /**
     * Decode and sample down a bitmap from a file input stream to the requested width and height.
     *
     * @param fileDescriptor The file descriptor to read from
     * @param reqWidth       The requested width of the resulting bitmap
     * @param reqHeight      The requested height of the resulting bitmap
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
     * that are equal to or greater than the requested width and height
     */
    public static Bitmap decodeSampledBitmapFromDescriptor(
            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

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

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
    }

    /**
     * Calculate an inSampleSize for use in a {@link Android.graphics.BitmapFactory.Options} object when decoding
     * bitmaps using the decode* methods from {@link Android.graphics.BitmapFactory}. This implementation calculates
     * the closest inSampleSize that will result in the final decoded bitmap having a width and
     * height equal to or larger than the requested width and height. This implementation does not
     * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
     * results in a larger bitmap which isn't as useful for caching purposes.
     *
     * @param options   An options object with out* params already populated (run through a decode*
     *                  method with inJustDecodeBounds==true
     * @param reqWidth  The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return The value to be used for inSampleSize
     */
    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) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

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

            // This offers some additional logic in case the image has a strange
            // aspect ratio. For example, a panorama may have a much larger
            // width than height. In these cases the total pixels might still
            // end up being too large to fit comfortably in memory, so we should
            // be more aggressive with sample down the image (=larger inSampleSize).

            final float totalPixels = width * height;

            // Anything more than 2x the requested pixels we'll sample down further
            final float totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }
0
Sunil Kumar

Dadurch erhalten Sie eine geeignete Bitmap und reduzieren den Speicherverbrauch

Java

Bitmap bm = null;

BitmapFactory.Options bmpOption = new BitmapFactory.Options();
bmpOption.inJustDecodeBounds = true;

FileInputStream fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, bmpOption);
fis.close();

int scale = 1;

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

BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();
bmpOption2.inSampleSize = scale;
fis = new FileInputStream(file);
bm = BitmapFactory.decodeStream(fis, null, bmpOption2);
fis.close();

Kotlin

val bm:Bitmap = null
val bmpOption = BitmapFactory.Options()
bmpOption.inJustDecodeBounds = true
val fis = FileInputStream(file)
BitmapFactory.decodeStream(fis, null, bmpOption)
fis.close()
val scale = 1
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE)
{
  scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()
}
val bmpOption2 = BitmapFactory.Options()
bmpOption2.inSampleSize = scale
fis = FileInputStream(file)
bm = BitmapFactory.decodeStream(fis, null, bmpOption2)
fis.close()
0
Naimish Vinchhi