java.lang.OutOfMemoryError: rozmiar bitmapy przekracza budżet maszyny wirtualnej-Android

Opracowałem aplikację, która wykorzystuje wiele obrazów na Androida.

Aplikacja uruchamia się raz, wypełnia informacje na ekranie (Layouts, Listviews, Textviews, ImageViews, etc) i użytkownik odczytuje informacje.

Nie ma animacji, żadnych efektów specjalnych ani niczego, co wypełniłoby pamięć. Czasami rysownice mogą się zmieniać. Niektóre są zasoby Androida, a niektóre są pliki zapisane w folderze w SDCARD.

Następnie użytkownik kończy pracę (metoda onDestroy jest wykonywana i aplikacja pozostaje w pamięci przez VM), a następnie w pewnym momencie użytkownik wchodzi ponownie.

Za każdym razem, gdy użytkownik wchodzi do aplikacji, widzę, że pamięć rośnie coraz bardziej, dopóki użytkownik nie otrzyma java.lang.OutOfMemoryError.

Jaki jest najlepszy / poprawny sposób obsługi wielu obrazów?

Czy powinienem umieścić je w metodach statycznych, aby nie były ładowane przez cały czas? Czy muszę czyścić układ lub obrazy użyte w układzie w specjalny sposób?

Author: Ironman, 2009-12-22

13 answers

Wygląda na to, że masz wyciek pamięci. Problem polega na tym, że nie obsługujesz wielu obrazów, ponieważ twoje obrazy nie są dealokowane, gdy Twoja aktywność zostanie zniszczona.

Trudno powiedzieć, dlaczego tak jest bez spojrzenia na Twój kod. Jednak ten artykuł zawiera kilka wskazówek, które mogą pomóc:

Http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

W szczególności używanie zmiennych statycznych może pogorszyć sytuację, a nie poprawić. Możesz potrzebować aby dodać kod, który usuwa wywołania zwrotne, gdy aplikacja przerysowuje -- ale znowu, nie ma tu wystarczająco dużo informacji, aby powiedzieć na pewno.

 70
Author: Trevor Johns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-03 22:13:10

Jednym z najczęstszych błędów, które znalazłem przy tworzeniu aplikacji na Androida jest " java.lang.Błąd OutOfMemoryError: Rozmiar bitmapy przekracza budżet maszyny wirtualnej". Ten błąd często spotykałem przy aktywnościach wykorzystujących wiele bitmap po zmianie orientacji: aktywność jest niszczona, tworzona ponownie, a układy są "napompowane" z XML zużywającego pamięć VM dostępną dla bitmap.

Bitmapy w poprzednim układzie aktywności nie są prawidłowo przydzielane przez garbage collector, ponieważ mają przekreślone odniesienia do ich działalności. Po wielu eksperymentach znalazłem całkiem dobre rozwiązanie tego problemu.

Najpierw ustaw atrybut " id " w widoku nadrzędnym układu XML:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/RootView"
     >
     ...

Następnie, w metodzie onDestroy() Twojej aktywności, wywołaj metodę unbindDrawables() przekazującą odwołanie do widoku nadrzędnego, a następnie wykonaj System.gc().

    @Override
    protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
    }

    private void unbindDrawables(View view) {
        if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
        ((ViewGroup) view).removeAllViews();
        }
    }

Ta metoda unbindDrawables() bada drzewo widoku rekurencyjnie i:

  1. usuwa wywołania zwrotne na wszystkich rysunkach tła
  2. usuwa dzieci na każdym viewgroup
 96
Author: hp.android,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-22 12:09:27

Aby uniknąć tego problemu można użyć natywnej metody Bitmap.recycle() przed null-ING Bitmap object (lub ustawienie innej wartości). Przykład:

public final void setMyBitmap(Bitmap bitmap) {
  if (this.myBitmap != null) {
    this.myBitmap.recycle();
  }
  this.myBitmap = bitmap;
}

A następnie możesz zmienić myBitmap W / o wywołanie System.gc() Jak:

setMyBitmap(null);    
setMyBitmap(anotherBitmap);
 10
Author: ddmytrenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-04 10:49:22

Napotkałem dokładnie ten problem. Sterta jest dość mała, więc te obrazy mogą wymknąć się spod kontroli dość szybko w odniesieniu do pamięci. Jednym ze sposobów jest podpowiedź do zbierania pamięci na bitmapie przez wywołanie jej metody recyklingu.

Ponadto metoda onDestroy nie jest gwarantowana. Możesz przenieść tę logikę / oczyścić do aktywności onPause. Aby uzyskać więcej informacji, zapoznaj się z diagramem/tabelą cyklu aktywności na tej stronie.

 7
Author: Donn Felker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-22 22:16:10

To wyjaśnienie może pomóc: http://code.google.com/p/android/issues/detail?id=8488#c80

"Szybkie Porady:

1) nigdy nie dzwoń do systemu.gc () To zostało propagowane jako poprawka tutaj, i to nie działa. Nie rób tego. Jeśli zauważyłeś w moim wyjaśnieniu, przed uzyskaniem OutOfMemoryError, JVM już uruchamia garbage collection, więc nie ma powodu, aby zrobić to ponownie (jego spowolnienie programu w dół). Robienie jednego pod koniec swojej aktywności to tylko tuszowanie problem. Może to spowodować szybsze umieszczanie bitmapy w kolejce finalizera, ale nie ma powodu, dla którego nie można było po prostu nazwać recycle na każdej bitmapie.

2) zawsze wywołaj recycle () na bitmapach, których już nie potrzebujesz. Co najmniej, w onDestroy swojej aktywności Przejrzyj i poddaj recyklingowi wszystkie bitmapy, których używałeś. Ponadto, jeśli chcesz, aby instancje bitmapy były zbierane ze sterty dalvik szybciej, nie zaszkodzi wyczyścić żadnych odniesień do bitmapy.

3) Wywołanie recycle (), a następnie System.gc () nadal może nie usuwać bitmapy ze sterty Dalvik. Nie przejmuj się tym. recycle() wykonało swoje zadanie i uwolniło natywną pamięć, wystarczy trochę czasu, aby przejść przez kroki, które wcześniej opisałem, aby faktycznie usunąć bitmapę ze sterty Dalvik. To nic wielkiego, ponieważ duża część pamięci natywnej jest już wolna!

4) zawsze zakładaj, że jest błąd w frameworku last. Dalvik robi dokładnie to, co powinien. To może nie być to, czego oczekujesz lub czego chcesz, ale to, jak to działa. "

 7
Author: Aron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-11 00:52:10

Miałem dokładnie ten sam problem. Po kilku testach odkryłem, że ten błąd pojawia się dla dużego skalowania obrazu. Zmniejszyłem skalowanie obrazu i problem zniknął.

P. S. na początku próbowałem zmniejszyć rozmiar obrazu bez skalowania obrazu w dół. To nie zatrzymało błędu.

 5
Author: GSree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-20 07:56:19

Następujące punkty bardzo mi pomogły. Mogą być też inne punkty, ale są one bardzo ważne:

  1. Użyj kontekstu aplikacji (zamiast aktywności.to) tam, gdzie jest to możliwe.
  2. Stop and release your threads in onpause () method of activity
  3. Release your views / callbacks in onDestroy () method of activity
 5
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-08 06:03:39

Sugeruję wygodny sposób rozwiązania tego problemu. Wystarczy przypisać wartość atrybutu "android: configChanges", jak następuje w Mainfest.xml dla Twojej błędnej aktywności. tak:

<activity android:name=".main.MainActivity"
              android:label="mainActivity"
              android:configChanges="orientation|keyboardHidden|navigation">
</activity>

Pierwsze rozwiązanie, które dałem, naprawdę zmniejszyło częstotliwość błędu OOM do niskiego poziomu. Ale to nie rozwiązało problemu całkowicie. A potem Dam 2 Rozwiązanie:

Jak szczegółowo OOM, użyłem zbyt dużo pamięci runtime. Więc zmniejszam Rozmiar obrazu w ~ / res / drawable of mój projekt. Takie jak zbyt kwalifikowane zdjęcie, które ma rozdzielczość 128x128, może zostać zmienione na 64x64, co również będzie odpowiednie dla mojej aplikacji. I po tym, jak zrobiłem to ze stertą zdjęć, błąd OOM nie pojawia się ponownie.

 4
Author: Ranger Way,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-22 14:04:57

Ja też jestem sfrustrowany błędem outofmemory. I tak, ja też zauważyłem, że ten błąd pojawia się często podczas skalowania obrazów. Na początku próbowałem tworzyć rozmiary obrazów dla wszystkich gęstości, ale okazało się, że znacznie zwiększyło to Rozmiar mojej aplikacji. Więc teraz używam tylko jednego obrazu dla wszystkich gęstości i skaluję moje obrazy.

Moja aplikacja wyrzuciłaby błąd outofmemory za każdym razem, gdy użytkownik przechodził z jednej aktywności do drugiej. Ustawianie moich rysunków na null i wywołanie systemu.gc () nie działa, podobnie jak recykling moich bitmapDrawables za pomocą getBitMap ().recycle (). Android nadal wyrzucał błąd outofmemory przy pierwszym podejściu i wysyłał komunikat o błędzie canvas za każdym razem, gdy próbował użyć przetworzonej bitmapy przy drugim podejściu.

Podjąłem nawet trzecie podejście. Ustawiłem wszystkie widoki na null, a tło na Czarne. Czyszczę to w mojej metodzie onStop (). Jest to metoda, która jest wywoływana, gdy tylko aktywność nie jest już widoczna. Jeśli zrobisz to w w metodzie onPause () użytkownicy zobaczą czarne tło. Nie idealnie. Jeśli chodzi o wykonanie tego w metodzie onDestroy (), nie ma gwarancji, że zostanie wywołane.

Aby zapobiec pojawieniu się czarnego ekranu, jeśli użytkownik naciśnie przycisk Wstecz na urządzeniu, przeładowuję aktywność w metodzie onRestart (), wywołując metody startActivity (getIntent ()), a następnie finish ().

