Додаток для Android закінчився проблемами з пам’яттю - спробував усе і все ще втратив


87

Я витратив 4 повних дні, намагаючись зробити все можливе, щоб з’ясувати витік пам’яті в програмі, яку розробляю, але речі давно перестали мати сенс.

Додаток, який я розробляю, має соціальний характер, тому подумайте про профіль Діяльність (P) та перелічіть Діяльність із даними - наприклад, значки (В). Ви можете перейти від профілю до списку значків до інших профілів, до інших списків тощо.

Тож уявіть собі такий потік, як P1 -> B1 -> P2 -> B2 -> P3 -> B3 і т. Д. Для послідовності я завантажую профілі та значки одного і того ж користувача, отже кожна сторінка P однакова і така ж кожна сторінка B.

Загальна суть проблеми полягає в тому, що, провівши трохи навігації, залежно від розміру кожної сторінки, я отримую виняток, який не вистачає пам’яті в випадкових місцях - растрові карти, рядки тощо - це, здається, не узгоджується.

Зробивши все, що можна собі уявити, щоб зрозуміти, чому у мене закінчується пам’ять, я нічого не придумав. Я не розумію, чому Android не вбиває P1, B1 тощо, якщо при завантаженні у нього закінчується пам’ять, а замість цього відбувається збій. Я би очікував, що ці попередні дії помруть і воскреснуть, якщо коли-небудь повернусь до них через onCreate () та onRestoreInstanceState ().

Не кажучи вже про це - навіть якщо я роблю P1 -> B1 -> Назад -> B1 -> Назад -> B1, я все одно отримую збій. Це вказує на якийсь витік пам'яті, але навіть після скидання hprof та використання MAT та JProfiler я не можу його точно визначити.

Я вимкнув завантаження зображень з Інтернету (і збільшив завантажені тестові дані, щоб це компенсувати та зробити тест справедливим) і переконався, що кеш зображень використовує SoftReferences. Android насправді намагається звільнити кілька програм SoftReferences, які він має, але безпосередньо перед тим, як виходить із пам'яті.

Сторінки бейджів отримують дані з Інтернету, завантажують їх у масив EntityData з BaseAdapter і подають до ListView (я насправді використовую чудовий MergesAdapter від CommonsWare , але в цій діяльності з бейджами в будь-якому випадку є лише 1 адаптер, але я хотів би згадати цей факт у будь-якому випадку).

Я пройшов код і не зміг знайти нічого, що могло б просочитися. Я очистив і обнулив все, що міг знайти, і навіть System.gc () ліворуч і праворуч, але програма все одно аварійно завершує роботу.

Я все ще не розумію, чому неактивні дії, які є в стеку, не отримують, і я б дуже хотів це зрозуміти.

На даний момент я шукаю будь-які підказки, поради, рішення ... все, що може допомогти.

Дякую.


Отже, якщо я відкрив 15 дій за останні 20 секунд (користувач проходить дуже швидко), чи може це бути проблемою? Який рядок коду слід додати для очищення дії після її відображення? Я отримую повідомлення про outOfMemoryпомилку. Дякую!
Ruchir Baronia

Відповіді:


109

Я все ще не розумію, чому неактивні дії, які є в стеку, не отримують, і я б дуже хотів це зрозуміти.

Не так усе працює. Єдине управління пам’яттю, яке впливає на життєвий цикл діяльності, - це глобальна пам’ять у всіх процесах, оскільки Android вирішує, що в ній недостатньо пам’яті, і тому потрібно вбивати фонові процеси, щоб повернути деякі.

Якщо ваша програма сидить на передньому плані, починаючи все більше і більше дій, вона ніколи не йде на другий план, тому вона завжди досягне свого локального обмеження пам’яті процесу, перш ніж система коли-небудь наблизиться до вбивства свого процесу. (І коли він уб’є свій процес, він уб’є процес, в якому розміщуються всі дії, включаючи те, що зараз є на передньому плані.)

Отже, мені здається, що ваша основна проблема полягає в тому, що ви дозволяєте виконувати занадто багато заходів одночасно, і / або кожна з цих видів діяльності тримає занадто багато ресурсів.

