Bir Bitmap nesnesine görüntü yüklerken bellekte tuhaflık sorunu


1288

Her satırda birkaç görüntü düğmesi olan bir liste görünümü var. Liste satırını tıklattığınızda yeni bir etkinlik başlatılır. Kamera düzeniyle ilgili bir sorun nedeniyle kendi sekmelerimi oluşturmak zorunda kaldım. Sonuç için başlatılan etkinlik bir haritadır. Görüntü önizlemesini başlatmak için düğmeme tıklarsam (SD karttan bir görüntü yükle), uygulama listviewbir görüntü widget'ından başka bir şey olmayan yeni etkinliğimi yeniden başlatmak için etkinlikten etkinliğe sonuç işleyicisine geri döner .

Liste görünümündeki görüntü önizlemesi imleç ve ile yapılır ListAdapter. Bu oldukça basit hale getirir, ancak yeniden boyutlandırılan bir görüntüyü nasıl yerleştirebileceğimden emin değilim (Yani daha küçük bit boyutu src, görüntü düğmesi için olduğu gibi piksel değil . Bu yüzden telefon kamerasından çıkan görüntüyü yeniden boyutlandırdım.

Sorun, geri dönüp 2. etkinliği yeniden başlatmaya çalıştığında bellek yetersiz hatası alıyorum.

  • Liste bağdaştırıcısını kolayca satır satır oluşturabileceğim bir yol var mı, burada anında yeniden boyutlandırabilirim ( biraz akıllıca )?

Odaklanma sorunu nedeniyle dokunmatik ekranla bir satır seçemediğim için her satırdaki widget'ların / öğelerin özelliklerinde bazı değişiklikler yapmam gerektiğinden bu tercih edilir. ( Silindir topu kullanabilirim. )

  • Ben bant dışı yeniden boyutlandırma ve benim görüntü kaydetmek yapabilirim biliyorum, ama bu gerçekten yapmak istediğim şey değil, ama bunun için bazı örnek kod güzel olurdu.

Liste görünümünde görüntüyü devre dışı bıraktığımda, tekrar iyi çalıştı.

FYI: Ben böyle yapıyordum:

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

Nerede R.id.imagefilenamea ButtonImage.

İşte LogCat'ım:

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 

Ayrıca bir görüntü görüntülerken yeni bir hata var:

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
Bunu Bitmap.decodeStream veya decodeFile'dan kaçınarak ve BitmapFactory.decodeFileDescriptor yöntemini kullanarak çözdüm.
Fraggle

1
Ayrıca birkaç hafta önce benzer bir sorunla karşı karşıya kaldım ve görüntüleri en uygun noktaya indirgeyerek çözdüm. Blog codingjunkiesforum.wordpress.com/2014/06/12/… adresinde tam bir yaklaşım yazdım ve OOM prone kodu vs OOM Proof kod atletleri ile komple örnek proje yükledim: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat

5
Bu soru üzerine kabul edilen cevabı üzerine görüşülmekte olan meta
Rene

4
Bu, Android geliştirici kılavuzlarını okumadığınızda olur
Pedro Varela

2
Bu kötü android mimarisi nedeniyle olur. İos gibi görüntüleri yeniden boyutlandırmalı ve UWP bunu yapıyor. Bunu kendim yapmak zorunda değilim. Android geliştiricileri bu cehenneme alışır ve olması gerektiği gibi çalıştığını düşünür.
Erişim Reddedildi

Yanıtlar:


650

Android Eğitim sınıfı, " Verimli Bitmaps gösteriliyor ", teklifler anlama ve istisna ile başa çıkmak için bazı büyük bilgi java.lang.OutOfMemoryError: bitmap size exceeds VM budgetBitmap'ler yüklerken.


Bitmap Boyutlarını ve Türünü Okuyun

BitmapFactorySınıf (birkaç çözme yöntemleri sağlar decodeByteArray(), decodeFile(), decodeResource()bir bağlantı oluşturmak için, vs.) Bitmapçeşitli kaynaklardan. Görüntü veri kaynağınıza göre en uygun kod çözme yöntemini seçin. Bu yöntemler, oluşturulmuş bitmap için bellek ayırmaya çalışır ve bu nedenle kolayca OutOfMemoryistisnaya neden olabilir . Her bir kod çözme yöntemi türünün, BitmapFactory.Optionssınıf yoluyla kod çözme seçeneklerini belirtmenize olanak tanıyan ek imzaları vardır . Ayar inJustDecodeBoundsmülkü truedönen, kaçınır bellek ayırmayı kodlama yaparken nullbitmap nesnesi ama ayarı için outWidth, outHeightve outMimeType. Bu teknik, bitmap'in oluşturulmasından (ve bellek ayrılmasından) önce görüntü verilerinin boyutlarını ve türünü okumanızı sağlar.

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;

java.lang.OutOfMemoryİstisnaları önlemek için , bitmap'in kodunu çözmeden önce boyutlarını kontrol edin, eğer kaynağa mevcut belleğe rahatça uyan tahmin edilebilir boyutta görüntü verileri sağlama konusunda kesinlikle güvenmiyorsanız.


Belleğe ölçeklendirilmiş bir sürüm yükleme

Artık görüntü boyutları bilindiğine göre, tam görüntünün belleğe yüklenip yüklenmeyeceğine veya bunun yerine alt örneklenmiş bir sürümün yüklenip yüklenmeyeceğine karar vermek için kullanılabilirler. Dikkate alınması gereken bazı faktörler şunlardır:

  • Tam görüntüyü belleğe yüklemenin tahmini bellek kullanımı.
  • Uygulamanızın diğer bellek gereksinimleri göz önüne alındığında, bu görüntüyü yüklemeyi taahhüt ettiğiniz bellek miktarı.
  • Görüntünün yükleneceği hedef ImageView veya UI bileşeninin boyutları.
  • Mevcut cihazın ekran boyutu ve yoğunluğu.

Örneğin, nihayetinde bir 128x96 piksel küçük resim halinde görüntülenecekse, 1024x768 piksel boyutundaki bir görüntüyü belleğe yüklemeye değmez ImageView.

Bellek, set haline küçük bir versiyonu yükleme, görüntü alt örnek için dekoder söylemek gerekirse inSampleSizeetmek trueiçin de BitmapFactory.Optionsnesnenin. Örneğin, çözünürlüğü 2048x1536 olan inSampleSizeve 4'ten biriyle kodu çözülmüş bir görüntü yaklaşık 512x384 bitmap üretir. Bunu belleğe yüklemek, tam görüntü için 12MB yerine 0.75MB kullanır (bitmap yapılandırması olduğu varsayılarak ARGB_8888). Aşağıda, hedef genişlik ve yüksekliğe dayalı olarak ikisinin gücü olan bir örnek boyutu değerini hesaplamak için bir yöntem verilmiştir:

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

Not : İki değere sahip bir güç hesaplanır, çünkü kod çözücü inSampleSizebelgelere göre iki en yakın güce yuvarlanarak nihai bir değer kullanır .

Bu yöntemi kullanmak için önce inJustDecodeBoundsset ile kodunu çöz true, seçenekleri içinden geçir ve sonra yeni inSampleSizedeğeri kullanarak kodu çöz ve şöyle inJustDecodeBoundsayarla 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);
}