Uwaga: zmiana tła na czarne nie jest konieczna.

 3
Author: subduedjoy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-23 21:58:21

BitmapFactory.metody decode*, omówione w lekcji load Large Bitmaps Efficiently , nie powinny być wykonywane w głównym wątku interfejsu użytkownika, jeśli dane źródłowe są odczytywane z dysku lub lokalizacji sieciowej (lub w rzeczywistości dowolnego źródła innego niż pamięć). Czas ładowania tych danych jest nieprzewidywalny i zależy od wielu czynników (szybkość odczytu z dysku lub sieci, Rozmiar obrazu, moc procesora itp.). Jeśli jedno z tych zadań zablokuje wątek interfejsu użytkownika, system oznaczy aplikację jako brak responsywności i użytkownik ma możliwość jej zamknięcia (zobacz Projektowanie responsywności, aby uzyskać więcej informacji).

 1
Author: Li3ro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-07 13:06:56

Próbowałem wszystkiego, co znalazłem w Internecie i żaden z nich nie zadziałał. System Połączeń.gc () tylko obniża prędkość aplikacji. Recykling bitmap w onDestroy też nie zadziałał.

Jedyną rzeczą, która teraz działa, jest posiadanie statycznej listy wszystkich bitmap, aby bitmapy przetrwały po ponownym uruchomieniu. I po prostu Użyj zapisanych bitmap zamiast tworzyć nowe za każdym razem, gdy aktywność zostanie ponownie uruchomiona.

W moim przypadku kod wygląda tak:

private static BitmapDrawable currentBGDrawable;

if (new File(uriString).exists()) {
    if (!uriString.equals(currentBGUri)) {
        freeBackground();
        bg = BitmapFactory.decodeFile(uriString);

        currentBGUri = uriString;
        bgDrawable = new BitmapDrawable(bg);
        currentBGDrawable = bgDrawable;
    } else {
        bgDrawable = currentBGDrawable;
    }
}
 0
Author: HarryHao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-18 06:40:34

Miałem ten sam problem tylko z przełączaniem obrazów tła o rozsądnych rozmiarach. Mam lepsze wyniki z ustawieniem ImageView NA null przed umieszczeniem nowego obrazu.

ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
 0
Author: Gunnar Bernstein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-20 23:22:45

FWIW, oto lekki bitmap-cache, który kodowałem i używałem od kilku miesięcy. To nie wszystko, więc przeczytaj kod zanim go użyjesz.

/**
 * Lightweight cache for Bitmap objects. 
 * 
 * There is no thread-safety built into this class. 
 * 
 * Note: you may wish to create bitmaps using the application-context, rather than the activity-context. 
 * I believe the activity-context has a reference to the Activity object. 
 * So for as long as the bitmap exists, it will have an indirect link to the activity, 
 * and prevent the garbaage collector from disposing the activity object, leading to memory leaks. 
 */
public class BitmapCache { 

    private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();  

    private StringBuilder sb = new StringBuilder(); 

    public BitmapCache() { 
    } 

    /**
     * A Bitmap with the given width and height will be returned. 
     * It is removed from the cache. 
     * 
     * An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.  
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public Bitmap get(int width, int height, Bitmap.Config config) { 
        String key = getKey(width, height, config); 
        ArrayList<Bitmap> list = getList(key); 
        int listSize = list.size();
        if (listSize>0) { 
            return list.remove(listSize-1); 
        } else { 
            try { 
                return Bitmap.createBitmap(width, height, config);
            } catch (RuntimeException e) { 
                // TODO: Test appendHockeyApp() works. 
                App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height); 
                throw e ; 
            }
        }
    }

    /**
     * Puts a Bitmap object into the cache. 
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public void put(Bitmap bitmap) { 
        if (bitmap==null) return ; 
        String key = getKey(bitmap); 
        ArrayList<Bitmap> list = getList(key); 
        list.add(bitmap); 
    }

    private ArrayList<Bitmap> getList(String key) {
        ArrayList<Bitmap> list = hashtable.get(key);
        if (list==null) { 
            list = new ArrayList<Bitmap>(); 
            hashtable.put(key, list); 
        }
        return list;
    } 

    private String getKey(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        Config config = bitmap.getConfig();
        return getKey(width, height, config);
    }

    private String getKey(int width, int height, Config config) {
        sb.setLength(0); 
        sb.append(width); 
        sb.append("x"); 
        sb.append(height); 
        sb.append(" "); 
        switch (config) {
        case ALPHA_8:
            sb.append("ALPHA_8"); 
            break;
        case ARGB_4444:
            sb.append("ARGB_4444"); 
            break;
        case ARGB_8888:
            sb.append("ARGB_8888"); 
            break;
        case RGB_565:
            sb.append("RGB_565"); 
            break;
        default:
            sb.append("unknown"); 
            break; 
        }
        return sb.toString();
    }

}
 0
Author: Guy Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-10 04:51:06