Dziwny problem braku pamięci podczas ładowania obrazu do obiektu Bitmap


1288

Mam widok listy z kilkoma przyciskami obrazów w każdym rzędzie. Kliknięcie wiersza listy uruchamia nową aktywność. Musiałem zbudować własne zakładki z powodu problemu z układem aparatu. Aktywnością uruchamianą dla wyniku jest mapa. Jeśli kliknę mój przycisk, aby uruchomić podgląd obrazu (załaduj obraz z karty SD), aplikacja powróci z działania z powrotem do listviewdziałania do modułu obsługi wyników, aby ponownie uruchomić moją nową aktywność, która jest niczym więcej niż widżetem obrazu.

Podgląd obrazu w widoku listy odbywa się za pomocą kursora i ListAdapter. To sprawia, że ​​jest to dość proste, ale nie jestem pewien, jak mogę umieścić obraz o zmienionym rozmiarze (tj. Mniejszy rozmiar bitowy, a nie piksel jak srcprzycisk obrazu w locie. Więc po prostu zmieniłem rozmiar obrazu, który wyszedł z aparatu telefonu.

Problem polega na tym, że pojawia się błąd braku pamięci, gdy próbuje wrócić i ponownie uruchomić 2. działanie.

  • Czy istnieje sposób, w jaki mogę łatwo zbudować adapter listy rząd po rzędzie, w którym mogę zmieniać rozmiar w locie ( nieco mądrzej )?

Byłoby to preferowane, ponieważ muszę wprowadzić pewne zmiany we właściwościach widgetów / elementów w każdym rzędzie, ponieważ nie mogę wybrać wiersza z ekranem dotykowym z powodu problemu z ostrością. ( Mogę użyć kulki. )

  • Wiem, że mogę wykonać zmianę rozmiaru poza pasmem i zapisać mój obraz, ale to nie jest tak naprawdę to, co chcę zrobić, ale jakiś przykładowy kod do tego byłby miły.

Gdy tylko wyłączyłem obraz w widoku listy, znów działał dobrze.

FYI: Tak to robiłem:

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

Gdzie R.id.imagefilenamejest ButtonImage.

Oto mój 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 

Mam również nowy błąd podczas wyświetlania obrazu:

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

8
Rozwiązałem ten problem unikając Bitmap.decodeStream lub decodeFile i używając metody BitmapFactory.decodeFileDescriptor.
Fraggle,

1
Kilka tygodni temu napotkałem również podobny problem i rozwiązałem go, zmniejszając obrazy do optymalnego punktu. Napisałem kompletne podejście w moim blogu codingjunkiesforum.wordpress.com/2014/06/12/... i przesłałem kompletny przykładowy projekt z podatnym kodem OOM vs OOM Proof code pod adresem: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat

5
Przyjęta odpowiedź na to pytanie jest omawiana w meta
przedłużeniu w

4
Dzieje się tak, gdy nie czytasz przewodników dla programistów Androida
Pedro Varela

2
Dzieje się tak z powodu złej architektury Androida. Powinien sam zmieniać rozmiar obrazów, podobnie jak iOS i UWP. Nie muszę tego robić sam. Programiści Androida przyzwyczajają się do tego piekła i uważają, że działa tak, jak powinien.
Odmowa dostępu

Odpowiedzi:


650

Android szkolenie klasy „ Wyświetlanie bitmapy Skutecznie ”, oferuje kilka świetnych informacji dla zrozumienia i radzenia sobie z wyjątkiem java.lang.OutOfMemoryError: bitmap size exceeds VM budgetpodczas ładowania bitmapy.


Przeczytaj wymiary i typ mapy bitowej

BitmapFactoryKlasy zapewnia kilka sposobów dekodowania ( decodeByteArray(), decodeFile(), decodeResource(), itd.) Do wykonywania Bitmapz różnych źródeł. Wybierz najbardziej odpowiednią metodę dekodowania na podstawie źródła danych obrazu. Te metody próbują przydzielić pamięć dla skonstruowanej mapy bitowej i dlatego mogą łatwo doprowadzić do OutOfMemorywyjątku. Każdy typ metody dekodowania ma dodatkowe podpisy, które pozwalają określić opcje dekodowania za pośrednictwem BitmapFactory.Optionsklasy. Ustawienie inJustDecodeBoundswłaściwości na truepodczas dekodowania pozwala uniknąć przydziału pamięci, powrót nulldo obiektu bitmapy, ale ustawienie outWidth, outHeighti outMimeType. Ta technika pozwala odczytać wymiary i typ danych obrazu przed budową (i alokacją pamięci) mapy bitowej.

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;

Aby uniknąć java.lang.OutOfMemorywyjątków, sprawdź wymiary bitmapy przed jej zdekodowaniem, chyba że absolutnie ufasz źródłu, że dostarczy ci przewidywanych rozmiarów danych obrazu, które wygodnie mieszczą się w dostępnej pamięci.


Załaduj zmniejszoną wersję do pamięci

Teraz, gdy wymiary obrazu są znane, można ich użyć, aby zdecydować, czy pełny obraz powinien zostać załadowany do pamięci, czy zamiast tego powinna zostać załadowana wersja podpróbkowana. Oto kilka czynników, które należy wziąć pod uwagę:

  • Szacowane użycie pamięci podczas ładowania pełnego obrazu do pamięci.
  • Ilość pamięci, którą chcesz zobowiązać się do załadowania tego obrazu, biorąc pod uwagę inne wymagania dotyczące pamięci aplikacji.
  • Wymiary docelowego komponentu ImageView lub interfejsu użytkownika, do którego ma zostać załadowany obraz.
  • Rozmiar ekranu i gęstość bieżącego urządzenia.

Na przykład nie warto ładować do pamięci obrazu o wymiarach 1024 x 768 pikseli, jeśli ostatecznie zostanie on wyświetlony w miniaturze 128 x 96 pikseli ImageView.

Aby nakazać dekoderowi podpróbkowanie obrazu, ładując mniejszą wersję do pamięci, ustaw inSampleSizena trueswój BitmapFactory.Optionsobiekt. Na przykład obraz o rozdzielczości 2048 x 1536, który jest dekodowany za pomocą inSampleSize4, tworzy mapę bitową o wielkości około 512 x 384. Ładowanie tego do pamięci zajmuje 0,75 MB zamiast 12 MB dla pełnego obrazu (przy założeniu konfiguracji bitmapy ARGB_8888). Oto metoda obliczenia wartości wielkości próbki, która jest potęgą dwóch, na podstawie docelowej szerokości i wysokości:

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

Uwaga : Moc dwóch wartości jest obliczana, ponieważ dekoder wykorzystuje wartość końcową zaokrąglając w dół do najbliższej mocy dwóch, zgodnie z inSampleSizedokumentacją.

Aby użyć tej metody, najpierw zdekoduj z inJustDecodeBoundsustawionym na true, przekaż opcje, a następnie ponownie zdekoduj przy użyciu nowej inSampleSizewartości i inJustDecodeBoundsustaw na 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);
}