Bu yöntem ImageView, aşağıdaki örnek kodda gösterildiği gibi, 100x100 piksellik küçük resim görüntüleyen bir görüntüye rastgele büyük boyutlu bir bitmap yüklemeyi kolaylaştırır :

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

Diğer yöntemlerden bitmap'lerin kodunu çözmek için, uygun BitmapFactory.decode*yöntemi gerektiği gibi kullanarak benzer bir işlemi takip edebilirsiniz .


21
Bu cevap üzerine görüşülmekte olan meta
Rene

9
Bu cevap (bağlantıdan ulaşılan bilgiler hariç) bir cevap için çok fazla çözüm sunmaz. Bağlantının önemli kısımları soru ile birleştirilmelidir.
FallenAngel

7
Bu cevap, soru ve diğer cevaplar gibi Topluluk Wiki'si, bu yüzden topluluğun düzenleyerek düzeltebileceği bir şey, moderatör müdahalesi gerektirmeyen bir şey.
Martijn Pieters

içeriğe ve Kotlin desteğine güncel bağlantı şu adreste bulunabilir: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr

890

OutOfMemory hatasını düzeltmek için aşağıdakine benzer bir şey yapmalısınız:

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

Bu inSampleSizeseçenek bellek tüketimini azaltır.

İşte tam bir yöntem. İlk olarak, içeriğin kendisini çözmeden görüntü boyutunu okur. Sonra en iyi inSampleSizedeğeri bulur, 2 gücü olmalı ve son olarak görüntünün kodu çözülür.

// 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
Bununla birlikte, 10'un inSampleSize için en iyi değer olmayabileceğini unutmayın, belgeler 2'nin güçlerini kullanmanızı önerir.
Mirko N.

70
Chrispix ile aynı problemle karşı karşıyayım, ancak buradaki çözümün sorunu gerçekten çözdüğünü düşünmüyorum, aksine yanıyor. Örnek boyutunu değiştirmek, kullanılan bellek miktarını azaltır (muhtemelen görüntü önizlemesi için uygun olan görüntü kalitesi pahasına), ancak birden fazla görüntü akışı çözülürse, yeterince büyük bir görüntü akışı çözülürse istisnayı engellemez. deşifre. Daha iyi bir çözüm bulursam (ve olmayabilir) burada bir cevap göndereceğim.
Flynn81

4
Ekranı piksel yoğunluğunda eşleştirmek için sadece uygun bir boyuta ihtiyacınız vardır, yakınlaştırmak için daha yüksek bir yoğunlukta görüntünün bir örneğini alabilirsiniz.
stealthcopter

4
REQUIRED_SIZE, ölçeklendirmek istediğiniz yeni boyuttur.
Fedor

8
bu çözüm bana yardımcı oldu, ancak görüntü kalitesi korkunç. Görüntüleri herhangi bir öneri görüntülemek için bir viewfilpper kullanıyorum?
user1106888

372

Fedor'un kodunda küçük bir gelişme sağladım. Temelde aynı şeyi yapar, ama (bence) çirkin döngü olmadan ve her zaman iki güçle sonuçlanır. Orijinal çözümü yaptığı için Fedor'a Kudos, onu bulana kadar sıkıştım ve sonra bunu yapabildim :)

 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
Evet haklısın, o kadar da güzel değil. Sadece herkese açıklığa kavuşturmaya çalıştım. Kodun için teşekkürler.
Fedor

