Problemy z pamięcią w aplikacji na Androida - próbowałem wszystkiego i nadal nie udało się


87

Spędziłem 4 pełne dni próbując wszystkiego, co w mojej mocy, aby znaleźć wyciek pamięci w aplikacji, którą tworzę, ale wszystko przestało mieć sens dawno temu.

Aplikacja, którą tworzę, ma charakter społecznościowy, więc pomyśl o profilu Działania (P) i wymień działania z danymi - na przykład odznaki (B). Możesz przeskoczyć z profilu do listy odznak do innych profili, do innych list itp.

Wyobraź sobie więc przepływ taki jak ten P1 -> B1 -> P2 -> B2 -> P3 -> B3, itd. Dla spójności ładuję profile i identyfikatory tego samego użytkownika, więc każda strona P jest taka sama i tak jest każdej stronie B.

Ogólna istota problemu jest taka: po dłuższej nawigacji, w zależności od rozmiaru każdej strony, otrzymuję wyjątek braku pamięci w losowych miejscach - mapy bitowe, ciągi znaków itp. - wydaje się, że nie jest spójny.

Po zrobieniu wszystkiego, co tylko można sobie wyobrazić, aby dowiedzieć się, dlaczego brakuje mi pamięci, nic nie znalazłem. Nie rozumiem, dlaczego Android nie zabija P1, B1 itd., Jeśli zabraknie pamięci po załadowaniu i zamiast tego ulega awarii. Spodziewałbym się, że te wcześniejsze działania umrą i zostaną wskrzeszone, jeśli kiedykolwiek wrócę do nich za pośrednictwem onCreate () i onRestoreInstanceState ().

Nie mówiąc już o tym - nawet jeśli zrobię P1 -> B1 -> Back -> B1 -> Back -> B1, nadal mam awarię. Wskazuje to na jakiś wyciek pamięci, ale nawet po zrzuceniu hprof i użyciu MAT i JProfiler nie mogę tego zlokalizować.

Wyłączyłem ładowanie obrazów z sieci (i zwiększyłem ładowane dane testowe, aby to zrekompensować i uczynić test sprawiedliwym) i upewniłem się, że pamięć podręczna obrazów używa SoftReferences. Android faktycznie próbuje zwolnić kilka SoftReferences, które ma, ale tuż przed wypadnięciem z pamięci.

Strony znaczków pobierają dane z sieci, ładują je do tablicy EntityData z BaseAdapter i przesyłają do ListView (w rzeczywistości używam doskonałego MergeAdapter firmy CommonsWare , ale w tym działaniu związanym z odznaką jest tak naprawdę tylko 1 adapter, ale ja tak czy inaczej chciałem o tym wspomnieć).

Przeszukałem kod i nie udało mi się znaleźć niczego, co mogłoby przeciekać. Wyczyściłem i anulowałem wszystko, co mogłem znaleźć, a nawet System.gc () z lewej i prawej strony, ale aplikacja nadal się zawiesza.

Nadal nie rozumiem, dlaczego nieaktywne działania, które znajdują się na stosie, nie są zbierane i naprawdę chciałbym to rozgryźć.

W tym miejscu szukam wszelkich wskazówek, porad, rozwiązań ... wszystkiego, co mogłoby pomóc.

Dziękuję Ci.


Jeśli więc otworzyłem 15 działań w ciągu ostatnich 20 sekund (użytkownik przechodzi przez bardzo szybko), czy to może być problem? Jaki wiersz kodu należy dodać, aby wyczyścić działanie po jego wyświetleniu? Otrzymuję outOfMemorybłąd. Dzięki!
Ruchir Baronia

Odpowiedzi:


109

Nadal nie rozumiem, dlaczego nieaktywne działania, które znajdują się na stosie, nie są zbierane i naprawdę chciałbym to rozgryźć.

