Android uygulaması yetersiz bellek sorunları - her şeyi denedi ve yine de kayıp


87

Geliştirdiğim bir uygulamadaki bellek sızıntısını anlamak için elimden gelen her şeyi denemek için 4 tam gün harcadım, ancak işler uzun zaman önce anlam kazanmayı bıraktı.

Geliştirdiğim uygulama sosyal niteliktedir, bu nedenle Aktiviteler (P) profilini düşünün ve verileri içeren Aktiviteleri listeleyin - örneğin rozetler (B). Profilden bir rozet listesine, diğer profillere, diğer listelere vb. Atlayabilirsiniz.

Öyleyse, P1 -> B1 -> P2 -> B2 -> P3 -> B3, vb. Gibi bir akış hayal edin. Tutarlılık için, aynı kullanıcının profillerini ve rozetlerini yüklüyorum, bu nedenle her P sayfası aynıdır ve bu yüzden her B sayfası.

Sorunun genel özü şudur: Her sayfanın boyutuna bağlı olarak biraz gezindikten sonra, rastgele yerlerde (Bitmapler, Dizeler vb.) Bir yetersiz bellek istisnası alıyorum - bu tutarlı görünmüyor.

Neden hafızamın tükendiğini anlamak için akla gelebilecek her şeyi yaptıktan sonra, hiçbir şey bulamadım. Anlayamadığım şey, Android'in yükleme sırasında hafızası biterse ve bunun yerine çökerse neden P1, B1, vb .'yi öldürmediğidir. OnCreate () ve onRestoreInstanceState () yoluyla onlara geri dönersem, bu önceki faaliyetlerin ölmesini ve diriltilmesini beklerdim.

Bunu bir kenara bırakın - P1 -> B1 -> Geri -> B1 -> Geri -> B1 yapsam bile, yine de bir çökme yaşıyorum. Bu, bir tür bellek sızıntısı olduğunu gösterir, ancak hprof'u döktükten ve MAT ve JProfiler'ı kullandıktan sonra bile tam olarak belirleyemiyorum.

Web'den görüntü yüklemeyi devre dışı bıraktım (ve bunu telafi etmek ve testi adil hale getirmek için yüklenen test verilerini artırdım) ve görüntü önbelleğinin SoftReferences kullandığından emin oldum. Android aslında sahip olduğu birkaç SoftReference'ı serbest bırakmaya çalışıyor, ancak bellekten hemen önce çöküyor.

Rozet sayfaları web'den veri alır, bir BaseAdapter'dan bir EntityData dizisine yükler ve bir ListView'e besler (Aslında CommonsWare'in mükemmel MergeAdapter'ını kullanıyorum , ancak bu Rozet etkinliğinde zaten sadece 1 adaptör var, ama ben bu gerçeği her iki şekilde de belirtmek istedim)

Kodu inceledim ve sızacak hiçbir şey bulamadım. Bulabildiğim her şeyi ve hatta System.gc () sol ve sağdaki her şeyi temizledim ve geçersiz kıldım ama yine de uygulama çöküyor.

Hala yığındaki aktif olmayan faaliyetlerin neden biçilmediğini anlamıyorum ve bunu gerçekten anlamayı çok isterim.

Bu noktada, yardımcı olabilecek herhangi bir ipucu, tavsiye, çözüm arıyorum ...

Teşekkür ederim.


Öyleyse, son 20 saniyede 15 etkinlik açtıysam (kullanıcı çok hızlı geçiyor), sorun bu olabilir mi? Etkinliği görüntülendikten sonra temizlemek için hangi kod satırını eklemeliyim? Bir outOfMemoryhata alıyorum. Teşekkürler!
Ruchir Baronia

Yanıtlar:


109

Hala yığındaki aktif olmayan faaliyetlerin neden biçilmediğini anlamıyorum ve bunu gerçekten anlamayı çok isterim.

İşler böyle yürümüyor. Etkinlik yaşam döngüsünü etkileyen tek bellek yönetimi, Android belleğin azaldığına ve bu nedenle geri almak için arka plan işlemlerini sonlandırması gerektiğine karar verdiğinden, tüm işlemlerdeki küresel bellektir.