10
@Thomas Vervest - Bu kodla ilgili büyük bir sorun var. ^ 2'yi bir güce yükseltmez, sonuçla 2'yi xors eder. Math.pow'u (2.0, ...) istiyorsunuz. Aksi takdirde, bu iyi görünüyor.
DougW

6
Ooh, bu çok iyi bir tane! Benim hatam, hemen düzeltirim, cevap için teşekkürler!
Thomas Vervest

8
Her yeni çağrı için bir tane olmak üzere iki yeni FileInputStreams oluşturuyorsunuz BitmapFactory.decodeStream(). Bir finallyblokta kapatılabilmeleri için her birine bir referans kaydetmeniz gerekmez mi?
matsev

1
@Babibu Belgeler akışın sizin için kapalı olduğunu belirtmiyor, bu nedenle hala kapalı olması gerektiğini varsayıyorum. İlginç ve ilgili bir tartışma burada bulunabilir . Doğrudan tartışmamızla ilgili olan Adrian Smith'in yorumuna dikkat edin.
Thomas Vervest

232

İOS deneyiminden geliyorum ve bir görüntüyü yüklemek ve göstermek gibi temel bir şeyle ilgili bir sorun keşfetmek beni hayal kırıklığına uğrattı. Sonuçta, bu sorunu yaşayan herkes makul boyutta görüntüler göstermeye çalışıyor. Her neyse, işte sorunumu gideren iki değişiklik (ve uygulamamı çok duyarlı hale getirdi).

1) Eğer yapsak BitmapFactory.decodeXYZ(), bir geçmek için emin olun BitmapFactory.Optionsile inPurgeablekarşı kümesi true(ve tercihen ile inInputShareableayrıca ayarlı true).

2) ASLA kullanmayın Bitmap.createBitmap(width, height, Config.ARGB_8888) . ASLA demek istemiyorum! Birkaç şeyden sonra hafıza hatasını yükseltmeyen bir şey yaşamadım. Ne olursa olsun recycle(), hiçbir System.gc()yardım etmedi. Her zaman istisna yarattı. Gerçekten işe yarayan diğer bir yol, çizilebilir cihazlarınızda (veya yukarıdaki 1. adımı kullanarak deşifre ettiğiniz başka bir Bitmap) kukla bir görüntüye sahip olmak, bunu istediğiniz şekilde yeniden ölçeklendirmek, daha sonra elde edilen Bitmap'i manipüle etmek (örneğin bir Tuval'e geçirmek gibi) daha fazla eğlence için). Peki, bunun yerine kullanması gereken geçerli: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Herhangi bir nedenle kaba kuvvet oluşturma yöntemini KULLANMALISINIZ, en azından geçin Config.ARGB_4444.

Bu gün olmasa bile size zaman kazandıracak. Görüntüyü ölçekleme, vb. Hakkında konuşan her şey gerçekten işe yaramaz (yanlış boyut veya bozulmuş görüntü elde etmeyi bir çözüm olarak düşünmüyorsanız).


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;ve Bitmap.createScaledBitmap(srcBitmap, width, height, false);android 4.0.0'da bellek dışı istisna ile yaşadığım sorunum çözüldü. Teşekkürler dostum!
Jan-Terje Sørensen

5
Bitmap.createScaledBitmap () çağrısında, büyük olasılıkla flag parametresi olarak true kullanmalısınız. Aksi takdirde, görüntü büyütülürken görüntü kalitesi düzgün olmaz. Bu konuyu kontrol et stackoverflow.com/questions/2895065/…
rOrlig

11
Bu gerçekten harika bir tavsiye. Size bu inanılmaz çılgınca dink hata için Google görev için ekstra bir +1 verebilir diliyorum. Yani ... bir hata değilse, o zaman belgelerin gerçekten "BU FOTOĞRAFLARI NASIL SÜRÜYORSUN" diyen ciddi bir şekilde yanıp sönen neon tabelalara sahip olması gerekiyor, çünkü 2 yıldır bununla mücadele ediyorum ve şimdi bu yazıyı buldum. Büyük bulmak.
Yevgeny Simkin

Resimlerinizi küçültmek kesinlikle yardımcı olur, ancak bu önemli bir adımdır ve nihayetinde bu sorunu benim için çözdü. Sadece resimlerinizi ölçeklendirmeyle ilgili sorun, çok fazla resminiz varsa veya kaynak resimler çok büyükse, yine de aynı sorunla karşılaşabilirsiniz. +1 size Ephraim.
Dave

10
Lollipop itibariyle BitmapFactory.Options.inPurgeableve BitmapFactory.Options.inInputShareablekullanımdan kaldırıldığını developer.android.com/reference/android/graphics/...
Denis Kniazhev

93

Bu bir bilinen bir hata değil, çünkü büyük dosyaların var. Android Çekmeceleri Önbelleğe Aldığından, birkaç resim kullandıktan sonra bellek yetersiz kalıyor. Ama android varsayılan önbellek sistemini atlayarak bunun için alternatif bir yol buldum.

Çözüm : Görüntüleri "varlıklar" klasörüne taşıyın ve BitmapDrawable'ı almak için aşağıdaki işlevi kullanın:

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

