Aplikasi Android keluar dari masalah memori - mencoba segalanya dan masih bingung

87

Saya menghabiskan 4 hari penuh mencoba semua yang saya bisa untuk mengetahui kebocoran memori dalam aplikasi yang saya kembangkan, tetapi hal-hal sudah lama tidak masuk akal.

Aplikasi yang saya kembangkan bersifat sosial, jadi pikirkan Profil Aktivitas (P) dan daftar Aktivitas dengan data - misalnya lencana (B). Anda dapat melompat dari profil ke daftar lencana ke profil lain, ke daftar lain, dll.

Jadi bayangkan aliran seperti ini P1 -> B1 -> P2 -> B2 -> P3 -> B3, dll. Untuk konsistensi, saya memuat profil dan lencana dari pengguna yang sama, jadi setiap halaman P sama dan begitu juga setiap halaman B.

Inti umum dari masalahnya adalah: setelah menavigasi sebentar, tergantung pada ukuran setiap halaman, saya mendapatkan pengecualian kehabisan memori di tempat acak - Bitmap, String, dll - tampaknya tidak konsisten.

Setelah melakukan semua yang bisa dibayangkan untuk mencari tahu mengapa saya kehabisan ingatan, saya tidak menemukan apa-apa. Yang tidak saya mengerti adalah mengapa Android tidak mematikan P1, B1, dll jika kehabisan memori saat memuat dan malah macet. Saya berharap aktivitas sebelumnya ini mati dan dibangkitkan jika saya pernah Kembali ke mereka melalui onCreate () dan onRestoreInstanceState ().

Jangankan ini - bahkan jika saya melakukan P1 -> B1 -> Kembali -> B1 -> Kembali -> B1, saya masih mengalami crash. Ini menunjukkan semacam kebocoran memori, namun bahkan setelah membuang hprof dan menggunakan MAT dan JProfiler, saya tidak dapat menunjukkannya.

Saya telah menonaktifkan pemuatan gambar dari web (dan meningkatkan data pengujian yang dimuat untuk menebusnya dan membuat pengujian adil) dan memastikan cache gambar menggunakan SoftReferences. Android sebenarnya mencoba membebaskan beberapa SoftReferences yang dimilikinya, tetapi tepat sebelum memori mogok.

Halaman lencana mendapatkan data dari web, memuatnya ke dalam larik EntityData dari BaseAdapter dan memasukkannya ke ListView (Saya sebenarnya menggunakan MergeAdapter yang sangat baik dari CommonsWare , tetapi dalam aktivitas Lencana ini, sebenarnya hanya ada 1 adaptor, tapi saya ingin menyebutkan fakta ini dengan cara apa pun).

Saya telah memeriksa kode dan tidak dapat menemukan apa pun yang akan bocor. Saya membersihkan dan membatalkan semua yang dapat saya temukan dan bahkan System.gc () kiri dan kanan tetapi aplikasi tetap macet.

Saya masih tidak mengerti mengapa aktivitas tidak aktif yang ada di tumpukan tidak menuai, dan saya sangat ingin mengetahuinya.

Pada titik ini, saya sedang mencari petunjuk, saran, solusi ... apa pun yang dapat membantu.

Terima kasih.

Artem Russakovskii
sumber
Jadi, jika saya telah membuka 15 aktivitas dalam 20 detik terakhir (pengguna melakukannya dengan sangat cepat), apakah itu masalahnya? Baris kode apa yang harus saya tambahkan untuk menghapus aktivitas setelah ditampilkan? Saya mendapatkan outOfMemorykesalahan. Terima kasih!
Ruchir Baronia

Jawaban:

109

Saya masih tidak mengerti mengapa aktivitas tidak aktif yang ada di tumpukan tidak menuai, dan saya sangat ingin mengetahuinya.

Ini bukan cara kerja. Satu-satunya manajemen memori yang memengaruhi siklus hidup aktivitas adalah memori global di semua proses, karena Android memutuskan bahwa memori hampir habis sehingga perlu menghentikan proses latar belakang untuk mendapatkannya kembali.

