Aneh dari masalah memori saat memuat gambar ke objek Bitmap

1289

Saya memiliki tampilan daftar dengan beberapa tombol gambar di setiap baris. Ketika Anda mengklik baris daftar, itu meluncurkan aktivitas baru. Saya harus membuat tab sendiri karena ada masalah dengan tata letak kamera. Aktivitas yang diluncurkan untuk hasilnya adalah peta. Jika saya mengklik tombol saya untuk meluncurkan pratinjau gambar (memuat gambar dari kartu SD) aplikasi kembali dari aktivitas kembali ke listviewaktivitas ke pengendali hasil untuk meluncurkan kembali aktivitas baru saya yang tidak lebih dari widget gambar.

Pratinjau gambar pada tampilan daftar sedang dilakukan dengan kursor dan ListAdapter. Ini membuatnya cukup sederhana, tetapi saya tidak yakin bagaimana saya dapat menempatkan ukuran gambar (Yaitu ukuran bit lebih kecil bukan pixel sebagai srcuntuk tombol gambar on the fly. Jadi saya hanya mengubah ukuran gambar yang keluar dari kamera ponsel.

Masalahnya adalah saya mendapatkan kesalahan memori saat mencoba kembali dan meluncurkan kembali aktivitas ke-2.

  • Apakah ada cara saya bisa membuat daftar adaptor dengan mudah baris demi baris, di mana saya dapat mengubah ukuran dengan cepat ( sedikit bijak )?

Ini akan lebih baik karena saya juga perlu membuat beberapa perubahan pada properti widget / elemen di setiap baris karena saya tidak dapat memilih baris dengan layar sentuh karena masalah fokus. ( Saya bisa menggunakan roller ball. )

  • Saya tahu saya bisa melakukan perubahan ukuran band dan menyimpan gambar saya, tapi itu bukan yang ingin saya lakukan, tetapi beberapa kode contoh untuk itu akan menyenangkan.

Segera setelah saya menonaktifkan gambar pada tampilan daftar itu berfungsi dengan baik lagi.

FYI: Ini adalah bagaimana saya melakukannya:

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

Dimana R.id.imagefilenamea ButtonImage.

Ini LogCat saya:

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 

Saya juga memiliki kesalahan baru saat menampilkan gambar:

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
Chrispix
sumber
8
Saya menyelesaikan ini dengan menghindari Bitmap.decodeStream atau decodeFile dan menggunakan metode BitmapFactory.decodeFileDescriptor.
Fraggle
1
Saya juga menghadapi masalah yang sama beberapa minggu lalu dan saya menyelesaikannya dengan menurunkan gambar ke titik optimal. Saya telah menulis pendekatan lengkap di codingjunkiesforum.wordpress.com/2014/06/12/… blog saya dan mengunggah proyek sampel lengkap dengan kode rawan OOM vs kode bukti OOM athttps: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat
5
Jawaban yang diterima untuk pertanyaan ini sedang dibahas dalam meta
rene
4
Ini terjadi ketika Anda tidak membaca panduan pengembang Android
Pedro Varela
2
Ini terjadi karena arsitektur android yang buruk. Seharusnya mengubah ukuran gambar itu sendiri seperti ios dan UWP melakukan ini. Saya tidak harus melakukan hal ini sendiri. Pengembang Android terbiasa dengan neraka itu dan berpikir itu berfungsi sebagaimana mestinya.
Akses Ditolak

Jawaban:

651

Kelas Pelatihan Android , " Menampilkan Bitmap dengan Efisien ", menawarkan beberapa informasi hebat untuk memahami dan menangani pengecualian java.lang.OutOfMemoryError: bitmap size exceeds VM budgetsaat memuat Bitmap.


Baca Dimensi dan Jenis Bitmap

The BitmapFactorykelas menyediakan beberapa metode decoding ( decodeByteArray(), decodeFile(), decodeResource(), dll) untuk menciptakan Bitmapdari berbagai sumber. Pilih metode decode yang paling tepat berdasarkan sumber data gambar Anda. Metode ini berupaya mengalokasikan memori untuk bitmap yang dibangun dan karenanya dapat dengan mudah menghasilkan OutOfMemorypengecualian. Setiap jenis metode decode memiliki tanda tangan tambahan yang memungkinkan Anda menentukan opsi decoding melalui BitmapFactory.Optionskelas. Mengatur inJustDecodeBoundsproperti ke truesaat decoding menghindari alokasi memori, kembali nullke objek bitmap tetapi pengaturan outWidth, outHeightdan outMimeType. Teknik ini memungkinkan Anda untuk membaca dimensi dan tipe data gambar sebelum konstruksi (dan alokasi memori) dari bitmap.

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;

Untuk menghindari java.lang.OutOfMemorypengecualian, periksa dimensi bitmap sebelum mendekodekannya, kecuali jika Anda benar-benar memercayai sumber untuk memberi Anda data gambar berukuran yang dapat diprediksi yang sesuai dengan memori yang tersedia.


Muat versi yang diperkecil ke dalam Memori

Sekarang setelah dimensi gambar diketahui, mereka dapat digunakan untuk memutuskan apakah gambar penuh harus dimuat ke dalam memori atau jika versi subsampel harus dimuat sebagai gantinya. Berikut adalah beberapa faktor yang perlu dipertimbangkan:

  • Diperkirakan penggunaan memori memuat gambar penuh dalam memori.
  • Jumlah memori yang bersedia Anda berkomitmen untuk memuat gambar ini mengingat persyaratan memori lain dari aplikasi Anda.
  • Dimensi target ImageView atau komponen UI tempat gambar dimasukkan.
  • Ukuran layar dan kepadatan perangkat saat ini.

Misalnya, tidak layak memuat gambar 1024x768 piksel ke dalam memori jika akhirnya akan ditampilkan dalam thumbnail 128x96 piksel dalam ImageView.

Untuk memberitahu decoder untuk melakukan subsampel gambar, memuat versi yang lebih kecil ke dalam memori, atur inSampleSizeke truedalam BitmapFactory.Optionsobjek Anda . Sebagai contoh, sebuah gambar dengan resolusi 2048x1536 yang diterjemahkan dengan an inSampleSizeof 4 menghasilkan bitmap sekitar 512x384. Memuat ini ke dalam memori menggunakan 0.75MB daripada 12MB untuk gambar penuh (dengan asumsi konfigurasi bitmap ARGB_8888). Berikut adalah metode untuk menghitung nilai ukuran sampel yang merupakan kekuatan dua berdasarkan lebar dan tinggi target:

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

Catatan : Kekuatan dua nilai dihitung karena dekoder menggunakan nilai akhir dengan membulatkan ke kekuatan terdekat dari dua, sesuai inSampleSizedokumentasi.

Untuk menggunakan metode ini, pertama decode dengan inJustDecodeBoundsset to true, melewati opsi melalui dan kemudian decode lagi menggunakan nilai baru inSampleSizedan inJustDecodeBoundsset ke 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);
}