Ta metoda ułatwia załadowanie mapy bitowej o dowolnym rozmiarze do ImageViewwyświetlanej miniatury 100 x 100 pikseli, jak pokazano w poniższym przykładowym kodzie:

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

Możesz wykonać podobny proces dekodowania map bitowych z innych źródeł, zastępując odpowiednią BitmapFactory.decode*metodę w razie potrzeby.


21
Ta odpowiedź jest dyskutowana na stronie meta
rene

9
Ta odpowiedź (z wyjątkiem informacji uzyskanych za pośrednictwem linku) nie oferuje zbyt dużego rozwiązania, jak odpowiedź. Ważne części linku powinny zostać połączone w pytanie.
FallenAngel

7
Ta odpowiedź, podobnie jak pytanie i inne odpowiedzi, to Wiki Wiki, więc jest to coś, co społeczność może naprawić poprzez edycję, coś, co nie wymaga interwencji moderatora.
Martijn Pieters

aktualny link do treści i wsparcia Kotlin można znaleźć na stronie: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr

890

Aby naprawić błąd OutOfMemory, powinieneś zrobić coś takiego:

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

Ta inSampleSizeopcja zmniejsza zużycie pamięci.

Oto kompletna metoda. Najpierw odczytuje rozmiar obrazu bez dekodowania samej treści. Następnie znajduje najlepszą inSampleSizewartość, powinna być potęgą 2, a na końcu obraz jest dekodowany.

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

31
Zauważ, że 10 może nie być najlepszą wartością dla inSampleSize, dokumentacja sugeruje użycie mocy 2.
Mirko N.

70
Stoję w obliczu tego samego problemu co Chrispix, ale nie sądzę, że rozwiązanie tutaj naprawdę rozwiązuje problem, ale raczej go omija. Zmiana wielkości próbki zmniejsza ilość używanej pamięci (kosztem jakości obrazu, co prawdopodobnie jest odpowiednie w przypadku podglądu obrazu), ale nie zapobiegnie wyjątkowi, jeśli dekodowany jest wystarczająco duży strumień obrazu, lub jeśli wiele strumieni obrazu jest dekodowany. Jeśli znajdę lepsze rozwiązanie (a może go nie być), opublikuję odpowiedź tutaj.
Flynn81

4
Potrzebujesz tylko odpowiedniego rozmiaru, aby dopasować się do ekranu pod względem gęstości pikseli, aby powiększyć i możesz pobrać próbkę obrazu o większej gęstości.
stealthcopter

4
REQUIRED_SIZE to nowy rozmiar, do którego chcesz skalować.
Fedor,

8
to rozwiązanie pomogło mi, ale jakość obrazu jest okropna. Korzystam z viewfilppera, aby wyświetlać obrazy jakieś sugestie?
user1106888

372

Wprowadziłem niewielką poprawę w kodzie Fedora. Zasadniczo robi to samo, ale bez (moim zdaniem) brzydkiej pętli while i zawsze daje potęgę dwóch. Uznanie dla Fedora za stworzenie oryginalnego rozwiązania, utknąłem, dopóki nie znalazłem jego, a potem udało mi się to zrobić :)

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

40
Tak, masz rację, dopóki nie jest tak piękna. Po prostu starałem się to wszystkim wyjaśnić. Dziękuję za Twój kod.
Fedor

10
@ Thomas Vervest - Istnieje duży problem z tym kodem. ^ nie podnosi 2 do potęgi, xors 2 z wynikiem. Chcesz Math.pow (2.0, ...). W przeciwnym razie wygląda to dobrze.
DougW,