Jika aplikasi Anda duduk di latar depan memulai lebih banyak aktivitas, itu tidak akan pernah pergi ke latar belakang, sehingga itu akan selalu mencapai batas memori proses lokalnya sebelum sistem hampir menghentikan prosesnya. (Dan ketika itu benar-benar menghentikan prosesnya, itu akan mematikan proses yang menghosting semua aktivitas, termasuk apa pun yang saat ini ada di latar depan.)

Jadi menurut saya masalah dasar Anda adalah: Anda membiarkan terlalu banyak aktivitas berjalan pada waktu yang sama, dan / atau masing-masing aktivitas tersebut bergantung pada terlalu banyak sumber daya.

Anda hanya perlu mendesain ulang navigasi Anda untuk tidak bergantung pada penumpukan sembarang jumlah aktivitas yang berpotensi berat. Kecuali jika Anda melakukan banyak hal di onStop () (seperti memanggil setContentView () untuk membersihkan hierarki tampilan aktivitas dan menghapus variabel apa pun yang mungkin dipegangnya), Anda hanya akan kehabisan memori.

Anda mungkin ingin mempertimbangkan menggunakan Fragment API baru untuk mengganti tumpukan aktivitas arbitrer ini dengan satu aktivitas yang mengelola memorinya dengan lebih ketat. Misalnya, jika Anda menggunakan fasilitas back-stack dari fragmen, ketika sebuah fragmen berada di back-stack dan tidak lagi terlihat, metode onDestroyView () -nya dipanggil untuk sepenuhnya menghapus hierarki tampilannya, sehingga sangat mengurangi footprint-nya.

Sekarang, sejauh Anda menabrak arus di mana Anda menekan kembali, pergi ke aktivitas, menekan kembali, pergi ke aktivitas lain, dll dan tidak pernah memiliki tumpukan yang dalam, maka ya Anda hanya mengalami kebocoran. Entri blog ini menjelaskan cara men-debug kebocoran: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

hackbod
sumber
47
Dalam pembelaan OP, bukan ini yang disarankan oleh dokumentasi. Mengutip developer.android.com/guide/topics/fundamentals/… "(Saat aktivitas dihentikan), aktivitas tersebut tidak lagi terlihat oleh pengguna dan dapat dimatikan oleh sistem saat memori dibutuhkan di tempat lain." "Jika suatu aktivitas dijeda atau dihentikan, sistem dapat melepaskannya dari memori ... dengan memintanya untuk menyelesaikannya (memanggil metode finish ())" "(onDestroy () dipanggil) karena sistem untuk sementara menghancurkan instance dari aktivitas untuk menghemat ruang. "
CommonsWare
42
Di halaman yang sama, "Namun, saat sistem menghancurkan aktivitas untuk memulihkan memori". Apa yang Anda tunjukkan adalah bahwa Android tidak pernah menghancurkan aktivitas untuk mendapatkan kembali memori, tetapi hanya akan menghentikan proses untuk melakukannya. Jika demikian, halaman ini membutuhkan penulisan ulang yang serius, karena berulang kali menyarankan bahwa Android akan menghancurkan aktivitas untuk mendapatkan kembali memori. Perhatikan juga bahwa banyak bagian yang dikutip juga ada di ActivityJavaDocs.
CommonsWare
6
Saya pikir saya telah meletakkan ini di sini, tetapi saya memposting permintaan untuk memperbarui dokumentasi untuk ini: code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller
15
Sekarang tahun 2013 dan dokumen hanya menyajikan poin palsu dengan lebih jelas: developer.android.com/training/basics/activity-lifecycle/… , "Setelah aktivitas Anda dihentikan, sistem mungkin menghancurkan instance jika perlu dipulihkan memori sistem. Dalam kasus EXTREME, sistem mungkin langsung menghentikan proses aplikasi Anda "
kaay
2
Yah, sial. Saya pasti selalu berpikir (karena dokumennya) bahwa sistem mengelola aktivitas yang dihentikan dalam proses Anda dan akan membuat serial dan deserialisasi (menghancurkan dan membuat) sesuai kebutuhan.
dcow
22