Aynı sorunu yaşadım ve BitmapFactory.decodeStream veya decodeFile işlevlerinden kaçınarak çözdüm ve bunun yerine kullandım BitmapFactory.decodeFileDescriptor

decodeFileDescriptor decodeStream / decodeFile'dan farklı yerel yöntemler çağırıyor gibi görünüyor.

Her neyse, bu işe yaradı (bazılarının yukarıda olduğu gibi bazı seçenekler eklediğimi unutmayın, ancak farkı yaratan şey bu değil. Kritik olan, decodeStream veya decodeFile yerine BitmapFactory.decodeFileDescriptor çağrısıdır ):

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

Ben decodeStream / decodeFile kullanılan yerel işlevi ile ilgili bir sorun olduğunu düşünüyorum. DecodeFileDescriptor kullanırken farklı bir yerel yöntem çağrıldığını doğruladım. Ayrıca okuduğum şey, "Görüntüler (Bitmapler) standart bir Java biçiminde değil, yerel aramalar yoluyla ayrılıyor; ayırmalar sanal yığının dışında yapılır, ancak buna karşı sayılır! "


1
memeory dışında aynı sonucu, aslında hangi yöntemi kullandığınız önemli olmayacak bellekten veri okumak için tuttuğunuz bayt sayısına bağlıdır.
Piyush Mishra

72

Bence bundan kaçınmanın en iyi yolu OutOfMemoryError onunla yüzleşmek ve anlamaktır.

Kasten sebep olmak için bir uygulama yaptımOutOfMemoryErrorBellek kullanımını ve .

Bu uygulama ile çok sayıda deney yaptıktan sonra, aşağıdaki sonuçları aldım:

Önce Honey Comb'ten önce SDK sürümleri hakkında konuşacağım.

  1. Bitmap yerel yığın halinde depolanır, ancak çöpleri otomatik olarak toplar, geri dönüşüm () çağrısı gereksizdir.

  2. {VM yığın boyutu} + {ayrılmış yerel yığın belleği}> = {aygıt için VM yığın boyutu sınırı} ve bitmap oluşturmaya çalışıyorsanız, OOM atılır.

    DİKKAT: VM HEOC SIZE, VM ALLOCATED MEMORY yerine sayılır.

  3. Ayrılan VM belleği küçülse bile VM Yığın boyutu büyütüldükten sonra asla küçülmez.

  4. Bu nedenle, VM Yığın Boyutunun Bitmaps için kullanılabilir belleği kaydetmek için çok büyük büyümesini önlemek için en yüksek VM belleğini olabildiğince düşük tutmalısınız.

  5. El ile System.gc () çağrısı anlamsızdır, sistem yığın boyutunu büyütmeye çalışmadan önce onu çağırır.

  6. Yerel Öbek Boyutu da asla küçülmez, ancak OOM için sayılmaz, bu yüzden endişelenmenize gerek yoktur.

Ardından, Honey Comb'ten SDK Starts hakkında konuşalım.

  1. Bitmap VM yığınında depolanır, OOM için yerel bellek sayılmaz.

  2. OOM koşulu çok daha basittir: {VM yığın boyutu}> = {aygıt için VM yığın boyutu sınırı}.

  3. Bu nedenle, aynı yığın boyutu sınırına sahip bitmap oluşturmak için daha fazla belleğiniz var, OOM'nin atılması daha az olası.

İşte Çöp Toplama ve Bellek Sızıntısı ile ilgili gözlemlerimden bazıları.

App kendiniz görebilirsiniz. Bir Etkinlik, Etkinlik yok edildikten sonra hala çalışmakta olan bir AsyncTask yürütürse, AsyncTask işlemi bitene kadar Etkinlik çöp toplanmaz.

Bunun nedeni, AsyncTask'ın anonim bir iç sınıf örneği olması, Etkinliğin bir referansını tutmasıdır.

Görev arka plan iş parçacığında bir GÇ işleminde engellendiyse, AsyncTask.cancel (true) çağrıldığında yürütme durdurulmaz.

Geri aramalar da anonim iç sınıflardır, bu nedenle projenizdeki statik bir örnek onları tutuyor ve serbest bırakmazsa, bellek sızıntı yapacaktır.

Örneğin bir Zamanlayıcı gibi yinelenen veya geciken bir görev zamanladıysanız ve onPause () öğesinde cancel () ve purge () yöntemlerini çağırmazsanız, bellek sızdırılır.


AsyncTask "anonim bir iç sınıf örneği" olmak zorunda değildir ve aynı durum Geri Aramalar için de geçerlidir. Kendi dosyasında AsyncTask'i genişleten yeni bir ortak sınıf veya hatta private static classaynı sınıfta bir sınıf oluşturabilirsiniz. Faaliyete herhangi bir atıfta bulunmayacaklardır (elbette bir tane vermedikçe)
Simon Forsberg

65

Son zamanlarda OOM istisnaları ve önbelleğe alma hakkında birçok soru gördüm. Geliştirici kılavuzunda gerçekten iyi bir makale var var, ancak bazıları bunu uygun bir şekilde uygulama konusunda başarısız olma eğilimindedir.

Bu nedenle bir Android ortamında önbelleğe alma gösteren örnek bir uygulama yazdım. Bu uygulama henüz bir OOM kazanmamıştır.

Kaynak koduna bağlantı için bu cevabın sonuna bakın.