6
Och, to bardzo dobre! Mój zły, natychmiast to poprawię, dziękuję za odpowiedź!
Thomas Vervest,

8
Tworzysz dwa nowe FileInputStreams, po jednym dla każdego wywołania do BitmapFactory.decodeStream(). Czy nie musisz zapisywać odniesienia do każdego z nich, aby można je było zamknąć w finallybloku?
matsev

1
@Babibu Dokumentacja nie stwierdza, że ​​strumień jest dla ciebie zamknięty, dlatego zakładam, że powinien być nadal zamknięty. Interesującą i powiązaną dyskusję można znaleźć tutaj . Zwróć uwagę na komentarz Adriana Smitha, który odnosi się bezpośrednio do naszej debaty.
Thomas Vervest

232

Pochodzę z iOS i byłem sfrustrowany, gdy odkryłem problem z czymś tak podstawowym, jak ładowanie i wyświetlanie obrazu. W końcu wszyscy, którzy mają ten problem, starają się wyświetlać obrazy o rozsądnych rozmiarach. Tak czy inaczej, oto dwie zmiany, które rozwiązały mój problem (i sprawiły, że moja aplikacja bardzo szybko reaguje).

1) Za każdym razem, gdy to zrobisz BitmapFactory.decodeXYZ(), upewnij się, że przekazujesz zestaw BitmapFactory.Optionsz inPurgeableustawionym na true(a najlepiej inInputShareablerównież z ustawionym na true).

2) NIGDY nie używaj Bitmap.createBitmap(width, height, Config.ARGB_8888). Mam na myśli NIGDY! Nigdy nie miałem tej rzeczy, aby nie podnieść błędu pamięci po kilku przejściach. Żadna ilość recycle(), System.gc()cokolwiek pomogło. Zawsze budziło wyjątek. Jedynym innym sposobem, który faktycznie działa, jest umieszczenie obrazu zastępczego w twoich plikach rysunkowych (lub innej bitmapie, którą zdekodowałeś przy użyciu kroku 1 powyżej), przeskaluj to, co chcesz, a następnie zmanipuluj uzyskaną bitmapę (na przykład przekazując ją do obszaru roboczego dla większej zabawy). Więc, co należy użyć zamiast tego jest: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Jeśli z jakiegokolwiek powodu MUSISZ użyć metody tworzenia brutalnej siły, to przynajmniej zalicz Config.ARGB_4444.

Jest to prawie na pewno zaoszczędzić godziny, jeśli nie dni. Wszystko, co mówi o skalowaniu obrazu itp., Tak naprawdę nie działa (chyba że rozważenie niewłaściwego rozmiaru lub zdegradowanego obrazu jest rozwiązaniem).


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;i Bitmap.createScaledBitmap(srcBitmap, width, height, false);rozwiązałem mój problem z wyjątkiem braku pamięci w Androidzie 4.0.0. Dzięki stary!
Jan-Terje Sørensen

5
W wywołaniu Bitmap.createScaledBitmap () prawdopodobnie powinieneś użyć true jako parametru flagi. W przeciwnym razie jakość obrazu nie będzie płynna podczas skalowania w górę. Sprawdź ten wątek stackoverflow.com/questions/2895065/…
rOrlig

11
To naprawdę wspaniała rada. Żałuję, że nie mogę dać ci dodatkowej +1 za zabranie Google'a za ten niesamowicie złośliwy błąd. Mam na myśli ... jeśli to nie jest błąd, to w dokumentacji naprawdę muszą być jakieś poważnie migające neony z napisem „TO, W JAKI SPOSÓB PROCESUJESZ ZDJĘCIA”, ponieważ zmagam się z tym od 2 lat i właśnie znalazłem ten post. Świetne znalezisko.
Jewgienij Simkin

Skalowanie zdjęć zdecydowanie pomaga, ale jest to ważny krok i to, co ostatecznie rozwiązało ten problem dla mnie. Problem z samym skalowaniem obrazów polega na tym, że jeśli masz ich dużo lub jeśli obrazy źródłowe są bardzo duże, nadal możesz napotkać ten sam problem. +1 do ciebie Efraim.
Dave,

10
Począwszy od Lollipopa BitmapFactory.Options.inPurgeablei BitmapFactory.Options.inInputShareablesą przestarzałe developer.android.com/reference/android/graphics/…
Denis Kniazhev

93

To znany błąd , który nie wynika z dużych plików. Ponieważ Android buforuje tabele rysunkowe, po użyciu kilku obrazów traci pamięć. Ale znalazłem alternatywny sposób, pomijając domyślny system pamięci podręcznej Androida.

Rozwiązanie : Przenieś obrazy do folderu „zasoby” i użyj następującej funkcji, aby uzyskać BitmapDrawable:

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

79

Miałem ten sam problem i rozwiązałem go, unikając funkcji BitmapFactory.decodeStream lub decodeFile, i zamiast tego użyłem BitmapFactory.decodeFileDescriptor

decodeFileDescriptor wygląda na to, że wywołuje inne metody rodzime niż decodeStream / decodeFile.

W każdym razie zadziałało to (zauważ, że dodałem niektóre opcje, jak niektóre powyżej), ale to nie to, co zrobiło różnicę. Najważniejsze jest wywołanie BitmapFactory.decodeFileDescriptor zamiast decodeStream lub 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;                        
}

