У Android приложения нет проблем с памятью - все перепробовал и все еще в растерянности - PullRequest
86 голосов
/ 24 сентября 2011

Я потратил 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 , но в этом действии значков есть в любом случае это всего лишь один адаптер, но я хотел бы упомянуть этот факт в любом случае).

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

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

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

Спасибо.

Ответы [ 10 ]

104 голосов
/ 28 сентября 2011

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

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

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

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

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

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

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

21 голосов
/ 15 января 2013

Несколько советов:

  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. Используйте Memory Analyzer, чтобы выяснить причину утечки памяти.
    Вот очень хорошее видео с Google I / O 2011: управление памятью для приложений Android
    Если вы работаете с растровыми изображениями, вам необходимо прочитать следующее: Эффективное отображение растровых изображений

4 голосов
/ 25 сентября 2011

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

2 голосов
/ 24 сентября 2011

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

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

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

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

Здесь - ссылка на страницу onStartCommand() с некоторыми подробностями о фронте неубивания процесса.

2 голосов
/ 24 сентября 2011

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

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

1 голос
/ 01 ноября 2011

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

Ответ Дайан Хэкборн и наше последующее обсуждение (также спасибо, CommonsWare) помогли прояснить некоторые вещи, которые меня смутили, поэтому спасибо вам за это.

1 голос
/ 28 сентября 2011

Попробуйте передать getApplicationContext () всему, что нуждается в Context. Возможно, у вас есть глобальная переменная, которая содержит ссылку на ваши действия и не позволяет собирать мусор.

1 голос
/ 24 сентября 2011

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

1 голос
/ 24 сентября 2011

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

0 голосов
/ 29 октября 2013

Я столкнулся с той же проблемой с вами. Я работал над приложением для обмена мгновенными сообщениями, для того же контакта можно запустить ProfileActivity в ChatActivity и наоборот. Я просто добавляю строку дополнительно в намерение начать другое действие, оно берет информацию о типе класса начального действия и идентификатор пользователя. Например, ProfileActivity запускает ChatActivity, затем в ChatActivity.onCreate я отмечаю тип класса invoker «ProfileActivity» и идентификатор пользователя; если он собирается запустить Activity, я бы проверял, является ли это «ProfileActivity» для пользователя или нет , Если это так, просто вызовите метод finish () и вернитесь к прежнему профилю деятельности вместо создания нового. Утечка памяти - это другое дело.

...