Gereksinimler:

  • Android API 2.1 veya üstü (API 1.6'daki bir uygulama için kullanılabilir belleği almayı başaramadım - API 1.6'da çalışmayan tek kod parçası)
  • Android destek paketi

Ekran görüntüsü

Özellikleri:

  • Tek birton kullanarak bir yön değişikliği varsa önbelleği korur
  • Önbelleğe atanmış uygulama belleğinin sekizinci bölümünü kullanın (isterseniz değiştirin)
  • Büyük bitmap'ler ölçeklenir (izin vermek istediğiniz maksimum pikselleri tanımlayabilirsiniz)
  • Bitmap'leri indirmeden önce bir internet bağlantısının olup olmadığını kontrol eder
  • Satır başına yalnızca bir görev başlattığınızdan emin olun
  • Eğer siz flinging olanListView koyma, sadece arasındaki bitmapleri indirmek olmayacak

Bu aşağıdakileri içermez:

  • Disk önbellekleme. Bunun uygulanması kolay olmalı - sadece bitmapleri diskten alan farklı bir göreve işaret et

Basit kod:

İndirilen görüntüler Flickr'daki görüntülerdir (75x75). Ancak, işlenmek istediğiniz görüntü URL'lerini koyun; uygulama, maksimum değeri aştığında görüntüyü küçültür. Bu uygulamada url'ler basitçe birString dizidedir.

LruCacheBitmaplerle başa iyi bir yol var. Ancak, bu uygulamada LruCacheuygulamayı daha uygulanabilir hale getirmek için oluşturduğum başka bir önbellek sınıfının bir örneğini koydum .

Cache.java'nın kritik öğeleri ( loadBitmap()yöntem en önemlisidir):

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

Disk önbelleği uygulamak istemiyorsanız, Cache.java dosyasında hiçbir şeyi düzenlemenize gerek yoktur.

MainActivity.java'nın kritik şeyleri:

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()çok sık aranır. Satır başına sonsuz miktarda iş parçacığı başlatmayacağımızdan emin olmamızı sağlayan bir denetim uygulamadıysak, normalde oradaki resimleri indirmek iyi bir fikir değildir. Cache.java rowObject.mBitmapUrlzaten bir görevde olup olmadığını kontrol eder ve eğer öyleyse başka bir görev başlatmaz. Bu nedenle, büyük olasılıkla çalışma sırası kısıtlamasınıAsyncTask havuzdan aşmıyoruz.

İndir:

Kaynak kodunu https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip adresinden indirebilirsiniz .


Son sözler:

Bunu birkaç hafta boyunca test ettim, henüz tek bir OOM istisnası almadım. Bunu emülatörde, Nexus One'ımda ve Nexus S'ımda test ettim. HD kalitesinde görüntüler içeren resim URL'lerini test ettim. Tek engel, indirmek için daha fazla zaman almasıdır.

OOM'un ortaya çıkacağını hayal edebileceğim tek bir senaryo var ve bu, çok, çok büyük görüntüler indirirsek ve ölçeklenip önbelleğe konmadan önce aynı anda daha fazla bellek kaplayacak ve bir OOM'ye neden olacak. Ama bu zaten ideal bir durum bile değil ve büyük olasılıkla daha uygulanabilir bir şekilde çözülmesi mümkün olmayacak.

Yorumlardaki hataları bildirin! :-)


43

Görüntüyü alıp anında yeniden boyutlandırmak için aşağıdakileri yaptım. Bu yardımcı olur umarım

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

26
Bu yaklaşım bitmap'i ölçeklendirir. Ancak OutOfMemory sorununu çözmez, çünkü tam bitmap yine de kodu çözülür.
Fedor

5
Eski koduma bakıp bakamayacağımı göreceğim, ama sanırım bellek dışı sorunlarımı çözdü. Eski kodumu iki kez kontrol edeceğim.
Chrispix

2
En azından bu örnekte, tam bitmap referansını tutmuyorsunuz, bu nedenle bellek tasarrufu sağlıyor.
NoBugs

Benim için bellek sorununu çözdü, ancak görüntülerin kalitesini düşürdü.
Pamela Sillah

35

Bu, çok farklı açıklamalarla çok uzun süren bir sorun gibi görünüyor. Burada sunulan en yaygın iki cevabın tavsiyesini aldım, ancak bunlardan hiçbiri VM'nin sürecin kod çözme kısmını gerçekleştirmenin baytları karşılayamayacağını iddia eden problemlerimi çözmedi . Bazı kazmalardan sonra, burada asıl sorunun NATIVE'dan uzaklaşan kod çözme sürecinin olduğunu öğrendim. yığınından .

Buraya bakın: BitmapFactory OOM beni deli ediyor

Bu da beni bu soruna birkaç çözüm daha bulduğum başka bir tartışma konusuna götürdü. Biri aramakSystem.gc(); resminiz görüntülendikten sonra manuel yapmaktır. Ancak bu, uygulamanızın yerel yığını azaltmak için DAHA FAZLA bellek kullanmasını sağlar. 2.0 (Donut) sürümünden itibaren daha iyi çözüm "inPurgeable" BitmapFactory seçeneğini kullanmaktır. Bu yüzden o2.inPurgeable=true;hemen ekledimo2.inSampleSize=scale; .

Bu konuda daha fazlası: Bellek yığını sınırı sadece 6M mi?