Uygulamanız ön planda oturup gittikçe daha fazla etkinlik başlatıyorsa, asla arka plana gitmez, bu nedenle sistem sürecini sonlandırmaya yaklaşmadan önce her zaman yerel işlem bellek sınırına ulaşır. (Ve sürecini bitirdiğinde, şu anda ön planda olanlar da dahil olmak üzere tüm etkinlikleri barındıran süreci öldürecektir .)

Bu yüzden bana temel probleminiz gibi geliyor: aynı anda çok fazla faaliyetin yürütülmesine izin veriyorsunuz ve / veya bu faaliyetlerin her biri çok fazla kaynağı elinde tutuyor.

Gezinme sisteminizi, rastgele sayıda potansiyel olarak ağır etkinliklerin yığılmasına dayanmayacak şekilde yeniden tasarlamanız gerekir. OnStop () 'da ciddi miktarda şey yapmazsanız (etkinliğin görünüm hiyerarşisini temizlemek için setContentView () işlevini çağırmak ve tuttuğu başka her şeyin değişkenlerini temizlemek gibi), hafızanız tükenir.

Bu rasgele etkinlik yığınını belleğini daha sıkı bir şekilde yöneten tek bir etkinlikle değiştirmek için yeni Parça API'lerini kullanmayı düşünebilirsiniz. Örneğin, parçaların arka yığın olanaklarını kullanırsanız, bir parça arka yığına gittiğinde ve artık görünür olmadığında, onDestroyView () yöntemi, ayak izini büyük ölçüde azaltarak görünüm hiyerarşisini tamamen kaldırmak için çağrılır.

Şimdi, geri bastığınız, bir aktiviteye gittiğiniz, geri bastığınız, başka bir aktiviteye gittiğiniz ve asla derin bir yığının olmadığı akışta çarpıştığınız sürece, o zaman evet sadece bir sızıntı var. Bu blog yazısı, sızıntılarda nasıl hata ayıklanacağını açıklar: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
OP'nin savunmasında, belgelerin önerdiği bu değil. Son teklif developer.android.com/guide/topics/fundamentals/... "(bir faaliyet durdurulur), artık görünür kullanıcıya olduğunu ve hafıza başka yerde ihtiyaç duyulan sistem tarafından öldürülebilir." "Bir etkinlik duraklatılırsa veya durdurulursa, sistem onu ​​bitirmesini isteyerek (finish () yöntemini çağırarak)" "(onDestroy () çağrılır) çünkü sistem bu olay örneğini geçici olarak yok etmektedir. yer kazanmak için etkinlik. "
CommonsWare

42
Aynı sayfada, "Ancak, sistem belleği kurtarmak için bir etkinliği yok ettiğinde". Gösterdiğiniz şey, Android'in hafızayı geri kazanmak için etkinlikleri asla yok etmediği , ancak bunu yapmak için yalnızca süreci sonlandıracağıdır. Öyleyse, Android'in hafızayı geri kazanmak için bir etkinliği yok edeceğini defalarca öne sürdüğü için bu sayfanın ciddi bir yeniden yazılması gerekiyor . Ayrıca alıntılanan pasajların Activityçoğunun JavaDocs'ta da bulunduğuna dikkat edin .
CommonsWare

6
Bunu buraya koyduğumu sanıyordum, ancak bunun için belgeleri güncellemek için bir istek gönderdim: code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller

15
Yıl 2013 ve dokümanlar yalnızca yanlış noktayı daha net bir şekilde ortaya koymaya başladı: developer.android.com/training/basics/activity-lifecycle/… , "Aktiviteniz durdurulduktan sonra, kurtarılması gerekirse sistem örneği yok edebilir sistem belleği. AŞIRI durumlarda, sistem uygulama sürecinizi basitçe öldürebilir "
kaay

2
Sıçtık. Kesinlikle her zaman (dokümanlar nedeniyle) sistemin durdurulan faaliyetleri sürecinizde yönettiğini ve gerektiğinde bunları seri hale getirip serileştireceğini (yok edip yaratacağını) düşündüm.
dcow