Вам просто потрібно перепроектувати навігацію, щоб не покладатися на довільну кількість потенційно важких дій. Якщо ви не зробите серйозної кількості речей у програмі onStop () (наприклад, виклику setContentView (), щоб очистити ієрархію перегляду діяльності та очистити змінні від того, на чому вона може триматися), у вас просто закінчиться пам’ять.

Можливо, ви захочете розглянути можливість використання нових фрагментних API для заміни цього довільного стеку дій на одну діяльність, яка більш жорстко управляє його пам'яттю. Наприклад, якщо ви використовуєте можливості зворотного стеку фрагментів, коли фрагмент переходить на задній стек і його вже не видно, його метод onDestroyView () викликається для повного видалення його ієрархії подання, значно зменшуючи його розмір.

Тепер, наскільки ви падаєте в потоці, де ви натискаєте назад, переходите до активності, натискаєте назад, переходите до іншої діяльності тощо, і ніколи не маєте глибокого стека, тоді так, у вас просто є витік. Цей допис у блозі описує способи налагодження витоків: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
На захист ОП, це не те, що пропонується в документації. Посилання на developer.android.com/guide/topics/fundamentals/… "(Коли діяльність припиняється), вона більше не відображається користувачеві, і її може вбити система, коли пам'ять потрібна в іншому місці." "Якщо дія призупинена або зупинена, система може скинути її з пам'яті ... попросивши її закінчити (викликаючи метод finish ())" "(викликається onDestroy ()), оскільки система тимчасово знищує цей екземпляр діяльність для економії місця ".
CommonsWare

42
На тій же сторінці "Однак, коли система знищує діяльність, щоб відновити пам'ять". Ви вказуєте, що Android ніколи не знищує дії з відновлення пам’яті, а лише припиняє процес для цього. У такому випадку цю сторінку потрібно серйозно переписати, оскільки вона неодноразово передбачає, що Android знищить діяльність, щоб повернути пам’ять. Також зауважте, що багато з цитованих уривків також існують у ActivityJavaDocs.
CommonsWare

6
Я думав, що помістив це тут, але опублікував запит на оновлення документації для цього: code.google.com/p/android/issues/detail?id=21552
Джастін Брейтфеллер

15
Це 2013 рік, і документи прийшли лише для більш чіткого подання хибної точки: developer.android.com/training/basics/activity-lifecycle/… , "Після припинення вашої діяльності система може знищити екземпляр, якщо потрібно відновити в екстремальних випадках система може просто вбити ваш додаток "
kaay,

2
Ну, лайно. Я, безумовно, завжди думав (через документи), що система керує зупиненими діями у вашому процесі і буде серіалізувати та десеріалізувати (знищувати та створювати) їх за потреби.
dcow

21

Кілька порад:

  1. Переконайтесь, що у вас немає контексту активності.

  2. Переконайтеся, що ви не зберігаєте посилання на растрові зображення. Очистіть усі ваші ImageView в Activity # onStop, приблизно так:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Утилізуйте растрові зображення, якщо вони вам більше не потрібні.

  4. Якщо ви використовуєте кеш пам'яті, наприклад memory-lru, переконайтеся, що він не використовує багато пам'яті.

  5. Не тільки зображення займають багато пам’яті, переконайтесь, що ви не зберігаєте в пам'яті занадто багато інших даних. Це легко може статися, якщо у вашому додатку є нескінченні списки. Спробуйте кешувати дані в DataBase.

  6. В android 4.2 є помилка (stackoverflow # 13754876) з апаратним прискоренням, тому, якщо ви використовуєте hardwareAccelerated=trueу своєму маніфесті, це буде втрачати пам'ять. GLES20DisplayList- продовжуйте зберігати посилання, навіть якщо ви виконували крок (2), і ніхто інший не посилається на це растрове зображення. Тут вам потрібно:

    а) вимкнути апаратне прискорення для api 16/17;
    або
    б) від'єднати вигляд, що містить растрове зображення

  7. Для Android 3+ ви можете спробувати використовувати android:largeHeap="true"у своєму AndroidManifest. Але це не вирішить ваших проблем з пам’яттю, просто відкладіть їх.

  8. Якщо вам потрібна нескінченна навігація, то фрагменти - це ваш вибір. Отже, у вас буде 1 діяльність, яка просто переключатиметься між фрагментами. Таким чином ви також вирішите деякі проблеми з пам'яттю, наприклад номер 4.

  9. За допомогою аналізатора пам'яті з’ясуйте причину витоку пам’яті.
    Ось дуже хороше відео від Google I / O 2011: Управління пам’яттю для програм для Android.
    Якщо ви маєте справу з растровими зображеннями, це повинно бути обов’язково: Ефективне відображення растрових зображень


