Pertanyaan Aneh keluar dari masalah memori saat memuat gambar ke objek Bitmap


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. Kegiatan yang diluncurkan untuk hasil adalah peta. Jika saya mengklik tombol saya untuk meluncurkan pratinjau gambar (memuat gambar dari kartu SD) aplikasi kembali dari aktivitas kembali ke listview aktivitas ke handler hasil untuk meluncurkan kembali aktivitas baru saya yang tidak lebih dari sebuah widget gambar.

Pratinjau gambar pada tampilan daftar sedang dilakukan dengan kursor dan ListAdapter. Ini membuatnya sangat sederhana, tetapi saya tidak yakin bagaimana saya dapat menempatkan gambar yang diubah ukurannya (I.e. Ukuran bit yang lebih kecil bukan piksel sebagai src untuk tombol gambar dengan cepat. Jadi saya hanya mengubah ukuran gambar yang keluar dari kamera ponsel.

Masalahnya adalah bahwa saya mendapatkan kesalahan memori ketika mencoba untuk kembali dan meluncurkan kembali aktivitas kedua.

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

Ini akan lebih baik karena saya juga perlu membuat beberapa perubahan pada properti dari 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 out of band mengubah ukuran dan menyimpan gambar saya, tetapi itu tidak benar-benar apa yang ingin saya lakukan, tetapi beberapa contoh kode untuk itu akan bagus.

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

FYI: Begini cara 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.imagefilename adalah ButtonImage.

Berikut ini adalah 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

1105


asal


Jawaban:


Itu Pelatihan Android kelas, "Menampilkan Bitmap secara Efisien", menawarkan beberapa informasi bagus untuk memahami dan menangani pengecualian java.lang.OutOfMemoryError: bitmap size exceeds VM budget saat memuat Bitmap.


Baca Dimensi dan Jenis Bitmap

Itu BitmapFactory kelas menyediakan beberapa metode decoding (decodeByteArray(), decodeFile(), decodeResource(), dll.) untuk membuat Bitmap dari berbagai sumber. Pilih metode dekode yang paling sesuai berdasarkan sumber data gambar Anda. Metode ini berusaha mengalokasikan memori untuk bitmap yang dibangun dan karenanya dapat dengan mudah menghasilkan OutOfMemory pengecualian. Setiap jenis metode decode memiliki tanda tangan tambahan yang memungkinkan Anda menentukan opsi decoding melalui BitmapFactory.Options kelas. Pengaturan inJustDecodeBounds properti ke true sementara decoding menghindari alokasi memori, kembali null untuk objek bitmap tetapi pengaturan outWidth, outHeight dan outMimeType. Teknik ini memungkinkan Anda untuk membaca dimensi dan jenis 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;

Menghindari java.lang.OutOfMemory pengecualian, periksa dimensi bitmap sebelum mendekodekannya, kecuali Anda benar-benar mempercayai sumber untuk memberi Anda data gambar ukuran yang dapat diprediksi yang cocok dengan nyaman dalam 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 yang diisikan sebagai pengganti harus dimuat sebagai gantinya. Berikut beberapa faktor yang perlu dipertimbangkan:

  • Diperkirakan penggunaan memori memuat gambar penuh dalam memori.
  • Jumlah memori yang Anda bersedia berkomitmen untuk memuat gambar ini mengingat persyaratan memori lainnya dari aplikasi Anda.
  • Dimensi dari target ImageView atau komponen UI tempat gambar akan dimuat.
  • 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 memberi tahu decoder ke sub-gambar, muat versi yang lebih kecil ke dalam memori, atur inSampleSize untuk true di dalam kamu BitmapFactory.Options obyek. Sebagai contoh, sebuah gambar dengan resolusi 2048x1536 yang diterjemahkan dengan inSampleSize dari 4 menghasilkan bitmap sekitar 512x384. Memuat ini ke dalam memori menggunakan 0,75MB daripada 12MB untuk gambar penuh (dengan asumsi konfigurasi bitmap dari 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 decoder menggunakan a   nilai akhir dengan membulatkan ke kekuatan terdekat dua, sesuai    inSampleSize dokumentasi.

Untuk menggunakan metode ini, decode pertama dengan inJustDecodeBounds mulai true, berikan opsi melalui dan kemudian decode lagi menggunakan yang baru inSampleSize nilai dan inJustDecodeBounds mulai 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 memudahkan untuk memuat bitmap ukuran besar yang sewenang-wenang menjadi sebuah ImageView yang menampilkan thumbnail 100x100 piksel, seperti yang ditunjukkan pada 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 yang sesuai BitmapFactory.decode* metode sesuai kebutuhan.


558



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

Ini inSampleSize opsi mengurangi konsumsi memori.

Ini metode yang lengkap. Pertama kali membaca ukuran gambar tanpa mendekode konten itu sendiri. Maka itu menemukan yang terbaik inSampleSize nilai, itu harus menjadi 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;
}

856



Saya telah membuat sedikit peningkatan 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 menemukan nya, dan kemudian saya bisa membuat yang satu 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;
}