Nie tak to działa. Jedyne zarządzanie pamięcią, które wpływa na cykl życia aktywności, to zarządzanie globalne pamięć we wszystkich procesach, ponieważ Android decyduje, że zaczyna brakować pamięci i dlatego musi zabić procesy w tle, aby odzyskać część.

Jeśli Twoja aplikacja znajduje się na pierwszym planie, rozpoczynając coraz więcej działań, nigdy nie przechodzi w tło, więc zawsze osiągnie limit lokalnej pamięci procesu, zanim system zbliży się do zabicia swojego procesu. (A kiedy zabije swój proces, zabije proces obsługujący wszystkie pliki działania, w tym to, co jest obecnie na pierwszym planie).

Wydaje mi się więc, że podstawowym problemem jest to, że pozwalasz na wykonywanie zbyt wielu działań w tym samym czasie i / lub każde z tych działań obejmuje zbyt wiele zasobów.

Wystarczy przeprojektować nawigację, aby nie polegać na gromadzeniu dowolnej liczby potencjalnie ciężkich działań. Jeśli nie wykonasz poważnej ilości rzeczy w onStop () (na przykład wywołanie setContentView () w celu wyczyszczenia hierarchii widoków działania i wyczyszczenia zmiennych z wszystkiego, co może się trzymać), po prostu zabraknie ci pamięci.

Możesz rozważyć użycie nowych interfejsów API fragmentów, aby zastąpić ten dowolny stos działań pojedynczym działaniem, które ściślej zarządza swoją pamięcią. Na przykład, jeśli używasz zaplecza stosu fragmentów, gdy fragment trafia na tylny stos i nie jest już widoczny, wywoływana jest jego metoda onDestroyView () w celu całkowitego usunięcia hierarchii widoku, co znacznie zmniejsza jego ślad.

Teraz, o ile spadasz w strumieniu, w którym naciskasz, przechodzisz do działania, naciskasz wstecz, przechodzisz do innej czynności itp. I nigdy nie masz głębokiego stosu, to tak, po prostu masz wyciek. W tym poście na blogu opisano, jak debugować wycieki: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
W obronie PO nie to sugeruje dokumentacja. Cytując developer.android.com/guide/topics/fundamentals/ ... „(Gdy działanie zostanie zatrzymane), nie jest już widoczne dla użytkownika i może zostać zabite przez system, gdy gdzie indziej potrzebna jest pamięć”. "Jeśli działanie jest wstrzymane lub zatrzymane, system może usunąć je z pamięci ... żądając zakończenia (wywołanie metody finish ())" "(wywoływana jest metoda onDestroy ()), ponieważ system tymczasowo niszczy to wystąpienie zajęcie, aby zaoszczędzić miejsce ”.
CommonsWare,

42
Na tej samej stronie: „Jednak gdy system niszczy działanie w celu odzyskania pamięci”. Wskazujesz na to, że Android nigdy nie niszczy działań w celu odzyskania pamięci, ale tylko zakończy proces, aby to zrobić. Jeśli tak, ta strona wymaga poważnego przepisania, ponieważ wielokrotnie sugeruje, że Android zniszczy działanie, aby odzyskać pamięć. Należy również zauważyć, że wiele z cytowanych fragmentów istnieje również w ActivityJavaDocs.
CommonsWare,

6
Myślałem, że umieściłem to tutaj, ale wysłałem prośbę o zaktualizowanie dokumentacji na ten temat: code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller.

15
Jest rok 2013, a dokumenty tylko jaśniej przedstawiają fałszywy punkt: developer.android.com/training/basics/activity-lifecycle/… , „Po zatrzymaniu Twojej aktywności system może zniszczyć instancję, jeśli będzie trzeba ją odzyskać pamięć systemowa. W EKSTREMALNYCH przypadkach system może po prostu zabić proces aplikacji ”
kaay,

