Проблемы с памятью в приложении для Android - все перепробовали и все еще не понимают

87

Я потратил 4 полных дня, пытаясь изо всех сил выяснить утечку памяти в разрабатываемом мной приложении, но все это давно перестало иметь смысл.

Приложение, которое я разрабатываю, носит социальный характер, поэтому подумайте о профильных мероприятиях (P) и перечислите действия с данными - например, значки (B). Вы можете переходить из профиля в список значков, в другие профили, в другие списки и т. Д.

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

Общая суть проблемы такова: после некоторой навигации, в зависимости от размера каждой страницы, я получаю исключение нехватки памяти в случайных местах - растровых изображениях, строках и т. Д. - это не похоже на согласованность.

Сделав все возможное, чтобы выяснить, почему у меня заканчивается память, я ничего не придумал. Я не понимаю, почему Android не убивает P1, B1 и т. Д., Если ему не хватает памяти при загрузке и вместо этого происходит сбой. Я ожидал, что эти предыдущие действия умрут и воскреснут, если я когда-нибудь вернусь к ним через onCreate () и onRestoreInstanceState ().

Не говоря уже об этом - даже если я сделаю P1 -> B1 -> Back -> B1 -> Back -> B1, у меня все равно будет сбой. Это указывает на какую-то утечку памяти, но даже после сброса hprof и использования MAT и JProfiler я не могу ее точно определить.

Я отключил загрузку изображений из Интернета (и увеличил количество загружаемых тестовых данных, чтобы компенсировать это и сделать тест честным) и убедился, что кеш изображений использует SoftReferences. Android фактически пытается освободить несколько имеющихся у него SoftReferences, но прямо перед тем, как он вылетает из памяти.

Страницы значков получают данные из Интернета, загружают их в массив EntityData из BaseAdapter и передают их в ListView (на самом деле я использую отличный MergeAdapter CommonsWare , но в этом действии Badge действительно есть только 1 адаптер, но я в любом случае хотел упомянуть этот факт).

Я просмотрел код и не смог найти ничего, что могло бы протечь. Я очистил и аннулировал все, что смог найти, и даже System.gc () слева и справа, но приложение все равно вылетает.

Я до сих пор не понимаю, почему неактивные действия, которые находятся в стеке, не собираются, и мне бы очень хотелось в этом разобраться.

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

Спасибо.

Артем Русаковский
источник
Итак, если я открыл 15 действий за последние 20 секунд (пользователь выполняет очень быстро), может ли это быть проблемой? Какую строку кода я должен добавить, чтобы очистить действие после его отображения? Я получаю outOfMemoryошибку Благодарность!
Ruchir Baronia 04

Ответы:

109

Я до сих пор не понимаю, почему неактивные действия, которые находятся в стеке, не собираются, и мне бы очень хотелось в этом разобраться.

Все не так. Единственное управление памятью, которое влияет на жизненный цикл активности, - это глобальная память во всех процессах, поскольку Android решает, что ей не хватает памяти, и поэтому для ее восстановления необходимо завершить фоновые процессы.

Если ваше приложение находится на переднем плане, начиная все больше и больше действий, оно никогда не переходит в фоновый режим, поэтому оно всегда достигает своего локального лимита памяти процесса, прежде чем система когда-либо приблизится к тому, чтобы убить свой процесс. (И когда он действительно убивает свой процесс, он убивает процесс, в котором выполняются все действия, включая все, что в данный момент находится на переднем плане.)

Мне кажется, ваша основная проблема заключается в следующем: вы позволяете запускать слишком много действий одновременно, и / или каждое из этих действий требует слишком большого количества ресурсов.

Вам просто нужно изменить дизайн вашей навигации, чтобы не полагаться на произвольное количество потенциально тяжелых действий. Если вы не сделаете серьезный объем работы в onStop () (например, вызовете setContentView (), чтобы очистить иерархию представления активности и очистить переменные от всего, что она может удерживать), у вас просто закончится нехватка памяти.

Вы можете рассмотреть возможность использования новых API-интерфейсов Fragment, чтобы заменить этот произвольный стек действий одним действием, которое более жестко управляет своей памятью. Например, если вы используете средства обратного стека фрагментов, когда фрагмент попадает в задний стек и больше не отображается, вызывается его метод onDestroyView (), чтобы полностью удалить его иерархию представлений, значительно уменьшая его размер.