22

Bazı ipuçları:

  1. Aktivite bağlamını sızdırmadığınızdan emin olun.

  2. Referansları bit eşlemlerde tutmadığınızdan emin olun. Tüm ImageView'larınızı Activity # onStop'da aşağıdaki gibi temizleyin:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Artık ihtiyacınız yoksa bitmap'leri geri dönüştürün.

  4. Memory lru gibi bir bellek önbelleği kullanıyorsanız, fazla bellek kullanmadığından emin olun.

  5. Yalnızca görüntüler çok fazla bellek almaz, bellekte çok fazla başka veri tutmadığınızdan emin olun. Uygulamanızda sonsuz listeniz varsa bu kolayca gerçekleşebilir. DataBase'de verileri önbelleğe almayı deneyin.

  6. Android 4.2'de donanım hızlandırmalı bir hata (stackoverflow # 13754876) vardır, bu nedenle hardwareAccelerated=truebildiriminizde kullanırsanız bellek sızıntısı olur. GLES20DisplayList- (2) adımını gerçekleştirmiş olsanız ve hiç kimse bu bitmap'e başvurmuyor olsa bile referansları tutmaya devam edin. İşte ihtiyacınız var:

    a) api 16/17 için donanım hızlandırmayı devre dışı bırakın;
    veya
    b) bitmap'i tutan görünümü ayırın

  7. Android 3+ için android:largeHeap="true"kendi AndroidManifest. Ama hafıza problemlerinizi çözmeyecek, sadece erteleyin.

  8. Sonsuz navigasyona ihtiyacınız varsa, o zaman Parçalar - seçiminiz olmalı. Böylece, sadece parçalar arasında geçiş yapacak olan 1 etkinliğiniz olacak. Bu şekilde, 4 numara gibi bazı hafıza sorunlarını da çözeceksiniz.

  9. Bellek sızıntınızın nedenini bulmak için Memory Analyzer'ı kullanın.
    İşte çok iyi bir video olduğunu Google I / O 2011: Android Uygulamaları için Bellek yönetimi
    : Eğer Bitmaplerle uğraşan Eğer bu bir okumalı olmalıdır verimli Bitmaps gösteriliyor


Öyleyse, son 20 saniyede 15 etkinlik açtıysam (kullanıcı çok hızlı geçiyor), sorun bu olabilir mi? Etkinliği görüntülendikten sonra temizlemek için hangi kod satırını eklemeliyim? Bir outOfMemoryhata alıyorum. Teşekkürler!
Ruchir Baronia

4

Bit eşlemler genellikle Android'deki bellek hatalarının suçlularıdır, bu nedenle iki kez kontrol etmek için iyi bir alan olur.


Öyleyse, son 20 saniyede 15 etkinlik açtıysam (kullanıcı çok hızlı geçiyor), sorun bu olabilir mi? Etkinliği görüntülendikten sonra temizlemek için hangi kod satırını eklemeliyim? Bir outOfMemoryhata alıyorum. Teşekkürler!
Ruchir Baronia

2

Her Aktivite için bazı referanslarınız var mı? AFAIK bu, Android'in yığındaki etkinlikleri silmesini engelleyen bir nedendir.

Bu hatayı başka cihazlarda da yeniden oluşturabiliyor musunuz? ROM ve / veya donanım üreticisine bağlı olarak bazı android cihazlarda bazı garip davranışlar yaşadım.


Bunu, emülatörde test ettiğim değerle aynı olan, maksimum yığın 16 MB olarak ayarlanmış CM7 çalıştıran bir Droid üzerinde yeniden üretebildim.
Artem Russakovskii

Bir şeyler bulmuş olabilirsin. 2. aktivite başladığında, 1. aktivite onPause-> onStop mı yoksa sadece onPause mu yapacak? Çünkü hepsini ******* yaşam döngüsü çağrılarına yazdırıyorum ve onStop olmadan Duraklat -> onCreate üzerinde görüyorum. Ve çökme dökümlerinden biri aslında öldürmekte olduğu 3 aktivite için onPause = true veya onStop = false gibi bir şey söyledi.
Artem Russakovskii