Myślę, że jest problem z natywną funkcją używaną w decodeStream / decodeFile. Potwierdziłem, że przy użyciu decodeFileDescriptor wywoływana jest inna natywna metoda. Również to, co czytałem to „że obrazy (bitmapy) nie są przydzielane w sposób standardowy, ale poprzez wywołania Java rodzimych; przydziały są wykonywane poza wirtualnym sterty, ale są ! Wliczane nim


1
ten sam wynik z pamięci, w rzeczywistości nie będzie miało znaczenia, którą metodę użyjesz, zależy od liczby bajtów, które trzymasz, aby odczytać dane wydane z pamięci.
PiyushMishra

72

Myślę, że najlepszym sposobem na uniknięcie tego OutOfMemoryErrorjest stawienie temu czoła i zrozumienie tego.

Zrobiłem aplikację, aby celowo powodować OutOfMemoryErrori monitorować użycie pamięci.

Po przeprowadzeniu wielu eksperymentów z tą aplikacją doszedłem do następujących wniosków:

Najpierw opowiem o wersjach SDK przed Honey Comb.

  1. Bitmapa jest przechowywana w natywnej stercie, ale automatycznie zbiera śmieci, wywoływanie recycle () jest niepotrzebne.

  2. Jeśli {rozmiar sterty maszyny wirtualnej} + {przydzielona natywna pamięć sterty}> = {limit wielkości sterty maszyny wirtualnej dla urządzenia} i próbujesz utworzyć bitmapę, zostanie wygenerowane OOM.

    POUCZENIE: ROZMIAR HEAP VM jest liczony, a nie PAMIĘĆ PRZYDZIELONA VM.

  3. Rozmiar stosu maszyny wirtualnej nigdy nie zmniejszy się po powiększeniu, nawet jeśli przydzielona pamięć maszyny wirtualnej zostanie zmniejszona.

  4. Musisz więc utrzymywać szczytową pamięć VM tak nisko, jak to możliwe, aby nie dopuścić do zbyt dużego wzrostu sterty VM, aby zaoszczędzić dostępną pamięć dla map bitowych.

  5. Ręczne wywołanie System.gc () nie ma znaczenia, system wywoła go najpierw, zanim spróbuje zwiększyć rozmiar sterty.

  6. Rozmiar stosu natywnego nigdy się nie zmniejszy, ale nie jest liczony dla OOM, więc nie musisz się o to martwić.

Porozmawiajmy o SDK Starts od Honey Comb.

  1. Mapa bitowa jest przechowywana na stercie maszyny wirtualnej, pamięć natywna nie jest liczona dla OOM.

  2. Warunek OOM jest znacznie prostszy: {rozmiar sterty maszyny wirtualnej}> = {limit wielkości sterty maszyny wirtualnej dla urządzenia}.

  3. Więc masz więcej dostępnej pamięci do tworzenia bitmapy z tym samym limitem wielkości sterty, OOM rzadziej zostanie wyrzucony.

Oto niektóre z moich spostrzeżeń na temat usuwania śmieci i wycieku pamięci.

Możesz to zobaczyć sam w aplikacji. Jeśli działanie wykonało zadanie AsyncTask, które nadal działało po tym, jak działanie zostało zniszczone, działanie nie będzie zbierać śmieci do czasu zakończenia zadania AsyncTask.

Wynika to z faktu, że AsyncTask jest instancją anonimowej klasy wewnętrznej i zawiera odwołanie do działania.

Wywołanie AsyncTask.cancel (true) nie zatrzyma wykonywania, jeśli zadanie zostanie zablokowane w operacji IO w wątku w tle.

Wywołania zwrotne są również anonimowymi klasami wewnętrznymi, więc jeśli statyczna instancja w twoim projekcie je przechowuje i nie zwalnia, pamięć wycieknie.

Jeśli zaplanowałeś powtarzające się lub opóźnione zadanie, na przykład Timer, i nie wywołujesz cancel () i purge () w onPause (), pamięć wyciekłaby.


AsyncTask niekoniecznie musi być „instancją anonimowej klasy wewnętrznej”, to samo dotyczy Callbackks. Możesz utworzyć nową klasę publiczną we własnym pliku, który rozszerza AsyncTask, a nawet private static classw tej samej klasie. Nie będą zawierać żadnych odniesień do działania (chyba, że ​​dasz im je oczywiście)
Simon Forsberg

65

Ostatnio widziałem wiele pytań na temat wyjątków OOM i buforowania. Przewodnik dla programistów zawiera naprawdę dobry artykuł na ten temat, ale niektórzy mają tendencję do niepowodzenia w odpowiednim wdrażaniu.

Z tego powodu napisałem przykładową aplikację, która demonstruje buforowanie w środowisku Androida. Ta implementacja nie otrzymała jeszcze OOM.

Spójrz na końcu tej odpowiedzi na link do kodu źródłowego.

Wymagania:

  • Android API 2.1 lub wyższy (po prostu nie mogłem uzyskać dostępnej pamięci dla aplikacji w API 1.6 - to jedyny fragment kodu, który nie działa w API 1.6)
  • Pakiet wsparcia dla Androida

Zrzut ekranu