Şimdi, tüm bunları söyledikten sonra, ben de Java ve Android ile tam bir dunce. Eğer bunun bu sorunu çözmenin korkunç bir yolu olduğunu düşünüyorsanız, muhtemelen haklısınız demektir. ;-) Ama bu benim için harikalar yaradı ve şimdi VM'yi yığın önbellekten çalıştırmanın imkansız olduğunu gördüm. Bulabildiğim tek dezavantaj, önbelleğe alınmış çizilmiş resminizi çöpe atmanızdır. Bu, sağdaki görüntüye geri giderseniz, görüntüyü her seferinde yeniden çizdiğiniz anlamına gelir. Uygulamamın nasıl çalıştığı durumunda, bu gerçekten bir sorun değildir. Kilometreniz değişebilir.


inPurgeable sabit OOM benim için.
Artem Russakovskii

35

ne yazık ki Yukarıdakilerin Hiçbiri çalışmazsa, bunu Manifest dosyanıza ekleyin . İç uygulama etiketi

 <application
         android:largeHeap="true"

1
Bunun gerçekte ne yaptığını açıklayabilir misiniz? Sadece insanlara bunu eklemelerini söylemek yardımcı olmaz.
Gizli Rabbi

1
Bu çok kötü bir çözüm. Temelde sorunu gidermeye çalışmıyorsunuz. Bunun yerine android sisteminden uygulamanız için daha fazla yığın alanı ayırmasını isteyin. GC'nin belleği temizlemek için geniş yığın alanından geçmesi gerektiğinden uygulamanızın çok fazla pil gücü tüketmesi gibi uygulamanız üzerinde çok kötü etkileri olacaktır ve ayrıca uygulama performansınız daha yavaş olacaktır.
Prakash

2
o zaman android neden bu android eklememize izin veriyor: manifestimize largeHeap = "true"? Artık Android'e meydan okuyorsunuz.
Himanshu Mori

32

Bunu kullanın Bu, bitmap.recycle();görüntü kalitesi sorunu olmadan yardımcı olur.


9
API'ye göre, recycle () öğesini çağırmaya gerek yoktur.
Artem Russakovskii

28

Ben herhangi bir tür ölçekleme gerektirmeyen çok daha etkili bir çözüm var. Bitmap'inizi yalnızca bir kez deşifre edin ve ardından haritanın adına karşı önbelleğe alın. Ardından, bitmap'i ada karşı alın ve ImageView'da ayarlayın. Yapılması gereken başka bir şey yok.

Kod çözülmüş bitmap'in gerçek ikili verileri dalvik VM yığını içinde saklanmadığından bu işe yarayacaktır. Harici olarak saklanır. Bu nedenle, bir bitmap'in kodunu her çözdüğünüzde, GC yığınından hiçbir zaman GC tarafından geri alınmayan belleği ayırır

Bunu daha iyi anlamanıza yardımcı olmak için, ur resminizi çekilebilir klasörde tuttuğunuzu düşünün. Sadece bir getResources (). GetDrwable (R.drawable.) Yaparak görüntüyü elde edersiniz. Bu, görüntünüzün kodunu her seferinde ÇÖZMEZ, ancak kodu her çağrışınızda kodu çözülmüş bir örneği yeniden kullanın. Yani özünde önbelleğe alınmış.

Resminiz bir yerde bir dosyada (veya harici bir sunucudan geliyor olabilir) olduğundan, kodu çözülmüş bitmap örneğini gereken yerlerde yeniden kullanmak üzere önbelleğe almak sizin sorumluluğunuzdadır.

Bu yardımcı olur umarım.


4
"yazın ve ardından adını haritaya göre bir haritada önbelleğe alın." Resimlerinizi tam olarak nasıl önbelleğe alırsınız?
Vincent

3
Bunu gerçekten denedin mi? Piksel verileri aslında Dalvik yığınında saklanmamış olsa da, yerel bellekteki boyutu VM'ye rapor edilir ve kullanılabilir belleğine sayılır.
ErikR

3
@Vincent Sanırım onları bir Haritaya kaydetmek zor değil. HashMap <KEY, Bitmap> map gibi bir şey önerebilirim. KEY olarak bir yol aldığınızı varsayalım, map.put (Path, Bitmap) olarak saklarsınız ve map.get (Path)
Rafael T

3
Eğer bir görüntü Önbellek uyguluyorsanız HashMap <String, SoftReference <Bitmap>> kullanmak istiyorsunuz, aksi takdirde yine de bellek bitebilir - ayrıca "GC tarafından asla geri kazanılmayan VM yığınının dışında bellek ayırdığını düşünmüyorum "doğrudur, anladığım kadarıyla bellek geri kazanılır, çünkü bitmap.recycle () ne için erken bir ipucu olarak, bitmap.recycle () ne için olduğunu anlıyor ...
Dori

28

Aynı sorunu şu şekilde çözdüm.

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

hepsi doğru ama OnCreate çizmek daire için birden fazla bitmap kullanıyorum ve etkinlik bitmap temizlemek ve nasıl bitmap kaldırmak ve tekrar etkinlik 0nCreate ikinci kez oluşturmak için nasıl 4-5 kez çağırmak ..
ckpatel

27

