So lösen Sie Probleme mit java.lang.OutOfMemoryError in Android

70

Obwohl ich ein sehr kleines Bild in einem Zeichenordner habe, erhalte ich diesen Fehler von den Benutzern. Und ich verwende keine Bitmap-Funktion im Code. Zumindest absichtlich :)

java.lang.OutOfMemoryError
    at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:683)
    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:513)
    at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:889)
    at android.content.res.Resources.loadDrawable(Resources.java:3436)
    at android.content.res.Resources.getDrawable(Resources.java:1909)
    at android.view.View.setBackgroundResource(View.java:16251)
    at com.autkusoytas.bilbakalim.SoruEkrani.cevapSecimi(SoruEkrani.java:666)
    at com.autkusoytas.bilbakalim.SoruEkrani$9$1.run(SoruEkrani.java:862)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5602)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
    at dalvik.system.NativeStart.main(Native Method)

Laut diesem stackTrace erhalte ich diesen Fehler in dieser Zeile ('tv' ist eine Textansicht):

tv.setBackgroundResource(R.drawable.yanlis);

Worin besteht das Problem? Wenn Sie weitere Informationen zum Code benötigen, kann ich diese hinzufügen. Vielen Dank!

Utku Soytaş
quelle
Versuchen Sie dies: stackoverflow.com/questions/19558713/…
Slampy
Nein, aber wie gesagt, ich habe ein sehr kleines Bild (max. 600 KB). Ich denke, das ist für ein größeres Bild. @ 2Dee
Utku Soytaş

Antworten:

146

Sie können die Größe des Heapspeichers nicht dynamisch erhöhen, aber Sie können die Verwendung von Heap mithilfe von anfordern.

android: largeHeap = "wahr"

In manifest.xmlkönnen Sie in Ihrem Manifest diese Zeilen hinzufügen, die in bestimmten Situationen funktionieren.

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

Gibt an, ob die Prozesse Ihrer Anwendung mit einem großen Dalvik-Heap erstellt werden sollen. Dies gilt für alle für die Anwendung erstellten Prozesse. Dies gilt nur für die erste in einen Prozess geladene Anwendung. Wenn Sie eine gemeinsam genutzte Benutzer-ID verwenden, um mehreren Anwendungen die Verwendung eines Prozesses zu ermöglichen, müssen alle diese Option konsistent verwenden, da sonst unvorhersehbare Ergebnisse erzielt werden. Die meisten Apps sollten dies nicht benötigen und sich stattdessen darauf konzentrieren, die Gesamtspeicherauslastung zu reduzieren, um die Leistung zu verbessern. Das Aktivieren dieser Option garantiert auch keine feste Erhöhung des verfügbaren Speichers, da einige Geräte durch ihren insgesamt verfügbaren Speicher eingeschränkt sind.


Verwenden Sie die Methoden getMemoryClass()oder , um die verfügbare Speichergröße zur Laufzeit abzufragen getLargeMemoryClass().

Wenn immer noch Probleme auftreten, sollte dies auch funktionieren

 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inSampleSize = 8;
 mBitmapInsurance = BitmapFactory.decodeFile(mCurrentPhotoPath,options);

Wenn ein Wert> 1 festgelegt ist, wird der Decoder aufgefordert, das Originalbild zu unterabtasten und ein kleineres Bild zurückzugeben, um Speicherplatz zu sparen.

Dies ist die optimale Verwendung von BitmapFactory.Options.inSampleSize im Hinblick auf die Geschwindigkeit der Bildanzeige. In der Dokumentation wird die Verwendung von Werten mit einer Potenz von 2 erwähnt, daher arbeite ich mit 2, 4, 8, 16 usw.

Gehen wir näher auf die Bildabtastung ein:

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 128 Pixeln 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 2100 x 1500 Pixel, 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 .


Ich fand diesen Code auch interessant:

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, o);
    in.close();

    int scale = 1;
    while ((o.outWidth * o.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) {
       scale++;
    }
    Log.d(TAG, "scale = " + scale + ", orig-width: " + o.outWidth + ", 
       orig-height: " + o.outHeight);

    Bitmap bitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        o = new BitmapFactory.Options();
        o.inSampleSize = scale;
        bitmap = BitmapFactory.decodeStream(in, null, o);

        // resize to desired dimensions
        int height = bitmap.getHeight();
        int width = bitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) x, 
           (int) y, true);
        bitmap.recycle();
        bitmap = scaledBitmap;

        System.gc();
    } else {
        bitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "bitmap size - width: " +bitmap.getWidth() + ", height: " + 
       bitmap.getHeight());
    return bitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}

So verwalten Sie den Speicher Ihrer App: Link


Es ist keine gute Idee, android:largeHeap="true" hier den Auszug aus Google zu verwenden, der dies erklärt:

Die Möglichkeit, einen großen Heap anzufordern, ist jedoch nur für eine kleine Anzahl von Apps vorgesehen, die die Notwendigkeit rechtfertigen, mehr RAM zu verbrauchen (z. B. eine große Fotobearbeitungs-App). Fordern Sie niemals einen großen Heap an, nur weil Ihnen der Speicher ausgeht und Sie eine schnelle Lösung benötigen. Sie sollten ihn nur verwenden, wenn Sie genau wissen, wo Ihr gesamter Speicher zugewiesen wird und warum er beibehalten werden muss. Selbst wenn Sie sicher sind, dass Ihre App den großen Haufen rechtfertigen kann, sollten Sie es vermeiden, ihn so weit wie möglich anzufordern. Die Verwendung des zusätzlichen Speichers wird sich zunehmend nachteilig auf die allgemeine Benutzererfahrung auswirken, da die Speicherbereinigung länger dauert und die Systemleistung langsamer sein kann, wenn Aufgaben gewechselt oder andere allgemeine Vorgänge ausgeführt werden.

Nachdem ich exkret gearbeitet habe, out of memory errorswürde ich sagen, dass es keine Sünde ist, dies dem Manifest hinzuzufügen, um das Problem zu vermeiden


Überprüfen des App-Verhaltens auf der Android Runtime (ART)

Die Android-Laufzeit (ART) ist die Standardlaufzeit für Geräte mit Android 5.0 (API-Stufe 21) und höher. Diese Laufzeit bietet eine Reihe von Funktionen, die die Leistung und Glätte der Android-Plattform und -Apps verbessern. Weitere Informationen zu den neuen Funktionen von ART finden Sie unter Einführung in ART .

Einige Techniken, die auf Dalvik funktionieren, funktionieren jedoch nicht auf ART. In diesem Dokument erfahren Sie, worauf Sie bei der Migration einer vorhandenen App achten müssen, um mit ART kompatibel zu sein. Die meisten Apps sollten nur funktionieren, wenn sie mit ART ausgeführt werden.


Beheben von Problemen mit der Garbage Collection (GC)

Unter Dalvik finden es Apps häufig nützlich, System.gc () explizit aufzurufen, um die Garbage Collection (GC) aufzufordern. Dies sollte bei ART weitaus weniger erforderlich sein, insbesondere wenn Sie die Garbage Collection aufrufen, um Vorkommen vom Typ GC_FOR_ALLOC zu verhindern oder die Fragmentierung zu verringern. Sie können überprüfen, welche Laufzeit verwendet wird, indem Sie System.getProperty ("java.vm.version") aufrufen. Wenn ART verwendet wird, beträgt der Wert der Eigenschaft "2.0.0" oder höher.

Darüber hinaus wird im Android Open-Source Project (AOSP) ein komprimierender Garbage Collector entwickelt, um die Speicherverwaltung zu verbessern. Aus diesem Grund sollten Sie die Verwendung von Techniken vermeiden, die mit der Komprimierung von GC nicht kompatibel sind (z. B. das Speichern von Zeigern auf Objektinstanzdaten). Dies ist besonders wichtig für Apps, die das Java Native Interface (JNI) verwenden. Weitere Informationen finden Sie unter Verhindern von JNI-Problemen.


Verhindern von JNI-Problemen

Das JNI von ART ist etwas strenger als das von Dalvik. Es ist eine besonders gute Idee, den CheckJNI-Modus zu verwenden, um häufige Probleme zu erkennen. Wenn Ihre App C / C ++ - Code verwendet, sollten Sie den folgenden Artikel lesen:


Sie können auch nativen Speicher ( NDK & JNI ) verwenden, sodass Sie die Beschränkung der Heap-Größe tatsächlich umgehen.

Hier sind einige Beiträge dazu:

und hier ist eine Bibliothek dafür gemacht:

Maveň ň
quelle
3
Dein Android: largeHeap = "wahre" Tricks helfen mir sehr. Vielen Dank, Sir
Md. Sajedul Karim
@ Fakher lesen Sie den Sir des anderen Links ... Hier sind einige Beiträge dazu: - stackoverflow.com/questions/17900732/… - stackoverflow.com/questions/18250951/… und hier ist eine Bibliothek, die dafür gemacht wurde: - github.com / AndroidDeveloperLB / AndroidJniBitmapOperations
Maveň ツ
2
Gut gesagt Zeilen Gattsu: "For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView."
Sam
1
Dies ist eine der vollständigsten und besten Antworten, die ich gelesen habe
UserID0908
5

Ich sehe nur zwei Möglichkeiten:

  1. Sie haben Speicherlecks in Ihrer Anwendung.
  2. Geräte haben nicht genügend Speicher, wenn Sie Ihre Anwendung ausführen.