352



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

1) Setiap kali Anda melakukannya BitmapFactory.decodeXYZ(), pastikan untuk lulus dalam BitmapFactory.Options dengan inPurgeable mulai true (dan sebaiknya dengan inInputShareable juga diatur ke true).

2) TIDAK PERNAH digunakan Bitmap.createBitmap(width, height, Config.ARGB_8888). Maksudku, PERNAH! Saya tidak pernah memiliki hal yang tidak meningkatkan kesalahan memori setelah beberapa kali berlalu. Tidak ada jumlah recycle(), System.gc(), apa pun yang membantu. Itu selalu meningkatkan pengecualian. Cara lain yang benar-benar berfungsi adalah memiliki gambar tiruan di drawable Anda (atau Bitmap lain yang Anda dekodekan menggunakan langkah 1 di atas), rescale itu ke apa pun yang Anda inginkan, kemudian memanipulasi Bitmap yang dihasilkan (seperti meneruskannya ke Kanvas untuk lebih menyenangkan). Jadi, apa yang sebaiknya Anda gunakan 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 jam jika tidak 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).


220



Itu a bug yang dikenal, bukan karena file besar. Karena Android Cache the Drawables, itu akan kehabisan memori setelah menggunakan beberapa gambar. Tetapi saya telah menemukan cara lain untuk itu, dengan melewati sistem cache default android.

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

84



Saya memiliki masalah yang sama dan memecahkannya dengan menghindari fungsi BitmapFactory.decodeStream atau decodeFile dan malah digunakan BitmapFactory.decodeFileDescriptor

decodeFileDescriptor Sepertinya itu memanggil metode asli yang berbeda dari decodeStream / decodeFile.