Metode ini membuatnya mudah untuk memuat bitmap ukuran besar secara sewenang-wenang ke dalam ImageViewyang menampilkan thumbnail 100x100 piksel, seperti yang ditunjukkan dalam kode contoh berikut:

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

Anda dapat mengikuti proses serupa untuk memecahkan kode bitmap dari sumber lain, dengan mengganti BitmapFactory.decode*metode yang sesuai sesuai kebutuhan.

Sazid
sumber
21
Jawaban ini sedang dibahas pada meta
rene
9
Jawaban ini (kecuali informasi yang diperoleh melalui tautan) tidak menawarkan banyak solusi seperti jawaban. Bagian penting dari tautan harus digabungkan ke dalam pertanyaan.
FallenAngel
7
Jawaban ini, seperti pertanyaan dan jawaban lainnya adalah Komunitas Wiki, jadi ini adalah sesuatu yang dapat diperbaiki oleh komunitas dengan mengedit, sesuatu yang tidak memerlukan intervensi moderator.
Martijn Pieters
tautan saat ini ke konten dan dukungan Kotlin dapat ditemukan di: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr
891

Untuk memperbaiki kesalahan OutOfMemory, Anda harus melakukan sesuatu seperti ini:

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

inSampleSizeOpsi ini mengurangi konsumsi memori.

Inilah metode yang lengkap. Pertama membaca ukuran gambar tanpa men-decoding konten itu sendiri. Kemudian ia menemukan nilai terbaik inSampleSize, itu harus kekuatan 2, dan akhirnya gambar diterjemahkan.

// 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;
}
Fedor
sumber
31
Perhatikan bahwa 10 mungkin bukan nilai terbaik untuk inSampleSize, dokumentasi menyarankan menggunakan kekuatan 2.
Mirko N.
70
Saya menghadapi masalah yang sama dengan Chrispix, tapi saya tidak berpikir solusi di sini benar-benar menyelesaikan masalah, tetapi lebih menghindarinya. Mengubah ukuran sampel mengurangi jumlah memori yang digunakan (dengan biaya kualitas gambar, yang mungkin oke untuk pratinjau gambar), tetapi itu tidak akan mencegah pengecualian jika aliran gambar yang cukup besar diterjemahkan, jika beberapa aliran gambar diterjemahkan. Jika saya menemukan solusi yang lebih baik (dan mungkin tidak ada) saya akan mengirim jawaban di sini.
Flynn81
4
Anda hanya perlu ukuran yang sesuai untuk menyesuaikan layar dalam kerapatan piksel, untuk memperbesar dan semacamnya, Anda dapat mengambil sampel gambar dengan kerapatan yang lebih tinggi.
stealthcopter
4
REQUIRED_SIZE adalah ukuran baru yang ingin Anda skala.
Fedor
8
solusi ini membantu saya tetapi kualitas gambarnya mengerikan. Saya menggunakan viewfilpper untuk menampilkan gambar saran?
user1106888
373

Saya telah membuat sedikit perbaikan pada kode Fedor. Ini pada dasarnya melakukan hal yang sama, tetapi tanpa (menurut saya) jelek saat loop dan selalu menghasilkan kekuatan dua. Kudos to Fedor untuk membuat solusi asli, saya terjebak sampai saya menemukannya, dan kemudian saya bisa membuat yang ini :)

 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;
}
Thomas Vervest
sumber
40
Ya Anda benar sementara tidak begitu indah. Saya hanya mencoba menjelaskan kepada semua orang. Terima kasih untuk kodemu.
Fedor
10
@ Thomas Vervest - Ada masalah besar dengan kode itu. ^ tidak menaikkan 2 ke kekuatan, itu mengoreksi 2 dengan hasilnya. Anda ingin Math.pow (2.0, ...). Kalau tidak, ini terlihat bagus.
DougW
6
Ooh, itu sangat bagus! Buruk saya, saya akan segera memperbaikinya, terima kasih atas jawabannya!
Thomas Vervest
8
Anda membuat dua FileInputStreams baru, satu untuk setiap panggilan ke BitmapFactory.decodeStream(). Tidakkah Anda harus menyimpan referensi untuk masing-masing sehingga mereka dapat ditutup di finallyblok?
matsev
1
@Babibu Dokumentasi tidak menyatakan bahwa aliran ditutup untuk Anda, oleh karena itu saya menganggap itu masih harus ditutup. Diskusi yang menarik dan terkait dapat ditemukan di sini . Perhatikan komentar oleh Adrian Smith, yang berhubungan langsung dengan debat kami.
Thomas Vervest
233