Burada iki konu var ....

  • Bitmap belleği VM yığınında değil, yerel yığında - bkz. BitmapFactory OOM beni deli ediyor
  • Yerel yığın için çöp toplama işlemi VM yığınından daha tembeldir - bu nedenle bir Etkinliğin onPause veya onDestroy'undan her geçtiğinizde bitmap.recycle ve bitmap = null yapmak konusunda oldukça agresif olmanız gerekir.

Android 2.3+'den beri VM yığınında
FindOut_Quran

27

Bu benim için çalıştı!

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

Yukarıdaki cevapların hiçbiri benim için işe yaramadı, ancak sorunu çözen korkunç çirkin bir çözüm buldum. Projeme kaynak olarak çok küçük, 1x1 piksel bir resim ekledim ve çöp toplama işlemini çağırmadan önce ImageView'e yükledim. ImageView'in Bitmap'i serbest bırakmamış olabileceğini düşünüyorum, bu yüzden GC asla almadı. Çirkin, ama şimdilik çalışıyor gibi görünüyor.

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.

imageView, bitmap'i tek başına geri dönüştürmüyor gibi görünüyor. Bana yardım etti, teşekkürler
Dmitry Zaytsev

@Mike imageloader'ın tam kodunu ekleyebilir veya bana bitmap görüntülerini yükleme bağlantısını verebilir misiniz? Bitmap üzerinde geri dönüşüm kullanırsam tüm liste görünümüm görüntülenir, ancak tüm öğeler boş gösterilir
TNR

@Mike u çöp toplama çağırmadan önce imageView = null yapmak iyi olup olmadığını söyleyebilir miyim?
Youddh

@TNR Burada eksik olduğunu düşünüyorum bitmapyukarıdaki kodda bir önceki görüntülenen görüntü, geri dönüşüm gerekir, herhangi bir referans temizlemek, imageViewküçük bir yedek ayarlayarak da unutmak gc(); ve tüm bunlardan sonra: YENİ görüntünüzü yukarıdaki kodda yükleyin bitmapve görüntüleyin ....
TWiStErRob

Bu yanlış. Bitmap'i geri dönüştürmeden ÖNCE imageView içeriğini her zaman temizlemelisiniz (gerçekte görüntülenip kullanılırken değil).
FindOut_Quran

20

Burada harika cevaplar, ama bu sorunu çözmek için tamamen kullanılabilir bir sınıf istedim .. Ben de yaptım.

İşte benim BitmapHelper sınıfı OutOfMemoryError kanıtı :-)

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

}

Bunu kullanan herkese: Ben sadece bir hata düzeltildi: "int scaledBitmapHeight = scaledBitmap.getWidth ();" yanlış (tabii ki. "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal

19

Bu benim için çalışıyor.

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

ve bu C # monodroidinde. görüntünün yolunu kolayca değiştirebilirsiniz. Burada önemli olan ayarlanacak seçenekler.


16

Bu, görüntüleri yüklemek ve işlemek için yardımcı program sınıfımı toplulukla paylaşmak için uygun bir yer gibi görünüyor, onu kullanabilir ve özgürce değiştirebilirsiniz.

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

Benim uygulama birinde ben de resim çekmek gerekiyor Camera/Gallery. Kullanıcı Kameradan görüntüyü tıklarsa (2MP, 5MP veya 8MP olabilir), görüntü boyutu kBs'den s'ye değişir MB. Görüntü boyutu kodun üzerinde daha az (veya 1-2 MB'a kadar) iyi çalışıyorsa, ancak 4MB veya 5MB'ın üzerinde bir resmim varsaOOM çerçeve içinde :(

o zaman bu sorunu çözmek için çalıştı ve nihayet Fedor (Bu güzel bir çözüm yapmak için Fedor Tüm Kredi) kodu aşağıdaki iyileştirme yaptık :)

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;

}

Umarım bu aynı problemle karşılaşan arkadaşlara yardımcı olur!

daha bakın bu


14

Birkaç dakika önce bu konuya girdim. Liste görünümü bağdaştırıcımı daha iyi bir iş yaparak çözdüm. Kullandığım yüzlerce 50x50 piksel görüntü ile ilgili bir sorun olduğunu düşündüm, satır her gösterildiğinde özel görünümümü şişirmeye çalıştığım ortaya çıktı. Sadece satır şişirilmiş olup olmadığını test ederek bu hatayı ortadan kaldırdım ve yüzlerce bitmap kullanıyorum. Bu aslında bir Spinner içindir, ancak temel adaptör bir ListView için aynı şekilde çalışır. Bu basit düzeltme, bağdaştırıcının performansını da büyük ölçüde geliştirdi.

@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
Bunun için sana yeterince teşekkür edemem! Bunu görmeden önce yanlış problemi takip ediyordum. Ama sizin için soru: Her veya benim liste satırları benzersiz bir isim ve fotoğraf olduğundan her satırın değerlerini korumak için bir convertView dizi kullanmak zorunda kaldı. Tek bir değişken kullanmanın bunu yapmanıza nasıl izin verdiğini göremedim. Bir şey mi kaçırıyorum?
PeteH

13

Bütün gün bu çözümleri test ederek geçirdim ve benim için işe yarayan tek şey, görüntüyü elde etmek ve GC'yi manuel olarak aramak için gerekli yaklaşımlar, biliyorum ki gerekli olması gerekmiyor, ama işe yarayan tek şey uygulamalarımı ağır yük testi altına aldığımda etkinlikler arasında geçiş yapıyorum. Uygulamamın bir liste görünümünde küçük resim görüntülerinin bir listesi var (etkinlik A diyelim) ve bu görüntülerden birine tıkladığınızda o öğe için ana görüntüyü gösteren başka bir etkinliğe (B etkinliği diyelim) yönlendirilir. İki etkinlik arasında gidip geldiğimde, sonunda OOM hatası alırdım ve uygulama kapanmaya zorlardı.

Ben liste görünümü yarıya indiğinde çökecekti.

Şimdi, B aktivitesinde aşağıdakileri uyguladığımda, tüm liste görünümünü sorunsuz bir şekilde geçirebilir ve devam etmeyi ve devam etmeyi ve çok hızlı bir şekilde devam edebilirim.

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

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

Çözümünüzü seviyorum! Ben de bu hatayı çözmek için saatler geçirdim, çok sinir bozucu! Düzenleme: Ne yazık ki, manzara modunda ekran yönümü değiştirdiğimde sorun hala var ...
Xarialon

Bu sonunda bana yardımcı oldu: - BitmapFactory.Options seçenekleri = yeni BitmapFactory.Options (); Seçenekler.InPurgeable = true; options.InSampleSize = 2;
user3833732

13

Bu sorun yalnızca Android emülatörlerinde gerçekleşir. Ayrıca bir emülatörde bu sorunla karşılaştım ama bir cihazı kontrol ettiğimde işe yaradı.

Lütfen bir cihazı kontrol edin. Cihazda çalıştırılabilir.


12

Benim 2 sent: OOM hatalarımı bitmap'lerle çözdüm:

a) Görüntülerimi 2 kat ölçekleme