2
Cóż, cholera. Zdecydowanie zawsze myślałem (ze względu na dokumentację), że system zarządzał zatrzymanymi działaniami w twoim procesie i serializował i deserializował (niszczył i tworzył) je w razie potrzeby.
dcow

21

Kilka porad:

  1. Upewnij się, że nie ujawniasz kontekstu aktywności.

  2. Upewnij się, że nie przechowujesz odniesień na mapach bitowych. Wyczyść wszystkie swoje ImageView w działaniu # onStop, coś takiego:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Przetwarzaj mapy bitowe, jeśli już ich nie potrzebujesz.

  4. Jeśli używasz pamięci podręcznej, takiej jak memory-lru, upewnij się, że nie używa ona zbyt dużo pamięci.

  5. Nie tylko obrazy zajmują dużo pamięci, upewnij się, że nie przechowujesz w pamięci zbyt wielu innych danych. Może się to łatwo zdarzyć, jeśli masz nieskończoną liczbę list w swojej aplikacji. Spróbuj buforować dane w DataBase.

  6. W systemie Android 4.2 występuje błąd (stackoverflow # 13754876) dotyczący przyspieszania sprzętowego, więc jeśli użyjesz go hardwareAccelerated=truew swoim manifeście, spowoduje to wyciek pamięci. GLES20DisplayList- zachowaj odniesienia, nawet jeśli wykonałeś krok (2) i nikt inny nie odwołuje się do tej mapy bitowej. Tutaj potrzebujesz:

    a) wyłączyć akcelerację sprzętową dla api 16/17;
    lub
    b) odłącz widok zawierający bitmapę

  7. W przypadku Androida 3+ możesz spróbować użyć android:largeHeap="true"w swoim AndroidManifest. Ale to nie rozwiąże twoich problemów z pamięcią, po prostu je odłóż.

  8. Jeśli potrzebujesz np. Nieskończonej nawigacji, to Fragmenty - powinny być twoim wyborem. Będziesz więc mieć 1 aktywność, która będzie po prostu przełączać się między fragmentami. W ten sposób rozwiążesz również niektóre problemy z pamięcią, takie jak numer 4.

  9. Użyj analizatora pamięci, aby znaleźć przyczynę wycieku pamięci.
    Oto bardzo dobry film z Google I / O 2011: Zarządzanie pamięcią dla aplikacji na Androida
    Jeśli masz do czynienia z bitmapami, należy to przeczytać: Wydajne wyświetlanie map bitowych


Jeśli więc otworzyłem 15 działań w ciągu ostatnich 20 sekund (użytkownik przechodzi przez bardzo szybko), czy to może być problem? Jaki wiersz kodu należy dodać, aby wyczyścić działanie po jego wyświetleniu? Otrzymuję outOfMemorybłąd. Dzięki!
Ruchir Baronia

4

Mapy bitowe są często winowajcami błędów pamięci w systemie Android, więc byłoby to dobre miejsce do podwójnego sprawdzenia.


Jeśli więc otworzyłem 15 działań w ciągu ostatnich 20 sekund (użytkownik przechodzi przez bardzo szybko), czy to może być problem? Jaki wiersz kodu należy dodać, aby wyczyścić działanie po jego wyświetleniu? Otrzymuję outOfMemorybłąd. Dzięki!
Ruchir Baronia

2

Czy masz jakieś odniesienia do każdego działania? AFAIK jest to powód, który powstrzymuje Androida przed usuwaniem działań ze stosu.

Czy możesz odtworzyć ten błąd również na innych urządzeniach? Doświadczyłem dziwnego zachowania niektórych urządzeń z Androidem w zależności od ROM i / lub producenta sprzętu.


Udało mi się to odtworzyć na Droidzie z CM7 z maksymalną stertą ustawioną na 16 MB, co jest tą samą wartością, którą testuję na emulatorze.
Artem Russakovskii