Saya berasal dari pengalaman iOS dan saya frustrasi menemukan masalah dengan sesuatu yang sangat mendasar seperti memuat dan menampilkan gambar. Lagi pula, semua orang yang mengalami masalah ini mencoba menampilkan gambar berukuran cukup. Bagaimanapun, berikut adalah dua perubahan yang memperbaiki masalah saya (dan membuat aplikasi saya sangat responsif).

1) Setiap kali Anda melakukannya BitmapFactory.decodeXYZ(), pastikan untuk lulus BitmapFactory.Optionsdengan inPurgeableset ke true(dan lebih disukai dengan inInputShareablejuga set ke true).

2) JANGAN PERNAH digunakan Bitmap.createBitmap(width, height, Config.ARGB_8888). Maksud saya TIDAK PERNAH! Saya tidak pernah mengalami hal yang tidak meningkatkan kesalahan memori setelah melewati beberapa. Tidak ada jumlah recycle(), System.gc(), apa pun membantu. Itu selalu menimbulkan pengecualian. Salah satu cara lain yang benar-benar berfungsi adalah memiliki gambar dummy di drawables Anda (atau Bitmap lain yang Anda dekodekan menggunakan langkah 1 di atas), skala ulang ke apa pun yang Anda inginkan, kemudian memanipulasi Bitmap yang dihasilkan (seperti meneruskannya ke Kanvas untuk lebih banyak kesenangan). Jadi, apa yang harus Anda gunakan sebagai gantinya adalah: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Jika karena alasan apa pun Anda HARUS menggunakan metode brute force create, maka setidaknya lulus Config.ARGB_4444.

Ini hampir dijamin untuk menghemat waktu Anda jika bukan berhari-hari. Semua yang berbicara tentang penskalaan gambar, dll. Tidak benar-benar berfungsi (kecuali jika Anda menganggap ukuran yang salah atau gambar yang terdegradasi sebagai solusi).

Efraim
sumber
22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;dan Bitmap.createScaledBitmap(srcBitmap, width, height, false);memecahkan masalah saya dengan pengecualian memori di Android 4.0.0. Terima kasih sobat!
Jan-Terje Sørensen
5
Dalam panggilan Bitmap.createScaledBitmap () Anda mungkin harus menggunakan true sebagai parameter flag. Kalau tidak, kualitas gambar tidak akan mulus saat ditingkatkan. Periksa utas ini stackoverflow.com/questions/2895065/…
rOrlig
11
Itu benar-benar nasihat yang luar biasa. Seandainya saya bisa memberi Anda +1 ekstra untuk membawa Google ke tugas untuk bug dink yang luar biasa ini. Maksud saya ... jika itu bukan bug maka dokumentasi benar-benar perlu memiliki beberapa tanda neon yang berkedip-kedip serius yang mengatakan "INI BAGAIMANA ANDA PROSES FOTO", karena saya telah berjuang dengan ini selama 2 tahun dan baru saja menemukan posting ini. Great ditemukan.
Yevgeny Simkin
Menurunkan gambar Anda pasti membantu, tetapi ini merupakan langkah penting dan apa yang akhirnya memecahkan masalah ini bagi saya. Masalah dengan hanya menskala gambar Anda adalah jika Anda memiliki banyak gambar, atau jika gambar sumber sangat besar maka Anda masih dapat mengalami masalah yang sama. +1 untuk Anda Efraim.
Dave
10
Pada Lollipop, BitmapFactory.Options.inPurgeabledan BitmapFactory.Options.inInputShareablesudah ditinggalkan developer.android.com/reference/android/graphics/…
Denis Kniazhev
93

Ini adalah bug yang dikenal , bukan karena file besar. Karena Android Cache Drawables, itu kehabisan memori setelah menggunakan beberapa gambar. Tapi saya telah menemukan cara alternatif untuk itu, dengan melewatkan sistem cache default android.

Solusi : Pindahkan gambar ke folder "aset" dan gunakan fungsi berikut untuk mendapatkan 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);
}
Anto Binish Kaspar
sumber
79

Saya memiliki masalah yang sama dan menyelesaikannya dengan menghindari fungsi BitmapFactory.decodeStream atau decodeFile dan sebagai gantinya digunakan BitmapFactory.decodeFileDescriptor

decodeFileDescriptor sepertinya memanggil metode asli yang berbeda dari decodeStream / decodeFile.

Ngomong - ngomong , apa yang berhasil adalah ini (perhatikan bahwa saya menambahkan beberapa opsi seperti beberapa di atas, tetapi bukan itu yang membuat perbedaan. Yang penting adalah panggilan ke BitmapFactory.decodeFileDescriptor alih-alih decodeStream atau 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;                        
}