Funkcje:

  • Zachowuje pamięć podręczną w przypadku zmiany orientacji przy użyciu singletonu
  • Użyj jednej ósmej przydzielonej pamięci aplikacji do pamięci podręcznej (zmodyfikuj, jeśli chcesz)
  • Duże mapy bitowe są skalowane (możesz zdefiniować maksymalną liczbę pikseli, na którą chcesz zezwolić)
  • Kontroluje dostępność połączenia internetowego przed pobraniem map bitowych
  • Zapewnia, że ​​tworzysz tylko jedno zadanie na wiersz
  • Jeśli jesteś rzucając się ListViewdalej, to po prostu nie będzie pobierał między bitmap

Nie obejmuje to:

  • Buforowanie dysku. W każdym razie powinno to być łatwe do wdrożenia - wystarczy wskazać inne zadanie, które pobiera mapy bitowe z dysku

Przykładowy kod:

Pobrane obrazy to obrazy (75x75) z Flickr. Jednak umieść dowolne adresy URL obrazów, które chcesz przetworzyć, a aplikacja zmniejszy je, jeśli przekroczy maksimum. W tej aplikacji adresy URL są po prostu w Stringtablicy.

LruCacheMa dobry sposób na radzenie sobie z bitmapy. Jednak w tej aplikacji umieściłem instancję LruCachewewnątrz innej klasy pamięci podręcznej, którą utworzyłem, aby aplikacja była bardziej wykonalna.

Krytyczne rzeczy Cache.java ( loadBitmap()metoda jest najważniejsza):

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

Nie trzeba edytować niczego w pliku Cache.java, chyba że chcesz wdrożyć buforowanie dysku.

Najważniejsze rzeczy z MainActivity.java:

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

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

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

    final Row rowObject = getItem(position);

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

    return row;
}

getView()dzwoni bardzo często. Zwykle nie jest dobrym pomysłem pobieranie tam zdjęć, jeśli nie wdrożyliśmy kontroli, która zapewni nam, że nie zaczniemy nieskończonej ilości wątków na wiersz. Cache.java sprawdza, czy rowObject.mBitmapUrljuż jest w zadaniu, a jeśli tak, to nie uruchomi innego. Dlatego najprawdopodobniej nie przekroczymy ograniczenia kolejki roboczej z AsyncTaskpuli.

Pobieranie:

Możesz pobrać kod źródłowy z https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Ostatnie słowa:

Testowałem to od kilku tygodni, nie dostałem jeszcze żadnego wyjątku OOM. Przetestowałem to na emulatorze, na moim Nexusie One i na moim Nexusie S. Testowałem adresy URL obrazów zawierające obrazy o jakości HD. Jedynym wąskim gardłem jest to, że pobieranie zajmuje więcej czasu.

Jest tylko jeden możliwy scenariusz, w którym mogę sobie wyobrazić, że pojawi się OOM, a mianowicie, jeśli pobieramy wiele, naprawdę dużych obrazów, a zanim zostaną skalowane i umieszczone w pamięci podręcznej, jednocześnie zajmą więcej pamięci i spowodują OOM. Ale i tak nie jest to idealna sytuacja i najprawdopodobniej nie będzie możliwe rozwiązanie w bardziej wykonalny sposób.

Zgłoś błędy w komentarzach! :-)


43

Wykonałem następujące czynności, aby zrobić zdjęcie i zmienić jego rozmiar w locie. Mam nadzieję że to pomoże

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

26
To podejście skaluje mapę bitową. Ale to nie rozwiązuje problemu OutOfMemory, ponieważ i tak dekodowana jest pełna mapa bitowa.
Fedor

5
Zobaczę, czy mogę spojrzeć na mój stary kod, ale myślę, że rozwiązało to problemy z brakiem pamięci. Dokładnie sprawdzi mój stary kod.
Chrispix,

2
Przynajmniej w tym przykładzie wygląda to tak, jakbyś nie zachowywał odniesienia do pełnej mapy bitowej, a tym samym oszczędności pamięci.
NoBugs

Dla mnie rozwiązało to problem z pamięcią, ale obniżyło jakość obrazów.
Pamela Sillah

35

Wydaje się, że jest to bardzo długotrwały problem, z wieloma różnymi wyjaśnieniami. Skorzystałem z dwóch najczęściej przedstawianych odpowiedzi tutaj, ale żadna z nich nie rozwiązała moich problemów z maszyną wirtualną, twierdząc, że nie stać jej na bajty na wykonanie części procesu dekodowania . Po kilku kopaniach dowiedziałem się, że prawdziwym problemem jest proces dekodowania zabierający ze sterty NATIVE .

Zobacz tutaj: BitmapFactory OOM doprowadza mnie do szału

Doprowadziło mnie to do kolejnego wątku do dyskusji, w którym znalazłem jeszcze kilka rozwiązań tego problemu. Jednym z nich jest System.gc();ręczne wywołanie po wyświetleniu obrazu. Ale to tak naprawdę powoduje, że Twoja aplikacja korzysta z WIĘCEJ pamięci, starając się zmniejszyć natywną stertę. Lepszym rozwiązaniem od wydania 2.0 (Donut) jest użycie opcji BitmapFactory „inPurgeable”. Więc po prostu dodałem o2.inPurgeable=true;zaraz o2.inSampleSize=scale;.

Więcej na ten temat tutaj: Czy limit sterty pamięci to tylko 6 MB?