Możesz być na czymś. Kiedy rozpocznie się druga czynność, czy pierwsza będzie wykonywana onPause-> onStop, czy tylko onPause? Ponieważ drukuję wszystko na wywołaniach cyklu życia ******* i widzę onPause -> onCreate, bez onStop. Jeden z zrzutów awaryjnych faktycznie powiedział coś takiego jak onPause = true lub onStop = false dla 3 działań, które zabija.
Artem Russakovskii

OnStop powinno być wywoływane, gdy aktywność opuszcza ekran, ale może nie być, jeśli zostanie wcześnie odzyskana przez system.
dten

Nie jest odzyskiwany, ponieważ nie widzę wywołań onCreate i onRestoreInstanceState, jeśli kliknę wstecz.
Artem Russakovskii,

zgodnie z cyklem życia te mogą nigdy nie zostać nazwane, sprawdź moją odpowiedź, która zawiera link do oficjalnego bloga deweloperów, jest więcej niż prawdopodobne, w jaki sposób przekazujesz swoje mapy bitowe
dten

2

Myślę, że problem może być kombinacją wielu czynników podanych tutaj w odpowiedziach, co powoduje problemy. Jak powiedział @Tim, (statyczne) odwołanie do działania lub elementu w tym działaniu może spowodować, że GC pominie działanie. Oto artykuł omawiający ten aspekt. Wydaje mi się, że prawdopodobny problem wynika z czegoś, co utrzymuje działanie w stanie „widocznego procesu” lub wyższym, co w dużym stopniu gwarantuje, że działanie i powiązane z nim zasoby nigdy nie zostaną odzyskane.

Jakiś czas temu przeszedłem przez odwrotny problem z usługą, więc to właśnie doprowadziło mnie do tej myśli: jest coś, co sprawia, że ​​Twoja aktywność jest wysoko na liście priorytetów procesu, więc nie będzie podlegać systemowi GC, na przykład odniesienie (@Tim) lub pętla (@Alvaro). Pętla nie musi być niekończącym się lub długo działającym elementem, po prostu czymś, co działa podobnie jak metoda rekurencyjna lub pętla kaskadowa (lub coś podobnego do tych linii).

EDYCJA: Jak rozumiem, onPause i onStop są wywoływane automatycznie w razie potrzeby przez Androida. Metody są przeznaczone głównie do obejścia, abyś mógł zająć się tym, czego potrzebujesz, zanim proces hostingu zostanie zatrzymany (zapisywanie zmiennych, ręczne zapisywanie stanu itp.); ale pamiętaj, że jest jasno określone, że onStop (razem z onDestroy) może nie być wywoływany w każdym przypadku. Ponadto, jeśli proces hostingu udostępnia również działanie, usługę itp. O statusie „Forground” lub „Visible”, system operacyjny może nawet nie patrzeć na zatrzymanie procesu / wątku. Na przykład: działanie i usługa są przeprowadzane w tym samym procesie, a usługa wraca START_STICKYzonStartCommand()proces automatycznie przyjmuje co najmniej widoczny status. To może być klucz tutaj, spróbuj zadeklarować nowy proces dla działania i zobacz, czy to coś zmieni. Spróbuj dodać ten wiersz do deklaracji swojej Aktywności w Manifeście jako:, android:process=":proc2"a następnie uruchom testy ponownie, jeśli Twoja Aktywność dzieli proces z czymkolwiek innym. Pomysł jest taki, że jeśli wyczyściłeś swoją Aktywność i jesteś prawie pewien, że problem nie jest twoją Aktywnością, to coś innego jest problemem i czas na to polować.

Nie pamiętam też, gdzie to widziałem (jeśli nawet widziałem to w dokumentach Androida), ale pamiętam coś o PendingIntentodwoływaniu się do działania, które może spowodować, że działanie zachowa się w ten sposób.

Tutaj jest link do onStartCommand()strony z kilkoma spostrzeżeniami na temat tego procesu.