Etkinlik ekrandan çıktığında OnStop çağrılmalıdır, ancak sistem tarafından erkenden geri kazanıldıysa olmayabilir.
DTen

Geri alınmıyor çünkü onCreate'i görmüyorum ve geri tıklarsam onRestoreInstanceState çağrıldı.
Artem Russakovskii

yaşam döngüsüne göre bunlar asla çağrılmayabilir, resmi geliştirici bloguna bağlantısı olan cevabımı kontrol edin, büyük olasılıkla bit eşlemlerinizi etrafta geçirme şekliniz
dten

2

Sanırım problem, burada cevaplarda belirtilen birçok faktörün bir kombinasyonu size problemler veriyor olabilir. @Tim'in dediği gibi, bir etkinliğe veya bu etkinlikteki bir öğeye (statik) referans, GC'nin Etkinliği atlamasına neden olabilir. Buraya bu yönü tartışan makale. Muhtemel sorunun, Aktiviteyi "Görünür Süreç" veya daha yüksek bir durumda tutan bir şeyden kaynaklandığını düşünüyorum, bu da Aktivitenin ve ilgili kaynaklarının asla geri alınmayacağını garanti eder.

Bir Servis ile bir süre önce ters problemi yaşadım, bu yüzden bu düşünceye devam etmemi sağlayan şey bu: Faaliyetinizi süreç öncelik listesinde yüksek tutan bir şey var, böylece sistem GC'sine tabi olmayacak, örneğin bir referans (@Tim) veya bir döngü (@Alvaro). Döngünün sonsuz veya uzun süre çalışan bir öğe olması gerekmez, yalnızca özyinelemeli bir yöntem veya basamaklı döngü (veya bu satırlar boyunca bir şey) gibi çalışan bir şey olması gerekir.

DÜZENLEME: Bunu anladığım kadarıyla, onPause ve onStop Android tarafından gerektiği gibi otomatik olarak çağrılıyor. Yöntemler, barındırma işlemi durdurulmadan önce neye ihtiyacınız olduğunu (değişkenleri kaydetme, durumu manuel olarak kaydetme, vb.) ama açıkça (OnDestroy ile birlikte) OnStop belirtilmektedir notu o olmayabilir her durumda çağrılabilir. Ek olarak, barındırma işlemi aynı zamanda "Ön plan" veya "Görünür" durumuna sahip bir Etkinlik, Hizmet vb. Barındırıyorsa, işletim sistemi işlemi / iş parçacığını durdurmaya bile bakmayabilir. Örneğin: bir Etkinlik ve bir Hizmet aynı süreçte luanlanır ve Hizmet iadeleriSTART_STICKY ,onStartCommand()işlem otomatik olarak en azından görünür bir durum alır. Buradaki kilit nokta bu olabilir, Etkinlik için yeni bir süreç ilan etmeyi deneyin ve bunun bir şeyi değiştirip değiştirmediğine bakın. Bu satırı, Manifest'teki Aktivitenizin bildirimine şu şekilde eklemeyi deneyin: android:process=":proc2"ve Aktiviteniz başka bir şeyle bir süreci paylaşıyorsa testleri tekrar çalıştırın. Buradaki düşünce, eğer Aktivitenizi temizlediyseniz ve sorunun Aktiviteniz olmadığından oldukça eminseniz, o zaman problem başka bir şeydir ve bunun için avlanma zamanıdır.

Ayrıca, onu nerede gördüğümü hatırlayamıyorum (hatta Android dokümanlarında görmüş olsaydım bile), ancak PendingIntentbir Aktiviteye atıfta bulunmakla ilgili bir şeyin bir Aktivitenin bu şekilde davranmasına neden olabileceğini hatırlıyorum .

Burada , onStartCommand()öldürme dışı süreçle ilgili bazı bilgiler içeren sayfaya bir bağlantı var .