Beberapa tips:

  1. Pastikan Anda tidak membocorkan konteks aktivitas.

  2. Pastikan Anda tidak menyimpan referensi di bitmap. Bersihkan semua ImageView Anda di Activity # onStop, seperti ini:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Daur ulang bitmap jika Anda tidak membutuhkannya lagi.

  4. Jika Anda menggunakan cache memori, seperti memory-lru, pastikan tidak menggunakan banyak memori.

  5. Tidak hanya gambar yang memakan banyak memori, pastikan Anda tidak menyimpan terlalu banyak data lain di memori. Ini dapat terjadi dengan mudah jika Anda memiliki daftar yang tidak terbatas di aplikasi Anda. Coba cache data di DataBase.

  6. Di android 4.2, terdapat bug (stackoverflow # 13754876) dengan akselerasi hardware, jadi jika Anda menggunakannya hardwareAccelerated=truedalam manifes, memori akan bocor.GLES20DisplayList- tetap simpan referensi, meskipun Anda melakukan langkah (2) dan tidak ada orang lain yang merujuk ke bitmap ini. Di sini Anda membutuhkan:

    a) nonaktifkan akselerasi perangkat keras untuk api 16/17;
    atau
    b) melepaskan tampilan yang menahan bitmap

  7. Untuk Android 3+, Anda dapat mencoba menggunakan android:largeHeap="true"di AndroidManifest. Tetapi itu tidak akan menyelesaikan masalah memori Anda, tunda saja.

  8. Jika Anda membutuhkan, seperti, navigasi tak terbatas, maka Fragmen - harus menjadi pilihan Anda. Jadi, Anda akan memiliki 1 aktivitas, yang hanya akan beralih di antara fragmen. Dengan cara ini Anda juga akan menyelesaikan beberapa masalah memori, seperti nomor 4.

  9. Gunakan Memory Analyzer untuk mengetahui penyebab kebocoran memori Anda.
    Berikut adalah video yang sangat bagus dari Google I / O 2011: Manajemen memori untuk Aplikasi Android
    Jika Anda berurusan dengan bitmap, ini harus dibaca: Menampilkan Bitmaps dengan Efisien

vovkab.dll
sumber
Jadi, jika saya telah membuka 15 aktivitas dalam 20 detik terakhir (pengguna melakukannya dengan sangat cepat), apakah itu masalahnya? Baris kode apa yang harus saya tambahkan untuk menghapus aktivitas setelah ditampilkan? Saya mendapatkan outOfMemorykesalahan. Terima kasih!
Ruchir Baronia
4

Bitmap sering menjadi penyebab kesalahan memori di Android, jadi itu akan menjadi area yang bagus untuk diperiksa ulang.

Ed Burnette
sumber
Jadi, jika saya telah membuka 15 aktivitas dalam 20 detik terakhir (pengguna melakukannya dengan sangat cepat), apakah itu masalahnya? Baris kode apa yang harus saya tambahkan untuk menghapus aktivitas setelah ditampilkan? Saya mendapatkan outOfMemorykesalahan. Terima kasih!
Ruchir Baronia
2

Apakah Anda memegang beberapa referensi untuk setiap Kegiatan? AFAIK ini adalah alasan yang mencegah Android menghapus aktivitas dari tumpukan.

Kami Anda dapat mereproduksi kesalahan ini di perangkat lain juga? Saya telah mengalami beberapa perilaku aneh dari beberapa perangkat android tergantung pada ROM dan / atau pabrikan perangkat kerasnya.