Bagaimanapun, apa yang berhasil adalah ini (perhatikan bahwa saya menambahkan beberapa opsi seperti yang dimiliki beberapa orang di atas, tetapi bukan itu yang membuat perbedaan. Yang penting adalah panggilan untuk BitmapFactory.decodeFileDescriptor dari pada 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 dalam decodeStream / decodeFile. Saya telah mengonfirmasi bahwa metode asli yang berbeda dipanggil ketika menggunakan decodeFileDescriptor. Juga apa 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 melawannya!"


69



Saya pikir cara terbaik untuk menghindari OutOfMemoryError adalah menghadapinya dan memahaminya.

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

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

Saya akan berbicara tentang versi SDK sebelum Honey Comb pertama.

  1. Bitmap disimpan di tumpukan asli, tetapi akan mendapatkan sampah yang dikumpulkan secara otomatis, memanggil recycle () tidak perlu.

  2. Jika {VM heap size} + {dialokasikan memori heap asli}> = {VM heap size limit untuk perangkat}, dan Anda mencoba untuk membuat bitmap, OOM akan dibuang.

    PEMBERITAHUAN: VM HEAP SIZE dihitung daripada VM ALLOCATED MEMORY.

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

  4. Jadi Anda harus menjaga memori VM puncak serendah mungkin untuk menjaga VM Heap Size dari pertumbuhan terlalu besar untuk menghemat memori yang tersedia untuk Bitmaps.

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

  6. Ukuran Heap Asli tidak akan pernah menyusut juga, tetapi tidak dihitung untuk OOM, jadi tidak perlu khawatir tentang itu.

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

  1. Bitmap disimpan di VM heap, 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 kecil kemungkinannya untuk dilemparkan.

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

Anda dapat melihatnya sendiri di Aplikasi. Jika suatu Kegiatan mengeksekusi AsyncTask yang masih berjalan setelah Kegiatan dihancurkan, Kegiatan tidak akan mendapatkan sampah yang dikumpulkan sampai selesai AsyncTask.

Ini karena AsyncTask adalah instance dari kelas dalam anonim, ia memegang referensi dari Kegiatan.

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

Callback adalah kelas internal anonim juga, jadi jika contoh statis dalam proyek Anda menahan mereka dan jangan melepaskannya, memori akan bocor.

Jika Anda menjadwalkan tugas yang berulang atau tertunda, misalnya Timer, dan Anda tidak memanggil membatalkan () dan membersihkan () dalam onPause (), memori akan bocor.


64



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

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

Lihatlah akhir jawaban ini untuk tautan ke kode sumber.

Persyaratan:

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

Screenshot

Fitur:

  • Mempertahankan cache jika ada perubahan orientasi, menggunakan tunggal
  • Menggunakan seperdelapan dari memori aplikasi yang ditetapkan ke cache (ubah jika Anda mau)
  • Bitmap besar akan diskalakan (Anda dapat menentukan piksel maksimum yang ingin Anda izinkan)
  • Kontrol bahwa ada koneksi internet yang tersedia sebelum mengunduh bitmap
  • Pastikan Anda hanya instantiate satu tugas per baris
  • Jika Anda melemparkan itu ListView jauh, itu tidak akan mengunduh bitmap antara

Ini tidak termasuk:

  • Disk cache. Ini harus mudah diimplementasikan - cukup arahkan ke tugas yang berbeda yang mengambil bitmap dari disk

Kode sampel:

Gambar yang sedang diunduh adalah gambar (75x75) dari Flickr. Namun, masukkan url gambar apa pun yang Anda inginkan untuk diproses, dan aplikasi akan menurunkannya jika melebihi batas maksimum. Dalam aplikasi ini url hanya dalam Stringlarik.

Itu LruCache memiliki cara yang baik untuk menangani bitmap. Namun, dalam aplikasi ini saya meletakkan sebuah instance dari sebuah LruCache di dalam kelas cache lain yang saya buat untuk mendapatkan aplikasi lebih layak.

Hal-hal penting Cache.java (yang loadBitmap() metode adalah 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 tidak perlu mengedit apa pun di file Cache.java kecuali Anda ingin menerapkan disk cache.

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 bagus untuk mengunduh gambar di sana jika kami belum menerapkan pemeriksaan yang memastikan bahwa kami tidak akan memulai jumlah untaian tak terbatas per baris. Cache.java memeriksa apakah rowObject.mBitmapUrl sudah ada dalam tugas dan jika ya, itu tidak akan memulai yang lain. Oleh karena itu, kami kemungkinan besar tidak melebihi batasan antrian kerja dari AsyncTask kolam.

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 satu pengecualian OOM. Saya telah menguji ini di emulator, di Nexus One saya dan di Nexus S. Saya telah menguji url gambar yang berisi gambar yang berkualitas HD. Satu-satunya hambatan adalah butuh lebih banyak waktu untuk mengunduh.

Hanya ada satu kemungkinan skenario di mana saya dapat 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 dipecahkan dengan cara yang lebih layak.

Laporkan kesalahan di komentar! :-)


58



Saya melakukan hal 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);    

36



Tampaknya ini adalah masalah yang sangat panjang, dengan banyak penjelasan yang berbeda. Saya mengambil saran dari dua jawaban yang paling umum disajikan di sini, tetapi tidak satu pun dari ini memecahkan masalah saya dari VM yang mengklaim itu tidak mampu membayar byte untuk melakukan decoding bagian dari proses. Setelah beberapa penggalian saya belajar bahwa masalah sebenarnya di sini adalah proses decoding yang mengambil dari ASLI tumpukan.

Lihat disini: BitmapFactory OOM membuatku gila

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

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

Sekarang, setelah mengatakan semua ini, saya sepenuhnya bodoh dengan Java dan Android juga. Jadi jika Anda berpikir ini adalah cara yang buruk untuk menyelesaikan masalah ini, Anda mungkin benar. ;-) Tapi ini telah berhasil keajaiban bagi saya, dan saya telah menemukan mustahil untuk menjalankan VM dari tumpukan cache sekarang. Satu-satunya kelemahan yang bisa saya temukan adalah bahwa Anda merusak gambar yang di-cache. Yang berarti jika Anda kembali ke gambar itu, Anda menggambar ulang setiap kali. Dalam kasus bagaimana aplikasi saya berfungsi, itu tidak benar-benar masalah. Jarak tempuh Anda mungkin bervariasi.


31