Saya pikir ada masalah dengan fungsi asli yang digunakan di decodeStream / decodeFile. Saya telah mengkonfirmasi bahwa metode asli yang berbeda dipanggil saat menggunakan decodeFileDescriptor. Juga yang saya baca adalah "bahwa Gambar (Bitmap) tidak dialokasikan dalam cara Java standar tetapi melalui panggilan asli; alokasi dilakukan di luar tumpukan virtual, tetapi dihitung menentangnya! "

Fraggle
sumber
1
hasil yang sama dari memeory, sebenarnya itu tidak masalah metode mana yang Anda gunakan tergantung pada jumlah byte yang Anda pegang untuk membaca data yang memberikan dari memori.
PiyushMishra
72

Saya pikir cara terbaik untuk menghindari OutOfMemoryErroradalah dengan menghadapinya dan memahaminya.

Saya membuat aplikasi untuk sengaja menyebabkan OutOfMemoryError, dan memonitor penggunaan memori.

Setelah saya melakukan banyak percobaan dengan Aplikasi ini, saya mendapat kesimpulan berikut:

Saya akan berbicara tentang versi SDK sebelum Honey Comb dulu.

  1. Bitmap disimpan di heap asli, tetapi itu akan mengumpulkan sampah secara otomatis, panggilan daur ulang () tidak perlu.

  2. Jika {VM heap size} + {memory heap native dialokasikan}> = {VM heap size limit untuk perangkat}, dan Anda mencoba membuat bitmap, OOM akan dilempar.

    PEMBERITAHUAN: VM HEAP SIZE lebih dihitung daripada MEMORY ALLOCATED VM.

  3. Ukuran VM Heap tidak akan pernah menyusut setelah tumbuh, bahkan jika memori VM yang dialokasikan menyusut.

  4. Jadi Anda harus menjaga memori VM puncak serendah mungkin untuk menjaga VM Heap Size tidak tumbuh terlalu besar untuk menghemat memori yang tersedia untuk Bitmap.

  5. Secara manual memanggil System.gc () tidak ada artinya, sistem akan memanggilnya terlebih dahulu sebelum mencoba menumbuhkan ukuran tumpukan.

  6. Native Heap Size tidak akan menyusut juga, tetapi tidak dihitung untuk OOM, jadi tidak perlu khawatir.

Lalu, mari kita bicara tentang Mulai SDK dari Honey Comb.

  1. Bitmap disimpan dalam tumpukan VM, Memori asli tidak dihitung untuk OOM.

  2. Kondisi untuk OOM jauh lebih sederhana: {VM heap size}> = {VM heap size limit untuk perangkat}.

  3. Jadi Anda memiliki lebih banyak memori yang tersedia untuk membuat bitmap dengan batas ukuran tumpukan yang sama, OOM lebih kecil kemungkinannya untuk dibuang.

Berikut adalah beberapa pengamatan saya tentang Pengumpulan Sampah dan Kebocoran Memori.

Anda dapat melihatnya sendiri di App. Jika suatu Kegiatan mengeksekusi AsyncTask yang masih berjalan setelah Kegiatan dihancurkan, Kegiatan tersebut tidak akan mengumpulkan sampah sampai AsyncTask selesai.

Ini karena AsyncTask adalah turunan dari kelas dalam anonim, yang memegang referensi Kegiatan.

Memanggil AsyncTask.cancel (true) tidak akan menghentikan eksekusi jika tugas diblokir dalam operasi IO di utas latar belakang.

Callback juga merupakan kelas dalam anonim, jadi jika instance statis dalam proyek Anda menahannya dan tidak melepaskannya, memori akan bocor.

Jika Anda menjadwalkan tugas yang berulang atau tertunda, misalnya Timer, dan Anda tidak memanggil cancel () dan purge () di onPause (), memori akan bocor.

coocood
sumber
AsyncTask tidak harus menjadi "turunan dari kelas batin anonim", dan hal yang sama berlaku untuk Callbackks. Anda dapat membuat kelas publik baru di file itu sendiri yang memperluas AsyncTask, atau bahkan private static classdi kelas yang sama. Mereka tidak akan memegang referensi apa pun untuk Kegiatan (kecuali jika Anda memberi mereka satu saja)
Simon Forsberg
65

Saya telah melihat banyak pertanyaan tentang pengecualian OOM dan caching belakangan ini. Panduan pengembang memiliki artikel yang sangat bagus tentang ini, tetapi beberapa cenderung gagal dalam mengimplementasikannya dengan cara yang sesuai.

Karena itu saya menulis contoh aplikasi yang menunjukkan caching di lingkungan Android. Implementasi ini belum mendapatkan OOM.

Lihatlah di akhir jawaban ini untuk tautan ke kode sumber.

Persyaratan:

  • Android API 2.1 atau lebih tinggi (saya tidak bisa mendapatkan memori yang tersedia untuk aplikasi di API 1.6 - itu adalah satu-satunya bagian dari kode yang tidak berfungsi di API 1.6)
  • Paket dukungan Android

Tangkapan layar

Fitur:

  • Mempertahankan cache jika ada perubahan orientasi , menggunakan singleton
  • Gunakan seperdelapan dari memori aplikasi yang ditugaskan ke cache (modifikasi jika Anda mau)
  • Bitmap besar akan diskalakan (Anda dapat menentukan piksel maksimum yang ingin Anda izinkan)
  • Mengontrol bahwa ada koneksi internet yang tersedia sebelum mengunduh bitmap
  • Memastikan bahwa Anda hanya membuat satu tugas per baris
  • Jika Anda melemparkan yang ListViewjauh, itu hanya tidak akan men-download bitmap antara