Sağladığınız bağlantıya göre (teşekkür ederim), bir etkinlik onStop çağrılmışsa sonlandırılabilir, ancak benim durumumda bir şeyin onStop'un çalışmasını engellediğini düşünüyorum. Neden olduğuna kesinlikle bakacağım. Bununla birlikte, sadece onPause ile faaliyetlerin de sonlandırılabileceğini söylüyor, bu benim durumumda gerçekleşmiyor (Duraklat aranmayı görüyorum ama onStop değil).
Artem Russakovskii

1

Yani gerçekten düşünebildiğim tek şey, bağlama doğrudan veya dolaylı olarak başvuran statik bir değişkeniniz olup olmadığıdır. Hatta uygulamanın bir kısmına atıfta bulunan bir şey bile. Eminim zaten denediniz, ancak her ihtimale karşı önereceğim, sadece çöp toplayıcının aldığından emin olmak için onDestroy () içindeki TÜM statik değişkenlerinizi sıfırlamayı deneyin.


1

Bulduğum en büyük bellek sızıntısı kaynağı, bağlama ilişkin bazı küresel, yüksek düzeyli veya uzun süreli referanslardan kaynaklanıyordu. Herhangi bir yerde bir değişkende depolanan "bağlamı" tutuyorsanız, öngörülemeyen bellek sızıntılarıyla karşılaşabilirsiniz.


Evet, bunu defalarca duydum ve okudum, ancak onu tespit etmekte zorlandım. Keşke bunları nasıl izleyeceğimi güvenilir bir şekilde bulabilsem.
Artem Russakovskii

1
Benim durumumda bu, bağlama eşit olarak ayarlanmış sınıf düzeyinde herhangi bir değişkene çevrildi (örneğin, Class1.variable = getContext ();). Genel olarak, uygulamamdaki her "bağlam" kullanımını yeni bir "getContext" çağrısıyla veya benzeriyle değiştirmek, en büyük bellek sorunlarımı çözdü. Ama benimki kararsız ve kararsızdı, sizin durumunuzdaki gibi tahmin edilemez, bu yüzden muhtemelen farklı bir şey.
D2TheC

1

GetApplicationContext () 'i bir Context gerektiren herhangi bir şeye iletmeyi deneyin. Faaliyetlerinize referans tutan ve bunların gereksiz yere toplanmasını engelleyen genel bir değişkeniniz olabilir.


1

Benim durumumdaki bellek sorununa gerçekten yardımcı olan şeylerden biri, Bitmap'lerim için inPurgeable'ı true olarak ayarlamakla sonuçlandı. Bkz Neden şimdiye BitmapFactory en inPurgeable seçeneğini KULLANMAYIN ki? ve daha fazla bilgi için cevabın tartışması.

Dianne Hackborn'un cevabı ve sonraki tartışmamız (ayrıca teşekkürler, CommonsWare) kafamın karıştığı bazı şeyleri netleştirmeye yardımcı oldu, bu yüzden bunun için teşekkür ederim.


Bunun eski bir konu olduğunu biliyorum, ancak bu konuyu birçok aramada bulduğumu görüyorum. Burada bulabileceğiniz çok şey öğrendiğim harika bir öğreticiyi bağlamak istiyorum: developer.android.com/training/displaying-bitmaps/index.html
Codeversed

0

Seninle aynı problemle karşılaştım. Bir anlık mesajlaşma uygulaması üzerinde çalışıyordum, aynı kişi için, bir ChatActivity'de bir ProfileActivity başlatmak ve bunun tersi mümkün. Başka bir aktivite başlatmak için niyete fazladan bir dize ekledim, başlangıç ​​aktivitesinin sınıf türü bilgisini ve kullanıcı kimliğini alır. Örneğin, ProfileActivity bir ChatActivity başlatır, ardından ChatActivity.onCreate'de, çağırıcı sınıf türünü 'ProfileActivity' ve kullanıcı kimliğini işaretlerim, eğer bir Etkinlik başlatacaksa, kullanıcı için bir 'ProfileActivity' olup olmadığını kontrol ederim . Öyleyse, 'finish ()' çağırın ve yeni bir tane oluşturmak yerine eski ProfileActivity'ye geri dönün. Bellek sızıntısı başka bir şeydir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.