Отже, якщо я відкрив 15 дій за останні 20 секунд (користувач проходить дуже швидко), чи може це бути проблемою? Який рядок коду слід додати для очищення дії після її відображення? Я отримую повідомлення про outOfMemoryпомилку. Дякую!
Ruchir Baronia

4

Растрові карти часто є винуватцем помилок пам'яті на Android, тому це було б гарною областю для повторної перевірки.


Отже, якщо я відкрив 15 дій за останні 20 секунд (користувач проходить дуже швидко), чи може це бути проблемою? Який рядок коду слід додати для очищення дії після її відображення? Я отримую повідомлення про outOfMemoryпомилку. Дякую!
Ruchir Baronia

2

Чи є у вас посилання на кожну діяльність? AFAIK це причина, яка заважає Android видаляти дії зі стеку.

Ми можемо відтворити цю помилку і на інших пристроях? Я відчував дивну поведінку деяких пристроїв Android залежно від виробника ПЗУ та / або обладнання.


Я зміг відтворити це на Droid, на якому запущено CM7 з максимальною купою, встановленою на 16 МБ, що є тим самим значенням, яке я тестую на емуляторі.
Артем Руссаковський

Ви можете бути на чомусь. Коли розпочнеться друга діяльність, чи буде перша робити onPause-> onStop чи просто onPause? Оскільки я друкую все на викликах життєвого циклу *******, і я бачу onPause -> onCreate, без onStop. І один із звалищів аварій насправді сказав щось на зразок onPause = true або onStop = false приблизно за 3 дії, які він вбивав.
Артем Руссаковський

OnStop слід викликати, коли діяльність залишає екран, але може не відбутися, якщо система відновлена ​​достроково.
dten

Він не відновлюється, тому що я не бачу onCreate і onRestoreInstanceState, якщо я натискаю назад.
Артем Руссаковський

відповідно до життєвого циклу, їх ніколи не можна назвати, перевірте мою відповідь, яка має посилання на офіційний блог розробника, це, швидше за все, спосіб передачі ваших растрових зображень
dten

2

Я думаю, що проблема, можливо, сукупність багатьох факторів, зазначених тут у відповідях, є тим, що створює вам проблеми. Як сказав @Tim, (статичне) посилання на діяльність або елемент у цій діяльності може призвести до того, що GC пропустить Activity. Ось стаття, що обговорює цей аспект. Я вважаю, що можлива проблема виникає внаслідок чогось, що підтримує Діяльність у стані "Видимий процес" або вище, що в значній мірі гарантує, що Діяльність та пов'язані з нею ресурси ніколи не будуть повернуті.

Я ще деякий час проходив через протилежну проблему зі Службою, тож саме це і підштовхнуло мене до цієї думки: є щось, що тримає вашу Діяльність високо в списку пріоритетів процесу, щоб вона не підлягала системному GC, наприклад посилання (@Tim) або цикл (@Alvaro). Цикл не повинен бути нескінченним або тривалим елементом, це просто те, що працює багато, як рекурсивний метод або каскадний цикл (або щось подібне).

РЕДАГУВАТИ: Як я розумію, onPause і onStop автоматично викликаються Android, коли це потрібно. Методи є в основному для того, щоб ви їх перевизначили, щоб ви могли подбати про те, що вам потрібно, до того, як процес хостингу буде зупинено (збереження змінних, збереження стану вручну тощо); але зауважте, що чітко зазначено, що onStop (разом з onDestroy) може бути викликаний не в кожному випадку. Крім того, якщо процес хостингу також розміщує діяльність, послугу тощо, які мають статус "Forground" або "Visible", ОС може навіть не дивитись на зупинку процесу / потоку. Наприклад: "Діяльність" і "Послуга" луніруються в одному процесі, і Служба повертається START_STICKYзonStartCommand()процес автоматично набуває щонайменше видимого стану. Це може бути ключовим тут, спробуйте оголосити новий процес для Діяльності та перевірте, чи це щось змінює. Спробуйте додати цей рядок до декларації про вашу активність у Маніфесті як: android:process=":proc2"а потім запустіть тести ще раз, якщо ваша активність поділяє процес з чимось іншим. Думка тут полягає в тому, що якщо ви очистили свою активність і майже впевнені, що проблема не у вашій діяльності, то проблема полягає в іншому, і час її шукати.