Ini tidak termasuk:

  • Caching disk. Ini seharusnya mudah diimplementasikan - tunjuk saja tugas yang berbeda yang mengambil bitmap dari disk

Kode sampel:

Gambar yang sedang diunduh adalah gambar (75x75) dari Flickr. Namun, letakkan url gambar apa pun yang Anda ingin diproses, dan aplikasi akan menurunkannya jika melebihi maksimum. Dalam aplikasi ini url-nya cukup dalamString array.

The LruCachememiliki cara yang baik untuk menangani bitmap. Namun, dalam aplikasi ini saya meletakkan instance LruCachedi dalam kelas cache lain yang saya buat untuk mendapatkan aplikasi yang lebih layak.

Hal-hal penting Cache.java ( loadBitmap()metode ini yang paling penting):

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

Anda seharusnya tidak perlu mengedit apa pun di file Cache.java kecuali Anda ingin mengimplementasikan cache caching.

Hal-hal penting 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()dipanggil sangat sering. Biasanya bukan ide yang baik untuk mengunduh gambar di sana jika kami belum menerapkan pemeriksaan yang memastikan bahwa kami tidak akan memulai jumlah utas per baris yang tidak terbatas. Cache.java memeriksa apakah rowObject.mBitmapUrlsudah ada dalam tugas dan jika sudah, itu tidak akan memulai yang lain. Oleh karena itu, kemungkinan besar kami tidak melebihi batasan antrian kerja dariAsyncTask kumpulan.

Unduh:

Anda dapat mengunduh kode sumber dari https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Kata-kata terakhir:

Saya telah menguji ini selama beberapa minggu sekarang, saya belum mendapatkan pengecualian OOM tunggal. Saya telah menguji ini pada emulator, pada Nexus One dan pada Nexus S. Saya telah menguji url gambar yang berisi gambar yang berkualitas HD. Satu-satunya hambatan adalah bahwa dibutuhkan lebih banyak waktu untuk mengunduh.

Hanya ada satu skenario yang mungkin di mana saya bisa membayangkan bahwa OOM akan muncul, dan itu adalah jika kita mengunduh banyak, gambar yang sangat besar, dan sebelum mereka diskalakan dan dimasukkan ke dalam cache, secara bersamaan akan mengambil lebih banyak memori dan menyebabkan OOM. Tapi itu bahkan bukan situasi yang ideal dan kemungkinan besar tidak akan mungkin untuk diselesaikan dengan cara yang lebih layak.

Laporkan kesalahan dalam komentar! :-)

Wroclai
sumber
43

Saya melakukan yang berikut untuk mengambil gambar dan mengubah ukurannya dengan cepat. Semoga ini membantu

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
Chrispix
sumber
26
Pendekatan ini menskalakan bitmap. Tapi itu tidak menyelesaikan masalah OutOfMemory karena bitmap penuh sedang diterjemahkan.
Fedor
5
Saya akan melihat apakah saya dapat melihat kode lama saya, tetapi saya pikir itu menyelesaikan masalah memori saya. Akan memeriksa ulang kode lama saya.
Chrispix
2
Setidaknya dalam contoh ini, sepertinya Anda tidak menyimpan referensi ke bitmap penuh, sehingga menghemat memori.
NoBugs
Bagi saya itu memang memecahkan masalah memori, tetapi mengurangi kualitas gambar.
Pamela Sillah
35

Tampaknya ini adalah masalah yang berjalan sangat lama, dengan banyak penjelasan berbeda. Saya mengambil saran dari dua jawaban yang paling umum disajikan di sini, tetapi tidak satu pun dari ini menyelesaikan masalah VM saya dengan mengklaim tidak mampu membayar byte untuk melakukan bagian decoding dari proses. Setelah menggali, saya mengetahui bahwa masalah sebenarnya di sini adalah proses decoding yang diambil dari NATIVE tumpukan .

Lihat di sini: BitmapFactory OOM membuat saya gila

Itu membawa saya ke utas diskusi lain di mana saya menemukan beberapa solusi untuk masalah ini. Salah satunya adalah menelepon System.gc();secara manual setelah gambar Anda ditampilkan. Tapi itu benar-benar membuat aplikasi Anda menggunakan memori LEBIH, dalam upaya untuk mengurangi tumpukan asli. Solusi yang lebih baik pada rilis 2.0 (Donut) adalah dengan menggunakan opsi BitmapFactory "inPurgeable". Jadi saya hanya menambahkan o2.inPurgeable=true;setelahnya o2.inSampleSize=scale;.

Lebih lanjut tentang topik itu di sini: Apakah batas memori hanya menumpuk 6M?

Sekarang, setelah mengatakan semua ini, saya bodoh dengan Java dan Android juga. Jadi jika Anda berpikir ini adalah cara yang mengerikan untuk menyelesaikan masalah ini, Anda mungkin benar. ;-) Tetapi ini telah berhasil bagi saya, dan saya merasa tidak mungkin untuk menjalankan VM dari heap cache sekarang. Satu-satunya kelemahan yang saya dapat temukan adalah bahwa Anda membuang gambar yang diambil dalam cache. Yang berarti jika Anda kembali ke gambar itu, Anda menggambarnya kembali setiap waktu. Dalam hal bagaimana aplikasi saya bekerja, itu tidak terlalu menjadi masalah. Jarak tempuh Anda mungkin beragam.