Теперь, если вы терпите крах в потоке, когда вы нажимаете назад, переходите к действию, нажимаете назад, переходите к другому действию и т.д. и никогда не имеете глубокого стека, тогда да, у вас просто утечка. Это сообщение в блоге описывает, как отлаживать утечки: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html

хакбод
источник
47
В защиту OP это не то, что предлагает документация. Цитата developer.android.com/guide/topics/fundamentals/… «(Когда действие остановлено), оно больше не отображается для пользователя и может быть уничтожено системой, когда память понадобится где-то еще». «Если действие приостановлено или остановлено, система может удалить его из памяти ... запросив его завершить (вызывая его метод finish ())» «(вызывается onDestroy ()), потому что система временно уничтожает этот экземпляр деятельность по экономии места ".
CommonsWare
42
На той же странице «Однако, когда система уничтожает действие, чтобы восстановить память». Вы указываете на то, что Android никогда не уничтожает действия по освобождению памяти, а только завершает процесс для этого. Если это так, то эту страницу необходимо серьезно переписать, поскольку она неоднократно предполагает, что Android уничтожит действие по освобождению памяти. Также обратите внимание, что многие из процитированных отрывков также существуют в ActivityJavaDocs.
CommonsWare
6
Я думал, что поместил это здесь, но я разместил запрос на обновление документации по этому поводу
Джастин Брайтфеллер
15
На дворе 2013 год, и документы пришли только для того, чтобы более четко представить ложную точку зрения: developer.android.com/training/basics/activity-lifecycle/… , «Как только ваша деятельность будет остановлена, система может уничтожить экземпляр, если ему потребуется восстановить системная память. В ЭКСТРЕМАЛЬНЫХ случаях система может просто убить процесс вашего приложения »
kaay
2
Ну дерьмо. Я определенно всегда думал (из-за документации), что система управляет остановленными действиями в вашем процессе и будет сериализовать и десериализовать (уничтожить и создать) их по мере необходимости.
dcow 08
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. Не только изображения занимают много памяти, убедитесь, что вы не храните в памяти слишком много других данных. Это легко может произойти, если в вашем приложении есть бесконечные списки. Попробуйте кэшировать данные в базе данных.

  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ошибку Благодарность!
Ручир Барония, 04
4

Растровые изображения часто являются виновниками ошибок памяти на Android, так что это хорошая область для двойной проверки.

Эд Бернетт
источник
Итак, если я открыл 15 действий за последние 20 секунд (пользователь выполняет очень быстро), может ли это быть проблемой? Какую строку кода я должен добавить, чтобы очистить действие после его отображения? Я получаю outOfMemoryошибку Благодарность!
Ручир Барония, 04
2

У вас есть ссылки на каждое действие? AFAIK, это причина, по которой Android не может удалять действия из стека.

Мы можем воспроизвести эту ошибку и на других устройствах? Я испытал странное поведение некоторых устройств Android в зависимости от ПЗУ и / или производителя оборудования.

NewProggie
источник
Я смог воспроизвести это на Droid с CM7 с максимальной кучей, установленной на 16 МБ, что является тем же значением, которое я тестирую на эмуляторе.
Артем Русаковский
Вы можете что-то понять. Когда начнется второе действие, будет ли первое делать onPause-> onStop или просто onPause? Потому что я печатаю все вызовы жизненного цикла ******* и вижу onPause -> onCreate без onStop. И один из аварийных дампов на самом деле сказал что-то вроде onPause = true или onStop = false для примерно трех действий, которые он убивал.
Артем Русаковский
OnStop должен вызываться, когда действие покидает экран, но может не быть, если оно будет восстановлено системой раньше.
dten
Он не восстанавливается, потому что я не вижу, чтобы onCreate и onRestoreInstanceState вызывались, если я щелкнул обратно.
Артем Русаковский
однако в зависимости от жизненного цикла они могут никогда не вызываться, проверьте мой ответ, в котором есть ссылка на официальный блог разработчика, это более чем вероятно то, как вы передаете свои растровые изображения
dten
2