Teraz, powiedziawszy to wszystko, jestem kompletnym dunce z Javą i Androidem. Więc jeśli uważasz, że to okropny sposób na rozwiązanie tego problemu, prawdopodobnie masz rację. ;-) Ale zadziałało to dla mnie cuda i nie mogłem teraz uruchomić maszyny wirtualnej z pamięci podręcznej sterty. Jedyną wadą, jaką mogę znaleźć, jest to, że niszczysz swój narysowany obraz w pamięci podręcznej. Co oznacza, że ​​jeśli wrócisz PRAWO do tego obrazu, przerysujesz go za każdym razem. W przypadku tego, jak działa moja aplikacja, nie jest to tak naprawdę problemem. Twój przebieg może się różnić.


inPurgeable naprawił dla mnie OOM.
Artem Russakovskii

35

niestety, jeśli żadna z powyższych nie działa, to dodaj to do pliku manifestu . Wewnątrz tagu aplikacji

 <application
         android:largeHeap="true"

1
Czy możesz wyjaśnić, co to właściwie robi? Samo powiedzenie ludziom, żeby to dodali, nie pomaga.
Stealth Rabbi

1
To bardzo złe rozwiązanie. Zasadniczo nie próbujesz rozwiązać problemu. Zamiast tego poprosić system Android o przydzielenie większej ilości miejsca na stos dla aplikacji. Będzie to miało bardzo złe konsekwencje dla Twojej aplikacji, takie jak aplikacja zużywająca dużo energii baterii, ponieważ GC musi przebiec przez dużą przestrzeń sterty, aby wyczyścić pamięć, a także wydajność aplikacji będzie wolniejsza.
Prakash

2
to dlaczego Android pozwala nam dodać tego Androida: largeHeap = "true" w naszym manifeście? Teraz jesteś wymagającym Androidem.
Himanshu Mori,


28

Mam o wiele bardziej skuteczne rozwiązanie, które nie wymaga żadnego skalowania. Wystarczy zdekodować bitmapę tylko raz, a następnie buforować ją na mapie w stosunku do jej nazwy. Następnie po prostu pobierz mapę bitową dla nazwy i ustaw ją w ImageView. Nie trzeba nic więcej robić.

Działa to, ponieważ rzeczywiste dane binarne zdekodowanej mapy bitowej nie są przechowywane w stercie maszyny wirtualnej Dalvik. Jest przechowywany na zewnątrz. Dlatego za każdym razem, gdy dekodujesz bitmapę, alokuje ona pamięć poza stertą VM, która nigdy nie jest odzyskiwana przez GC

Aby lepiej to docenić, wyobraź sobie, że trzymałeś swój obraz w folderze, który można wyciągnąć. Po prostu dostajesz obraz, wykonując getResources (). GetDrwable (R.drawable.). To NIE dekoduje twojego obrazu za każdym razem, ale ponownie użyje już zdekodowanej instancji za każdym razem, gdy ją wywołasz. Zasadniczo jest on buforowany.

Ponieważ twój obraz znajduje się gdzieś w pliku (lub może nawet pochodzić z zewnętrznego serwera), Twoim obowiązkiem jest buforowanie zdekodowanej instancji bitmapy w celu ponownego użycia tam, gdzie jest to potrzebne.

Mam nadzieję że to pomoże.


4
„a następnie buforuj go na mapie pod jego nazwą.” Jak dokładnie buforujesz swoje zdjęcia?
Vincent

3
Czy rzeczywiście tego próbowałeś? Mimo że dane pikseli nie są faktycznie przechowywane w stercie Dalvik, ich rozmiar w pamięci natywnej jest zgłaszany do maszyny wirtualnej i liczony w stosunku do dostępnej pamięci.
ErikR

3
@ Vincent Myślę, że nie jest trudno przechowywać je na mapie. Sugerowałbym coś takiego jak HashMap <KEY, Bitmap> mapa, gdzie Kluczem może być Ciąg źródła lub cokolwiek, co ma dla ciebie sens. Załóżmy, że podążasz ścieżką jako KLUCZ, przechowujesz ją jako map.put (Ścieżka, Bitmapa) i otrzymujesz ją poprzez map.get (Ścieżka)
Rafael T

3
prawdopodobnie chcesz użyć HashMap <String, SoftReference <Bitmap>>, jeśli implementujesz pamięć podręczną obrazu, inaczej możesz skończyć się w pamięci - i nie sądzę, że "przydziela pamięć poza stertą VM, która nigdy nie jest odzyskiwana przez GC „to prawda, pamięć jest odzyskiwana, ponieważ rozumiem, że może to być opóźnienie, do czego właśnie służy bitmap.recycle (), jako wskazówka do odzyskania pamięci wcześnie ...
Dori

28

Ten sam problem rozwiązałem w następujący sposób.

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

to wszystko w porządku, ale używam wielu bitmap do rysowania okręgu w OnCreate i wywołanie aktywności 4-5 razy, więc jak wyczyścić bitmapę i jak usunąć bitmapę i utworzyć ponownie po raz drugi, gdy aktywność 0nCreate ..
ckpatel

27

Są tutaj dwa problemy ....

  • Pamięć bitmap nie znajduje się na stercie maszyny wirtualnej, ale raczej na stercie natywnym - patrz BitmapFactory OOM doprowadza mnie do szału
  • Wyrzucanie elementów bezużytecznych dla sterty natywnej jest bardziej leniwe niż sterty maszyn wirtualnych - więc musisz być bardzo agresywny, wykonując bitmap.recycle i bitmap = null za każdym razem, gdy przejdziesz do działania onPause lub onDestroy