NewProggie
sumber
Saya dapat mereproduksi ini pada Droid yang menjalankan CM7 dengan heap maksimum yang disetel ke 16MB, yang merupakan nilai yang sama yang saya uji pada emulator.
Artem Russakovskii
Kamu mungkin berencana untuk melakukan sesuatu. Saat aktivitas ke-2 dimulai, apakah aktivitas pertama akan dilakukan onPause-> onStop atau hanya onPause? Karena saya mencetak semua pada panggilan siklus hidup *******, dan saya melihat onPause -> onCreate, tanpa onStop. Dan salah satu crash dump benar-benar mengatakan sesuatu seperti onPause = true atau onStop = false untuk 3 aktivitas seperti yang dimatikannya.
Artem Russakovskii
OnStop harus dipanggil saat aktivitas meninggalkan layar, tetapi mungkin tidak jika aktivitas tersebut diklaim ulang oleh sistem lebih awal.
Selesai
Itu tidak diklaim ulang karena saya tidak melihat onCreate dan onRestoreInstanceState dipanggil jika saya mengklik kembali.
Artem Russakovskii
menurut siklus hidup mereka mungkin tidak pernah dipanggil, periksa jawaban saya yang memiliki tautan ke blog pengembang resmi, kemungkinan besar cara Anda melewatkan bitmap Anda
dten
2

Saya pikir masalahnya mungkin kombinasi dari banyak faktor yang dinyatakan di sini dalam jawaban adalah apa yang memberi Anda masalah. Seperti yang dikatakan @Tim, referensi (statis) ke aktivitas atau elemen dalam aktivitas itu dapat menyebabkan GC melewati Aktivitas. Sini artikel yang membahas aspek ini. Menurut saya, kemungkinan besar masalah berasal dari sesuatu yang membuat Aktivitas dalam status "Proses Terlihat" atau lebih tinggi, yang akan cukup menjamin bahwa Aktivitas dan sumber daya terkaitnya tidak akan pernah diklaim kembali.

Saya mengalami masalah yang berlawanan beberapa waktu yang lalu dengan Layanan, jadi itulah yang membuat saya berpikir ini: ada sesuatu yang membuat Aktivitas Anda tetap tinggi pada daftar prioritas proses sehingga tidak akan tunduk pada GC sistem, seperti referensi (@Tim) atau loop (@Alvaro). Loop tidak perlu menjadi item yang tidak ada habisnya atau berjalan lama, hanya sesuatu yang berjalan seperti metode rekursif atau loop berjenjang (atau sesuatu di sepanjang garis itu).

EDIT: Seperti yang saya pahami, onPause dan onStop dipanggil sesuai kebutuhan secara otomatis oleh Android. Metode yang ada di sana terutama untuk Anda timpa sehingga Anda dapat mengurus apa yang Anda perlukan sebelum proses hosting dihentikan (menyimpan variabel, menyimpan status secara manual, dll.); tetapi perhatikan bahwa dengan jelas dinyatakan bahwa onStop (bersama dengan onDestroy) tidak dapat dipanggil di setiap kasus. Selain itu, jika proses hosting juga menghosting Aktivitas, Layanan, dll. Yang memiliki status "Forground" atau "Visible", OS mungkin tidak akan menghentikan proses / utas. Misalnya: Aktivitas dan Layanan keduanya ditandai dalam proses yang sama dan Layanan kembaliSTART_STICKY darionStartCommand()proses tersebut secara otomatis membutuhkan setidaknya satu status yang terlihat. Itu mungkin kuncinya di sini, coba deklarasikan proc baru untuk Aktivitas dan lihat apakah itu mengubah sesuatu. Coba tambahkan baris ini ke deklarasi Aktivitas Anda di Manifes sebagai: android:process=":proc2"lalu jalankan pengujian lagi jika Aktivitas Anda berbagi proses dengan hal lain. Pemikirannya di sini adalah bahwa jika Anda telah membersihkan Aktivitas Anda dan cukup yakin bahwa masalahnya bukan Aktivitas Anda, maka sesuatu yang lain adalah masalahnya dan waktunya untuk memburu untuk itu.

Selain itu, saya tidak dapat mengingat di mana saya melihatnya (bahkan jika saya melihatnya di dokumen Android) tetapi saya ingat sesuatu tentang PendingIntentreferensi Aktivitas dapat menyebabkan Aktivitas berperilaku seperti ini.

Berikut ini tautan untuk onStartCommand()halaman dengan beberapa wawasan tentang proses depan non-pembunuhan.