Na podstawie podanego linku (dziękuję) działanie może zostać zabite, jeśli wywołano jego onStop, ale w moim przypadku wydaje mi się, że coś uniemożliwia uruchomienie onStop. Zdecydowanie sprawdzę, dlaczego. Jednak mówi również, że działania z tylko onPause mogą również zostać zabite, co nie ma miejsca w moim przypadku (widzę, że jest wywoływany onPause, ale nie onStop).
Artem Russakovskii

1

więc jedyną rzeczą, o której naprawdę mogę pomyśleć, jest to, czy masz zmienną statyczną, która odwołuje się bezpośrednio lub pośrednio do kontekstu. Nawet coś tak bardzo, jak odniesienie do części aplikacji. Jestem pewien, że już tego próbowałeś, ale zasugeruję to na wszelki wypadek, spróbuj po prostu wyzerować WSZYSTKIE twoje zmienne statyczne w onDestroy (), aby upewnić się, że garbage collector je dostanie


1

Największe źródło wycieku pamięci, jakie znalazłem, było spowodowane jakimś globalnym, wysokopoziomowym lub długotrwałym odniesieniem do kontekstu. Jeśli przechowujesz „kontekst” w zmiennej gdziekolwiek, możesz napotkać nieprzewidywalne wycieki pamięci.


Tak, słyszałem i czytałem to wiele razy, ale miałem problem z ustaleniem tego. Gdybym tylko mógł niezawodnie wymyślić, jak je wyśledzić.
Artem Russakovskii,

1
W moim przypadku to przełożyło się na dowolną zmienną na poziomie klasy, która została ustawiona jako równa kontekstowi (np. Class1.variable = getContext ();). Ogólnie rzecz biorąc, zastąpienie każdego użycia „kontekstu” w mojej aplikacji nowym wywołaniem „getContext” lub podobnego rozwiązania rozwiązało moje największe problemy z pamięcią. Ale moje były niestabilne i nieregularne, nieprzewidywalne jak w twoim przypadku, więc prawdopodobnie coś innego.
D2TheC

1

Spróbuj przekazać metodę getApplicationContext () do wszystkiego, co wymaga kontekstu. Możesz mieć zmienną globalną, która zawiera odniesienie do twoich działań i zapobiega ich zbieraniu jako śmieci.


1

Jedną z rzeczy, które naprawdę pomogły w problemie z pamięcią w moim przypadku, okazało się ustawienie InPurgeable na true dla moich map bitowych. Zobacz Dlaczego nigdy nie miałbym używać opcji inPurgeable BitmapFactory? i dyskusja na temat odpowiedzi, aby uzyskać więcej informacji.

Odpowiedź Dianne Hackborn i nasza późniejsza dyskusja (również dzięki, CommonsWare) pomogły wyjaśnić pewne rzeczy, w których byłem zdezorientowany, więc dziękuję za to.


Wiem, że to stary temat, ale wydaje mi się, że znajduję ten wątek przy wielu wyszukiwaniach. Chcę
zamieścić

0

Napotkałem z tobą ten sam problem. Pracowałem nad aplikacją do obsługi wiadomości błyskawicznych, dla tego samego kontaktu możliwe jest uruchomienie ProfileActivity w ChatActivity i odwrotnie. Po prostu dodaję ciąg znaków do zamiaru rozpoczęcia innej czynności, pobiera informacje o typie klasy działania startera i identyfikatorze użytkownika. Na przykład ProfileActivity uruchamia ChatActivity, a następnie w ChatActivity.onCreate zaznaczam typ klasy wywołującej „ProfileActivity” i identyfikator użytkownika, jeśli ma rozpocząć działanie, sprawdzę, czy jest to „ProfileActivity” dla użytkownika, czy nie . Jeśli tak, po prostu wywołaj „finish ()” i wróć do poprzedniego ProfileActivity zamiast tworzyć nowy. Wyciek pamięci to inna sprawa.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.