Jest w stosie maszyn wirtualnych od Androida 2.3+
FindOut_Quran

27

To zadziałało dla mnie!

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

20

Żadna z powyższych odpowiedzi nie działała dla mnie, ale wpadłem na strasznie brzydkie obejście, które rozwiązało problem. Dodałem bardzo mały obraz 1 x 1 piksel do mojego projektu jako zasób i załadowałem go do ImageView przed wywołaniem odśmiecania. Myślę, że być może ImageView nie wypuściło mapy bitowej, więc GC nigdy jej nie podniosła. To brzydkie, ale na razie wydaje się, że działa.

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.

wygląda na to, że imageView naprawdę nie przetwarza mapy bitowej samodzielnie. Pomógł mi, dzięki
Dmitrij Zajcew

@ Mike, czy możesz dodać pełny kod imageloadera, czy możesz podać link do ładowania obrazów bitmapowych. Jeśli używam opcji recyklingu na mapie bitowej, wyświetlany jest cały mój widok listy, ale wszystkie elementy są puste
TNR

@ Mike może stwierdzić, czy wykonuję imageView = null przed wywołaniem funkcji wyrzucania elementów bezużytecznych, czy to dobrze, czy nie?
Youddh

@TNR Myślę, że brakuje ci tutaj tego, że bitmapw powyższym kodzie znajduje się poprzednio już wyświetlany obraz, musisz go przetworzyć, wyczyścić wszelkie odniesienia do niego, imageViewzapomnij o nim również, ustawiając niewielki zamiennik gc(); a po tym wszystkim: załaduj NOWY obraz bitmapi wyświetl go ...w powyższym kodzie.
TWiStErRob

To jest źle. Zawsze powinieneś wyczyścić obraz Wyświetl zawartość PRZED recyklingiem bitmapy (zamiast gdy jest ona faktycznie wyświetlana i używana).
FindOut_Quran

20

Świetne odpowiedzi tutaj, ale chciałem, aby klasa w pełni użyteczna rozwiązała ten problem .. więc zrobiłem jedną.

Oto moja klasa BitmapHelper, która jest odporna na OutOfMemoryError :-)

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

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

public class BitmapHelper
{

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

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

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

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

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

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

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

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

            boolean recycleDecodedBitmap = false;

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

            // crop image to match required image size

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

            Bitmap croppedBitmap = scaledBitmap;

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

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

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

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

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

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

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

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

        return inSampleSize;
    }

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

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

        return bitmap;
    }

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

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

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

}

Dla każdego, kto tego używa: Właśnie naprawiłem błąd: "int scaledBitmapHeight = scaledBitmap.getWidth ();" jest źle (oczywiście. Zamieniłem go na „int scaledBitmapHeight = scaledBitmap.getHeight ();”
Pascal

19

To działa dla mnie.

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

i to jest na C # monodroid. możesz łatwo zmienić ścieżkę obrazu. ważne są tutaj opcje do ustawienia.


16

Wydaje się, że jest to odpowiednie miejsce do dzielenia się moją klasą narzędziową do ładowania i przetwarzania obrazów ze społecznością, możesz go używać i dowolnie modyfikować.

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

16

W jednej z moich aplikacji muszę zrobić zdjęcie Camera/Gallery. Jeśli użytkownik kliknie obraz z aparatu (może to być 2MP, 5MP lub 8MP), rozmiar obrazu zmienia się od kBs do MBs. Jeśli rozmiar obrazu jest mniejszy (lub do 1-2 MB) powyżej kodu działa dobrze, ale jeśli mam obraz powyżej 4 MB lub 5 MB, to OOMpojawia się w ramce :(

potem pracowałem nad rozwiązaniem tego problemu i na koniec wprowadziłem poniższe ulepszenie do kodu Fedora (All Credit to Fedor za stworzenie tak fajnego rozwiązania) :)

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;

}

Mam nadzieję, że pomoże to kumplom zmierzyć się z tym samym problemem!

po więcej zapoznaj się z tym


14

Właśnie natknąłem się na ten problem kilka minut temu. Rozwiązałem to, wykonując lepszą pracę w zarządzaniu moim adapterem listview. Myślałem, że to problem z setkami zdjęć 50 x 50 pikseli, których używałem, okazuje się, że próbowałem zawyżać swój niestandardowy widok za każdym razem, gdy wyświetlany był wiersz. Po prostu testując, czy wiersz został zawyżony, wyeliminowałem ten błąd i używam setek map bitowych. To jest właściwie dla Spinnera, ale adapter podstawowy działa tak samo dla ListView. Ta prosta poprawka znacznie poprawiła również wydajność adaptera.

@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);
    }
...

3
Nie mogę ci za to wystarczająco podziękować! Zanim to zobaczyłem, szukałem niewłaściwego problemu. Pytanie dla ciebie: Ponieważ każdy wiersz lub moja lista ma unikalną nazwę i zdjęcie, musiałem użyć tablicy convertView, aby zachować wartości każdego z tych wierszy. Nie widziałem, jak użycie jednej zmiennej pozwoli ci to zrobić. Czy coś brakuje?
PeteH,

13