RayHaque
sumber
inOur diperbaiki OOM bagi saya.
Artem Russakovskii
35

sayangnya jika Tidak Ada Yang Di Atas berfungsi, lalu Tambahkan ini ke file Manifest Anda . Di dalam tag aplikasi

 <application
         android:largeHeap="true"
Himanshu Mori
sumber
1
Bisakah Anda jelaskan apa yang sebenarnya dilakukannya? Memberitahu orang lain untuk menambahkan ini tidak membantu.
Stealth Rabbi
1
Ini solusi yang sangat buruk. Pada dasarnya Anda tidak mencoba untuk memperbaiki masalah. Alih-alih meminta sistem android untuk mengalokasikan lebih banyak ruang tumpukan untuk aplikasi Anda. Ini akan memiliki implikasi yang sangat buruk pada aplikasi Anda seperti aplikasi Anda menghabiskan banyak daya baterai karena GC harus dijalankan melalui ruang tumpukan besar untuk membersihkan memori dan juga kinerja aplikasi Anda akan lebih lambat.
Prakash
2
lalu mengapa android memungkinkan kita menambahkan android ini: largeHeap = "true" di manifes kita? Sekarang Anda menantang Android.
Himanshu Mori
32

Gunakan ini bitmap.recycle();Ini membantu tanpa masalah kualitas gambar.

Arsalan
sumber
9
Menurut API, panggilan daur ulang () tidak diperlukan.
Artem Russakovskii
28

Saya punya solusi yang jauh lebih efektif yang tidak perlu penskalaan apa pun. Cukup decode bitmap Anda hanya sekali dan kemudian cache itu di peta dengan namanya. Kemudian cukup ambil bitmap terhadap nama dan atur di ImageView. Tidak ada lagi yang perlu dilakukan.

Ini akan berfungsi karena data biner aktual dari bitmap yang di-decode tidak disimpan di dalam tumpukan VM dalvik. Itu disimpan secara eksternal. Jadi setiap kali Anda mendekode bitmap, ia mengalokasikan memori di luar tumpukan VM yang tidak pernah direklamasi oleh GC

Untuk membantu Anda lebih menghargai ini, bayangkan Anda telah menyimpan gambar Anda di folder yang dapat digambar. Anda cukup mendapatkan gambar dengan melakukan getResources (). GetDrwable (R.drawable.). Ini TIDAK akan men-decode gambar Anda setiap kali tetapi menggunakan kembali contoh yang sudah diterjemahkan setiap kali Anda menyebutnya. Jadi pada dasarnya itu di-cache.

Sekarang karena gambar Anda berada dalam file di suatu tempat (atau bahkan mungkin berasal dari server eksternal), itu adalah tanggung jawab ANDA untuk men-cache instance bitmap yang telah didekodekan untuk digunakan kembali di mana pun dibutuhkan.

Semoga ini membantu.

Parth Mehta
sumber
4
"dan kemudian cache di peta dengan namanya." Bagaimana tepatnya Anda menyimpan gambar Anda?
Vincent
3
Sudahkah Anda mencoba ini? Meskipun data piksel tidak benar-benar disimpan di dalam tumpukan Dalvik, ukurannya dalam memori asli dilaporkan ke VM dan dihitung berdasarkan memori yang tersedia.
ErikR
3
@Vincent Saya pikir tidak sulit untuk menyimpannya di Peta. Saya akan menyarankan sesuatu seperti peta HashMap <KEY, Bitmap>, di mana Kunci bisa menjadi String sumber atau apa pun yang masuk akal untuk Anda. Mari kita asumsikan Anda mengambil jalur sebagai KUNCI, Anda menyimpannya sebagai map.put (Path, Bitmap) dan menerimanya melalui map.get (Path)
Rafael T
3
Anda mungkin ingin menggunakan HashMap <String, SoftReference <Bitmap>> jika Anda menerapkan gambar Cache jika tidak, Anda mungkin kehabisan memori - juga saya tidak berpikir bahwa "itu mengalokasikan memori di luar tumpukan VM yang tidak pernah direklamasi oleh GC "Benar, memori tersebut direklamasi karena saya mengerti mungkin saja ada penundaan, untuk itulah bitmap.recycle () diperuntukkan, sebagai petunjuk untuk mendapatkan kembali memo awal ...
Dori
28

Saya telah mengatasi masalah yang sama dengan cara berikut.

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);
Prerna
sumber
tidak apa-apa tapi saya menggunakan beberapa bitmap untuk menggambar lingkaran di OnCreate dan panggilan aktivitas 4-5 kali jadi bagaimana menghapus bitmap dan cara menghapus bitmap dan membuat lagi untuk kedua kalinya ketika aktivitas 0nCreate ..
ckpatel
27

Ada dua masalah di sini....

  • Memori Bitmap tidak ada di tumpukan VM melainkan di tumpukan asli - lihat BitmapFactory OOM membuat saya gila
  • Pengumpulan sampah untuk tumpukan asli lebih malas daripada tumpukan VM - jadi Anda harus cukup agresif dalam melakukan bitmap.recycle dan bitmap = null setiap kali Anda melewati onPause atau onDestroy pada Kegiatan
Torid
sumber
Itu ada di tumpukan VM sejak android 2.3+
FindOut_Quran
27

