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

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

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

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

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

Спасибо.

Ответ 1

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Некоторые советы:

  • Убедитесь, что вы не являетесь контекстом активности утечки.

  • Убедитесь, что вы не храните ссылки на растровые изображения. Очистите весь свой ImageView в Activity # onStop, примерно так:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  • Переработайте растровые изображения, если они вам больше не нужны.

  • Если вы используете кеш памяти, например memory-lru, убедитесь, что он не использует много памяти.

  • Не только изображения занимают много памяти, убедитесь, что вы не храните слишком много других данных в памяти. Это легко может произойти, если в вашем приложении есть бесконечные списки. Попробуйте кэшировать данные в DataBase.

  • В Android версии 4.2 есть ошибка fooobar.com/questions/58817/... с аппаратным ускорением, поэтому, если вы используете hardwareAccelerated=true в своем манифесте, это приведет к утечке Память. GLES20DisplayList - сохраняйте ссылки на холдинг, даже если вы сделали шаг (2), и никто другой не ссылается на это растровое изображение. Здесь вам нужно:

    a) отключить аппаратное ускорение для api 16/17;
    или
    б) отделить представление, которое поддерживает растровое изображение

  • Для Android 3+ вы можете использовать android:largeHeap="true" в своем AndroidManifest. Но это не решит ваши проблемы с памятью, просто отложите их.

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

  • Используйте Memory Analyzer, чтобы узнать причину утечки памяти.
    Вот очень хорошее видео из Google I/O 2011: Управление памятью для Android-приложений
    Если вы имеете дело с растровыми изображениями, это должно быть обязательно прочитано: Эффективное отображение растровых изображений

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

Ответ 10

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