Cały dzień spędziłem na testowaniu tych rozwiązań, a jedyną rzeczą, która działała dla mnie, były powyższe metody uzyskiwania obrazu i ręcznego wywoływania GC, które, jak wiem, nie powinny być konieczne, ale tylko to działało kiedy poddaję moją aplikację testom pod dużym obciążeniem, przełączając się między czynnościami. Moja aplikacja ma listę miniatur w widoku listy w (powiedzmy aktywność A), a kiedy klikniesz jeden z tych obrazów, przeniesie Cię do innej aktywności (powiedzmy aktywność B), która pokazuje główny obraz dla tego elementu. Kiedy przełączałem się między tymi dwiema czynnościami, w końcu otrzymywałem błąd OOM i aplikacja wymuszała zamknięcie.

Gdy dostanę się w połowie widoku listy, nastąpi awaria.

Teraz, gdy zaimplementuję następujące działanie B, mogę bez problemu przejrzeć cały widok listy i kontynuować pracę, pracę i pracę ... i to szybko.

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

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

Uwielbiam twoje rozwiązanie! Spędziłem również godziny na rozwiązywaniu tego problemu, więc irytujące! Edycja: Niestety problem nadal występuje, gdy zmieniam orientację ekranu w trybie poziomym ...
Xarialon

To w końcu pomogło mi wraz z: - BitmapFactory.Options options = new BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732,

13

Ten problem występuje tylko w emulatorach Androida. Napotkałem również ten problem w emulatorze, ale kiedy sprawdziłem urządzenie, działało dobrze.

Więc proszę sprawdzić w urządzeniu. Można go uruchomić w urządzeniu.


12

Moje 2 centy: Rozwiązałem moje błędy OOM w bitmapach przez:

a) skalowanie moich zdjęć 2 razy

b) użycie biblioteki Picasso w moim niestandardowym adapterze dla ListView, z jednym wywołaniem w getView w następujący sposób:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


Cieszę się, że wspomniałeś o Picassie, ponieważ znacznie ułatwia ładowanie zdjęć. Szczególnie zdalnie przechowywane.
Chrispix

12

użyj tego kodu dla każdego obrazu wybranego z SdCard lub dreorable do konwersji obiektu bitmapowego.

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;

użyj ścieżki obrazu instend ImageData_Path.get (img_pos) .getPath () .


12

Zasadniczo rozmiar sterty urządzeń z systemem Android wynosi tylko 16 MB (różni się w zależności od urządzenia / systemu operacyjnego, patrz Post Heap Sizes ), jeśli ładujesz obrazy i przekracza on rozmiar 16 MB, spowoduje to wyrzucenie wyjątku pamięci zamiast użycia mapy bitowej do ładowania obrazy z karty SD lub z zasobów, a nawet z sieci, spróbuj użyć getImageUri , ładowanie bitmapy wymaga więcej pamięci lub możesz ustawić bitmapę na zero, jeśli wykonałeś pracę z tą bitmapą.


1
A jeśli setImageURI nadal otrzymuje wyjątek, to skorzystaj z tej stackoverflow.com/questions/15377186/...
Mahesh

11

Wszystkie rozwiązania tutaj wymagają ustawienia IMAGE_MAX_SIZE. Ogranicza to urządzenia z mocniejszym sprzętem, a jeśli rozmiar obrazu jest zbyt niski, wygląda brzydko na ekranie HD.

Wynalazłem rozwiązanie, które działa z moim Samsung Galaxy S3 i kilkoma innymi urządzeniami, w tym mniej wydajnymi, z lepszą jakością obrazu, gdy używane jest mocniejsze urządzenie.

Jego celem jest obliczenie maksymalnej pamięci przydzielonej dla aplikacji na konkretnym urządzeniu, a następnie ustawienie najniższej możliwej skali bez przekraczania tej pamięci. Oto kod:

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

Ustawiłem maksymalną pamięć używaną przez tę bitmapę na 25% maksymalnej przydzielonej pamięci, być może będziesz musiał dostosować ją do swoich potrzeb i upewnić się, że ta bitmapa jest wyczyszczona i nie pozostanie w pamięci po zakończeniu jej używania. Zazwyczaj używam tego kodu do obracania obrazu (bitmapa źródłowa i docelowa), więc moja aplikacja musi jednocześnie ładować 2 bitmapy do pamięci, a 25% daje mi dobry bufor bez wyczerpania pamięci podczas obracania obrazu.

Mam nadzieję, że to pomoże komuś tam ..


11

Taki OutofMemoryExceptionnie może być całkowicie rozwiązany przez wywołanie System.gc()i tak dalej.

Odnosząc się do cyklu życia aktywności

Stany aktywności są określane przez sam system operacyjny, z zastrzeżeniem użycia pamięci dla każdego procesu i priorytetu każdego procesu.

Możesz wziąć pod uwagę rozmiar i rozdzielczość każdego z użytych obrazów bitmapowych. Zalecam zmniejszenie rozmiaru, ponowne próbkowanie do niższej rozdzielczości, zapoznaj się z projektem galerii (jeden mały obraz PNG i jeden oryginalny obraz).


11

Ten kod pomoże załadować dużą mapę bitową z rysowalnej

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

Fajnie, czy lepiej byłoby użyć modułu ładującego zamiast zadania asynchronicznego?
Chrispix,

Jak o Bitmap.Config.ARGB_565? Jeśli wysoka jakość nie jest krytyczna.
Hamzeh Soboh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.