Ini berhasil untuk saya!

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

Tidak ada jawaban di atas yang bekerja untuk saya, tetapi saya menemukan solusi yang sangat buruk yang menyelesaikan masalah. Saya menambahkan gambar pixel 1x1 yang sangat kecil ke proyek saya sebagai sumber daya, dan memuatnya ke ImageView saya sebelum memanggil pengumpulan sampah. Saya pikir mungkin ImageView tidak merilis Bitmap, jadi GC tidak pernah mengambilnya. Ini jelek, tetapi tampaknya berfungsi untuk saat ini.

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.
Mike
sumber
Sepertinya imageView benar-benar tidak mendaur ulang bitmap dengan sendirinya. Membantu saya, terima kasih
Dmitry Zaytsev
@ Mike dapatkah Anda menambahkan kode imageloader yang lengkap atau Anda dapat memberi saya tautan memuat gambar bitmap. Jika saya menggunakan daur ulang pada bitmap semua tampilan daftar saya ditampilkan tetapi semua item ditampilkan kosong
TNR
@ Mike, bisakah kamu tahu apakah saya melakukan imageView = null sebelum memanggil ke pengumpulan sampah apakah itu baik atau tidak?
Youddh
@TNR Saya pikir apa yang Anda hilang di sini adalah bahwa bitmapdalam kode di atas adalah sebelumnya gambar yang sudah ditampilkan, Anda perlu recycle itu, jelas setiap referensi untuk itu, membuat imageViewlupa juga dengan menetapkan pengganti kecil, gc(); dan setelah semua ini: muat gambar BARU Anda ke bitmapdan tampilkan, ...dalam kode di atas.
TWiStErRob
Ini salah. Anda harus selalu menghapus konten imageView Anda SEBELUM mendaur ulang bitmap (alih-alih saat itu benar-benar ditampilkan dan digunakan).
FindOut_Quran
20

Jawaban yang bagus di sini, tetapi saya ingin kelas yang sepenuhnya dapat digunakan untuk mengatasi masalah ini .. jadi saya melakukannya.

Ini adalah kelas BitmapHelper saya yang merupakan bukti 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;
    }

}
Pascal
sumber
Untuk siapa saja yang menggunakan ini: Saya baru saja memperbaiki bug: "int scaledBitmapHeight = scaledBitmap.getWidth ();" salah (jelas. Saya menggantinya dengan "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal
19

Ini bekerja untuk saya.

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

dan ini di C # monodroid. Anda dapat dengan mudah mengubah jalur gambar. yang penting di sini adalah opsi yang harus ditetapkan.

Dobermaxx99
sumber
16

Ini sepertinya tempat yang tepat untuk membagikan kelas utilitas saya untuk memuat dan memproses gambar dengan komunitas, Anda dipersilakan untuk menggunakannya dan memodifikasinya secara bebas.

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

Di salah satu aplikasi saya, saya perlu mengambil gambar dari Camera/Gallery. Jika pengguna mengklik gambar dari Kamera (mungkin 2MP, 5MP atau 8MP), ukuran gambar bervariasi dari kBs ke MBs. Jika ukuran gambar kurang (atau hingga 1-2MB) di atas kode berfungsi dengan baik tetapi jika saya memiliki gambar ukuran di atas 4MB atau 5MB maka OOMdatang dalam bingkai :(

maka saya telah bekerja untuk menyelesaikan masalah ini & akhirnya saya membuat perbaikan di bawah ini ke kode Fedor (Semua Kredit ke Fedor untuk membuat solusi yang bagus) :)

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;

}

Saya harap ini akan membantu teman-teman yang menghadapi masalah yang sama!

untuk lebih lanjut silakan lihat ini

Rupesh Yadav
sumber
14

Saya baru saja mengalami masalah ini beberapa menit yang lalu. Saya menyelesaikannya dengan melakukan pekerjaan yang lebih baik dalam mengelola adaptor listview saya. Saya pikir itu adalah masalah dengan ratusan gambar 50x50px yang saya gunakan, ternyata saya mencoba untuk mengembang tampilan kustom saya setiap kali baris ditampilkan. Cukup dengan menguji untuk melihat apakah baris telah meningkat saya menghilangkan kesalahan ini, dan saya menggunakan ratusan bitmap. Ini sebenarnya untuk Spinner, tetapi adaptor dasar berfungsi sama untuk ListView. Perbaikan sederhana ini juga sangat meningkatkan kinerja adaptor.

@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);
    }
...
BajaBob
sumber
3
Saya tidak bisa cukup berterima kasih untuk ini! Saya mengejar masalah yang salah sebelum melihat ini. Pertanyaan untuk Anda: Karena setiap baris daftar atau saya memiliki nama dan foto unik, saya harus menggunakan array convertView untuk mempertahankan masing-masing nilai baris. Saya tidak bisa melihat bagaimana menggunakan variabel tunggal akan memungkinkan Anda melakukannya. Apakah saya melewatkan sesuatu?
PeteH
13

Saya telah menghabiskan sepanjang hari menguji solusi ini dan satu-satunya hal yang bekerja untuk saya adalah pendekatan di atas untuk mendapatkan gambar dan secara manual memanggil GC, yang saya tahu tidak seharusnya diperlukan, tetapi itu adalah satu-satunya hal yang berhasil ketika saya meletakkan aplikasi saya di bawah pengujian beban berat, beralih di antara aktivitas. Aplikasi saya memiliki daftar gambar mini dalam tampilan daftar (katakanlah aktivitas A) dan ketika Anda mengklik salah satu gambar itu akan membawa Anda ke aktivitas lain (katakanlah aktivitas B) yang menampilkan gambar utama untuk item itu. Ketika saya akan bolak-balik antara dua aktivitas, saya akhirnya akan mendapatkan kesalahan OOM dan aplikasi akan ditutup paksa.