b) bir ListView için özel Bağdaştırıcımda Picasso kitaplığını kullanarak , getView'de bir çağrı şöyle:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


Picasso'dan bahsettiğinize sevindim, çünkü görüntüleri yüklemeyi çok daha kolay hale getiriyor. Özellikle uzaktan depolananlar.
Chrispix

12

bu kodu SdCard veya bitmap nesnesini dönüştürmek için çizilebilir her görüntü için kullanın.

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;

ImageData_Path.get (img_pos) .getPath () resim yolunuzu kullanın .


12

Genellikle android cihaz yığın boyutu sadece 16MB (cihaza / işletim sistemine göre değişir ) Yığın Boyutlarına bakın ), eğer görüntüleri yüklüyorsanız ve 16MB boyutunu aşarsa, bitmap'i kullanmak yerine bellek istisnasını atar, yükleme SD karttan veya kaynaklardan veya hatta ağdan görüntüler getImageUri'yi kullanmaya çalışın , bitmap'i yüklemek daha fazla bellek gerektirir veya bu bitmap ile işiniz bittiğinde bitmap'i null değerine ayarlayabilirsiniz.


1
Ve setImageURI hala istisna alıyorsa, bu stackoverflow.com/questions/15377186/…
Mahesh

11

Buradaki tüm çözümlerin IMAGE_MAX_SIZE ayarlanması gerekir. Bu, daha güçlü donanıma sahip cihazları sınırlar ve görüntü boyutu çok düşükse HD ekranda çirkin görünür.

Samsung Galaxy S3'üm ve daha az güçlü olanlar da dahil olmak üzere diğer birkaç cihazla, daha güçlü bir cihaz kullanıldığında daha iyi görüntü kalitesiyle çalışan bir çözümle ortaya çıktım.

Bunun amacı, belirli bir cihazda uygulama için ayrılan maksimum belleği hesaplamak, ardından ölçeği bu belleği aşmadan mümkün olan en düşük değere ayarlamaktır. İşte 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;
}

Bu bitmap tarafından kullanılan maksimum belleği, ayrılan maksimum belleğin% 25'i olarak ayarladım, bunu ihtiyaçlarınıza göre ayarlamanız ve bu bitmap'in temizlendiğinden ve kullanmayı bitirdiğinizde bellekte kalmamanız gerekebilir. Genelde bu kodu görüntü döndürme (kaynak ve hedef bitmap) gerçekleştirmek için kullanıyorum, böylece uygulamamın aynı anda 2 bitmap'i belleğe yüklemesi gerekiyor ve% 25 bana görüntü döndürme yaparken bellek tükenmeden iyi bir tampon veriyor.

Umarım bu birisine yardım eder ..


11

Bu OutofMemoryException, System.gc()vb. Çağrılarak tamamen çözülemez .

Etkinlik Yaşam Döngüsüne başvurarak

Etkinlik Durumları, her işlem için bellek kullanımına ve her işlemin önceliğine tabi olarak işletim sisteminin kendisi tarafından belirlenir.

Kullanılan bitmap resimlerinin her birinin boyutunu ve çözünürlüğünü göz önünde bulundurabilirsiniz. Boyutu azaltmanızı, daha düşük çözünürlüğe yeniden örneklemenizi, galerilerin tasarımına bakın (bir küçük resim PNG ve bir orijinal resim.)


11

Bu kod, çekmeceden büyük bitmap yüklemeye yardımcı olacaktır

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

Güzel, Async Task yerine Loader kullanmanın daha iyi olacağını mı düşünüyorsun?
Chrispix

Nasıl Bitmap.Config.ARGB_565? Yüksek kalite kritik değilse.
Hamzeh Soboh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.