Крім того, я не пам’ятаю, де я це бачив (якщо навіть бачив у документах Android), але пам’ятаю, щось про PendingIntentпосилання на Активність може призвести до того, що Активність поводиться так.

Ось посилання на onStartCommand()сторінку з деякими уявленнями про процес невбивства.


На підставі наданого вами посилання (дякую), діяльність може бути знищена, якщо було викликано її onStop, але в моєму випадку я думаю, що щось заважає запустити onStop. Я точно вивчу, чому. Однак там також сказано, що також можуть бути вбиті дії лише з onPause, що не відбувається в моєму випадку (я бачу, що onPause викликають, але не onStop).
Артем Руссаковський

1

тож єдине, про що я дійсно можу думати, це якщо у вас є статична змінна, яка прямо чи опосередковано посилається на контекст. Навіть щось на зразок посилання на частину програми. Я впевнений, що ви це вже спробували, але я запропоную це на всякий випадок, спробуйте просто обнулити ВСІ ваші статичні змінні в onDestroy (), щоб переконатися, що збирач сміття отримує це


1

Найбільше джерело витоку пам'яті, яке я знайшов, було спричинене глобальним, високим рівнем або давнім посиланням на контекст. Якщо ви зберігаєте "контекст", який зберігається у змінній де завгодно, ви можете зіткнутися з непередбачуваними витоками пам'яті.


Так, я це чув і читав багато разів, але мені було важко закріпити це. Якби я міг надійно зрозуміти, як їх простежити.
Артем Руссаковський

1
У моєму випадку це перекладено в будь-яку змінну на рівні класу, яка була встановлена ​​рівною контексту (наприклад, Class1.variable = getContext ();). Загалом, заміна кожного використання "контексту" в моєму додатку на новий виклик "getContext" або подібного вирішила мої найбільші проблеми з пам'яттю. Але мої були нестабільні та непостійні, не передбачувані, як у вашому випадку, тож можливо щось інше.
D2TheC

1

Спробуйте передати getApplicationContext () усьому, що потребує контексту. Можливо, у вас є глобальна змінна, яка містить посилання на ваші дії та не дозволяє їм збирати сміття.


1

Однією з речей, яка дійсно допомогла проблемі з пам'яттю в моєму випадку, виявилося встановлення параметра inPurgeable в значення true для моїх растрових зображень. Подивіться, чому б я ніколи не використовував опцію inPurgeable від BitmapFactory? та обговорення відповіді для отримання додаткової інформації.

Відповідь Діанни Хакборн та наше подальше обговорення (також подяка, CommonsWare) допомогли прояснити деякі речі, в яких я був розгублений, тож дякую за це.


Я знаю, що це стара тема, але, здається, я знаходжу цю тему під час багатьох пошуків. Я хочу зв’язати чудовий навчальний посібник, про який я так багато дізнався, з якого ви можете знайти тут: developer.android.com/training/displaying-bitmaps/index.html
Codeversed

0

Я зіткнувся з тією ж проблемою з вами. Я працював над програмою обміну миттєвими повідомленнями, для того ж контакту можна запустити ProfileActivity у ChatActivity, і навпаки. Я просто додаю рядок додатково в намір розпочати іншу діяльність, вона бере інформацію про тип класу початкової діяльності та ідентифікатор користувача. Наприклад, ProfileActivity запускає ChatActivity, тоді в ChatActivity.onCreate я позначаю тип класу виклику «ProfileActivity» та ідентифікатор користувача, якщо він збирається запустити Activity, я перевірю, чи є це «ProfileActivity» для користувача чи ні. . Якщо так, просто зателефонуйте 'finish ()' і поверніться до колишнього ProfileActivity, замість того, щоб створювати новий. Витік пам'яті - це інша справа.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.