Ketika saya akan mendapatkan setengah jalan dari listview itu akan crash.

Sekarang ketika saya menerapkan hal berikut dalam aktivitas B, saya bisa melalui seluruh tampilan daftar tanpa masalah dan terus berjalan dan pergi dan pergi ... dan banyak yang cepat.

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
Jesse
sumber
Cintai solusi Anda! Saya juga menghabiskan waktu berjam-jam untuk menyelesaikan bug ini, sangat menjengkelkan! Sunting: Sedihnya, masalahnya masih ada ketika saya mengubah orientasi layar saya dalam mode lansekap ...
Xarialon
Ini akhirnya membantu saya bersama dengan: - Pilihan BitmapFactory.Options = BitmapFactory.Options baru (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732
13

Masalah ini hanya terjadi pada emulator Android. Saya juga menghadapi masalah ini dalam sebuah emulator tetapi ketika saya memeriksa sebuah perangkat maka itu berfungsi dengan baik.

Jadi silakan periksa di perangkat. Ini dapat dijalankan di perangkat.

hackjutsu
sumber
12

2 sen saya: saya memecahkan kesalahan OOM saya dengan bitmap dengan:

a) menskalakan gambar saya dengan faktor 2

b) menggunakan pustaka Picasso di Adaptor kustom saya untuk ListView, dengan satu panggilan di getView seperti ini:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

matsoftware
sumber
Saya senang Anda menyebutkan Picasso, karena itu membuat memuat gambar lebih mudah. Terutama yang disimpan dari jarak jauh.
Chrispix
12

gunakan kode ini untuk setiap gambar di pilih dari SdCard atau drewable untuk mengkonversi objek bitmap.

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;

gunakan instend jalur gambar Anda dari ImageData_Path.get (img_pos) .getPath () .

Gaurav Pansheriya
sumber
12

Umumnya ukuran tumpukan perangkat android hanya 16MB (bervariasi dari perangkat / OS lihat ukuran Heap posting ), jika Anda memuat gambar dan melintasi ukuran 16MB, itu akan membuang memori keluar, daripada menggunakan Bitmap untuk, memuat gambar dari kartu SD atau dari sumber daya atau bahkan dari jaringan mencoba menggunakan getImageUri , memuat bitmap memerlukan lebih banyak memori, atau Anda dapat mengatur bitmap ke null jika pekerjaan Anda dilakukan dengan bitmap itu.

Rohit Sharma
sumber
1
Dan jika setImageURI masih mendapatkan pengecualian maka lihat stackoverflow.com/questions/15377186/…
Mahesh
11

Semua solusi di sini memerlukan pengaturan IMAGE_MAX_SIZE. Ini membatasi perangkat dengan perangkat keras yang lebih kuat dan jika ukuran gambar terlalu rendah terlihat jelek pada layar HD.

Saya keluar dengan solusi yang bekerja dengan Samsung Galaxy S3 saya dan beberapa perangkat lain termasuk yang kurang kuat, dengan kualitas gambar yang lebih baik ketika perangkat yang lebih kuat digunakan.

Intinya adalah untuk menghitung memori maksimum yang dialokasikan untuk aplikasi pada perangkat tertentu, kemudian mengatur skala serendah mungkin tanpa melebihi memori ini. Berikut kodenya:

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

Saya mengatur memori maksimum yang digunakan oleh bitmap ini menjadi 25% dari memori maksimum yang dialokasikan, Anda mungkin perlu menyesuaikan ini dengan kebutuhan Anda dan memastikan bitmap ini dibersihkan dan tidak tinggal di memori ketika Anda selesai menggunakannya. Biasanya saya menggunakan kode ini untuk melakukan rotasi gambar (bitmap sumber dan tujuan) sehingga aplikasi saya perlu memuat 2 bitmap di memori pada saat yang sama, dan 25% memberi saya buffer yang baik tanpa kehabisan memori saat melakukan rotasi gambar.

Semoga ini bisa membantu seseorang di luar sana ..

Bruce
sumber
11

Tersebut OutofMemoryExceptiontidak dapat sepenuhnya diselesaikan dengan memanggilSystem.gc() dan sebagainya.

Dengan merujuk pada Siklus Hidup Aktivitas

Status Aktivitas ditentukan oleh OS itu sendiri tergantung pada penggunaan memori untuk setiap proses dan prioritas setiap proses.

Anda dapat mempertimbangkan ukuran dan resolusi untuk masing-masing gambar bitmap yang digunakan. Saya sarankan untuk mengurangi ukuran, resample ke resolusi yang lebih rendah, lihat desain galeri (satu gambar PNG kecil, dan satu gambar asli.)

Raju Gujarati
sumber
11

Kode ini akan membantu memuat bitmap besar dari drawable

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;
    }
}
vineet
sumber
Bagus, pikir akan lebih baik menggunakan Loader daripada Async Task?
Chrispix
Bagaimana dengan Bitmap.Config.ARGB_565? Jika kualitas tinggi tidak kritis.
Hamzeh Soboh