Cativail
quelle
5
Wie kann ich Speicherlecks in meiner App beheben?
Utku Soytaş
2
Durch Erkennen der Orte, an denen viele Daten generiert und gespeichert werden (normalerweise liegt dies am Problem, dass die Bilder nicht freigegeben werden). Möglicherweise finden Sie diese hilfreiche stackoverflow.com/questions/2298208/…
Cativail
4

Sie sollten einen LRU-Cache-Manager implementieren, wenn Sie mit Bitmap arbeiten

http://developer.android.com/reference/android/util/LruCache.html http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html Wann sollte ich eine Bitmap mit LRUCache recyceln?

ODER

Verwenden Sie eine Ebenenbibliothek wie Universal Image Loader:

https://github.com/nostra13/Android-Universal-Image-Loader

EDIT:

Wenn ich mich jetzt mit Bildern und meistens mit Bitmap beschäftige, verwende ich Glide, mit dem Sie ein Glide-Modul und einen LRUCache konfigurieren können

https://github.com/bumptech/glide

Android
quelle
4

Einige Hinweise zur Behandlung solcher Fehler / Ausnahmen für Android Apps:

  1. Aktivitäten und Anwendungen haben Methoden wie:

    • onLowMemory
    • onTrimMemory Behandeln Sie diese Methoden, um die Speichernutzung zu überwachen.
  2. Für das Tag im Manifest kann das Attribut 'largeHeap' auf TRUE gesetzt sein, wodurch mehr Heap für die App-Sandbox angefordert wird.

  3. Verwalten des In-Memory-Caching und des Festplatten-Caching:

    • Bilder und andere Daten könnten während der Ausführung der App im Speicher zwischengespeichert worden sein (lokal in Aktivitäten / Fragmenten und global). sollte verwaltet oder entfernt werden.
  4. Verwendung von WeakReference, SoftReference zur Erstellung von Java-Instanzen, speziell für Dateien.

  5. Wenn so viele Bilder vorhanden sind, verwenden Sie die richtige Bibliothek / Datenstruktur, die den Speicher verwalten kann, verwenden Sie die Abtastung der geladenen Bilder und übernehmen Sie das Festplatten-Caching.

  6. Behandeln Sie die OutOfMemory-Ausnahme

  7. Befolgen Sie die Best Practices für die Codierung

    • Gedächtnisverlust (Halten Sie nicht alles mit starkem Bezug)
  8. Minimieren Sie den Aktivitätsstapel, z. B. die Anzahl der Aktivitäten im Stapel (halten Sie nicht alles auf Kontext / Aktivität)

    • Der Kontext ist sinnvoll. Diese Daten / Instanzen, die nicht außerhalb des Gültigkeitsbereichs (Aktivität und Fragmente) benötigt werden, halten sie in einem geeigneten Kontext statt in einer globalen Referenz.
  9. Minimieren Sie die Verwendung von Statik und vielen weiteren Singletons.

  10. Achten Sie auf die Grundspeicher des Betriebssystems

    • Probleme mit der Speicherfragmentierung
  11. Rufen Sie GC.Collect () manchmal manuell auf, wenn Sie sicher sind, dass kein In-Memory-Caching mehr erforderlich ist.

Sandipkumar Savani
quelle
Können Sie bitte Code teilen, wie OutOfMemory Ausnahme zu behandeln
Chetan Chaudhari
4

Wenn Sie diesen Fehler java.lang.OutOfMemoryError erhalten, ist dies das häufigste Problem, das in Android auftritt. Dieser Fehler wird von der Java Virtual Machine (JVM) ausgelöst, wenn ein Objekt aufgrund fehlenden Speicherplatzes nicht zugewiesen werden kann.

Versuchen Sie dies android:hardwareAccelerated="false" , android:largeHeap="true"in Ihrer manifest.xml-Datei unter Anwendung wie folgt:

<application
  android:name=".MyApplication"
  android:allowBackup="true"
  android:icon="@mipmap/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme"
  android:hardwareAccelerated="false"
  android:largeHeap="true" />
Gaurav Lambole
quelle
Warum sollte ich machen android:hardwareAccelerated="false"? Wenn ich es wahr mache, was passiert dann?
Prinz Dholakiya
4
Wenn Sie hardwareAccelerated = "false" schreiben, erhalten Sie in Ihrem Projekt keine Erhöhung für Cardview. Denken Sie darüber nach?
Dileep krishnan
0

android:largeHeap="true" habe den Fehler nicht behoben

In meinem Fall wurde dieser Fehler angezeigt, nachdem ich dem Ordner "Drawable" ein Symbol / Bild hinzugefügt hatte, indem ich SVG in einen Vektor konvertierte. Gehen Sie einfach zur XML-Symboldatei und legen Sie kleine Zahlen für die Breite und Höhe fest

android:width="24dp"
android:height="24dp"
android:viewportWidth="3033"
android:viewportHeight="3033"
Ribaz
quelle