Я думаю, что проблема может быть связана с сочетанием многих факторов, указанных здесь в ответах, и это то, что вызывает у вас проблемы. Как сказал @Tim, (статическая) ссылка на действие или элемент в этом действии может заставить GC пропустить действие. Вот статья, в которой обсуждается этот аспект. Я думаю, что вероятная проблема возникает из-за того, что что-то держит Activity в состоянии «Visible Process» или выше, что в значительной степени гарантирует, что Activity и связанные с ним ресурсы никогда не будут восстановлены.

Некоторое время назад я столкнулся с противоположной проблемой с помощью службы, поэтому я подумал: есть что-то, что держит вашу активность на высоком уровне в списке приоритетов процесса, так что она не будет подчиняться системному GC, например ссылка (@Tim) или цикл (@Alvaro). Цикл не обязательно должен быть бесконечным или длительным элементом, просто чем-то, что работает во многом как рекурсивный метод или каскадный цикл (или что-то в этом роде).

РЕДАКТИРОВАТЬ: насколько я понимаю, onPause и onStop автоматически вызываются Android по мере необходимости. Методы предназначены в основном для вас, чтобы вы могли позаботиться о том, что вам нужно, прежде чем процесс хостинга будет остановлен (сохранение переменных, сохранение состояния вручную и т. Д.); но обратите внимание, что четко указано, что onStop (вместе с onDestroy) не может быть вызван в каждом случае. Кроме того, если в процессе хостинга также размещается Activity, Service и т. Д. Со статусом «Forground» или «Visible», ОС может даже не смотреть на остановку процесса / потока. Например: действие и служба запускаются в одном процессе, и служба возвращается START_STICKYизonStartCommand()процесс автоматически принимает хотя бы видимый статус. Это может быть ключевым моментом, попробуйте объявить новую процедуру для Activity и посмотрите, изменится ли это что-нибудь. Попробуйте добавить эту строку к объявлению вашей Activity в манифесте как: android:process=":proc2"а затем снова запустите тесты, если ваша Activity разделяет процесс с чем-либо еще. Мысль здесь в том, что если вы очистили свою Activity и почти уверены, что проблема не в вашей Activity, то проблема в другом, и пора заняться этим.

Кроме того, я не могу вспомнить, где я это видел (если я даже видел это в документации Android), но я помню, что что-то о PendingIntentссылке на Activity может привести к тому, что Activity ведет себя таким образом.

Вот ссылка на onStartCommand()страницу с некоторыми взглядами на процесс, не связанный с убийством.

Kingsolmn
источник
Основываясь на предоставленной вами ссылке (спасибо), действие можно убить, если его onStop был вызван, но в моем случае я думаю, что что-то мешает запуску onStop. Я обязательно выясню почему. Тем не менее, в нем также говорится, что действия только с onPause могут быть также уничтожены, чего не происходит в моем случае (я вижу, что вызывается onPause, но не onStop).
Артем Русаковский
1

поэтому единственное, о чем я действительно могу думать, - это иметь ли у вас статическую переменную, которая прямо или косвенно ссылается на контекст. Даже что-то вроде ссылки на часть приложения. Я уверен, что вы уже пробовали это, но я предлагаю это на всякий случай, попробуйте просто обнулить ВСЕ ваши статические переменные в onDestroy (), чтобы убедиться, что сборщик мусора получает это

Тим Фентон - VenumX
источник
1

Самый большой источник утечки памяти, который я обнаружил, был вызван некоторой глобальной, высокоуровневой или давней ссылкой на контекст. Если вы где-нибудь храните «контекст» в переменной, вы можете столкнуться с непредсказуемыми утечками памяти.

D2TheC
источник
Да, я слышал и читал это много раз, но у меня были проблемы с определением. Если бы я только мог надежно выяснить, как их отследить.
Артем Русаковский
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 я отмечаю тип класса invoker «ProfileActivity» и идентификатор пользователя, если он собирается запустить Activity, я бы проверил, является ли это «ProfileActivity» для пользователя или нет. . Если это так, просто вызовите finish () и вернитесь к прежнему ProfileActivity вместо того, чтобы создавать новый. Другое дело - утечка памяти.

qiuping345
источник