Kingsolmn
sumber
Berdasarkan tautan yang Anda berikan (terima kasih), suatu aktivitas dapat dimatikan jika onStop-nya telah dipanggil, tetapi dalam kasus saya, saya pikir ada sesuatu yang mencegah onStop berjalan. Saya pasti akan mencari tahu mengapa. Namun, ia juga mengatakan aktivitas dengan hanya onPause juga dapat dimatikan, yang tidak terjadi dalam kasus saya (saya melihat onPause dipanggil tetapi tidak onStop).
Artem Russakovskii
1

jadi satu-satunya hal yang benar-benar dapat saya pikirkan adalah jika Anda memiliki variabel statis yang merujuk langsung atau tidak langsung ke konteks. Bahkan sesuatu yang lebih banyak sebagai referensi ke bagian aplikasi. Saya yakin Anda sudah mencobanya tetapi saya akan menyarankannya untuk berjaga-jaga, coba batalkan SEMUA variabel statis Anda di onDestroy () hanya untuk memastikan pengumpul sampah mendapatkannya

Tim Fenton - VenumX
sumber
1

Sumber kebocoran memori terbesar yang saya temukan disebabkan oleh beberapa referensi global, tingkat tinggi, atau yang sudah berlangsung lama. Jika Anda menyimpan "konteks" dalam variabel di mana saja, Anda mungkin mengalami kebocoran memori yang tidak dapat diprediksi.

D2TheC
sumber
Ya, saya mendengar dan membaca ini berkali-kali, tapi saya kesulitan menjepitnya. Kalau saja saya bisa dengan andal mencari cara untuk melacaknya.
Artem Russakovskii
1
Dalam kasus saya ini diterjemahkan ke dalam variabel apa pun di tingkat kelas yang diatur sama dengan konteks (misalnya Class1.variable = getContext ();). Secara umum, mengganti setiap penggunaan "konteks" di aplikasi saya dengan panggilan baru ke "getContext" atau yang serupa memecahkan masalah memori terbesar saya. Tetapi kasus saya tidak stabil dan tidak menentu, tidak dapat diprediksi seperti dalam kasus Anda, jadi mungkin sesuatu yang berbeda.
D2TheC
1

Coba teruskan getApplicationContext () ke apa pun yang membutuhkan Konteks. Anda mungkin memiliki variabel global yang menyimpan referensi ke Aktivitas Anda dan mencegahnya agar tidak dikumpulkan.

vee
sumber
1

Salah satu hal yang sangat membantu masalah memori dalam kasus saya adalah menyetel inPurgeable menjadi true untuk Bitmap saya. Lihat Mengapa saya TIDAK pernah menggunakan opsi inPurgeable BitmapFactory?dan pembahasan jawabannya untuk info lebih lanjut.

Jawaban Dianne Hackborn dan diskusi kita selanjutnya (juga terima kasih, CommonsWare) membantu memperjelas hal-hal tertentu yang membuat saya bingung, jadi terima kasih untuk itu.

Artem Russakovskii
sumber
Saya tahu ini adalah topik lama tetapi saya melihat tampaknya menemukan utas ini pada banyak pencarian. Saya ingin menautkan tutorial hebat yang saya pelajari banyak dari mana Anda dapat menemukannya di sini: developer.android.com/training/displaying-bitmaps/index.html
Codeversed
0

Saya mengalami masalah yang sama dengan Anda. Saya sedang mengerjakan aplikasi perpesanan instan, untuk kontak yang sama, dimungkinkan untuk memulai ProfileActivity di ChatActivity, dan sebaliknya. Saya hanya menambahkan string ekstra ke dalam maksud untuk memulai aktivitas lain, ini mengambil informasi jenis kelas aktivitas pemula, dan id pengguna. Misalnya, ProfileActivity memulai ChatActivity, lalu di ChatActivity.onCreate, saya menandai jenis kelas invoker 'ProfileActivity' dan id pengguna, jika itu akan memulai Aktivitas, saya akan memeriksa apakah itu 'ProfileActivity' untuk pengguna atau tidak . Jika demikian, cukup panggil 'finish ()' dan kembali ke ProfileActivity sebelumnya alih-alih membuat yang baru. Kebocoran memori adalah hal lain.

qiuping345
sumber