Country not specified
Unknown website Share

Apps4all

Страна: -
Город: -
Был онлайн: -
О себе:
 
08-07-2016, 10:59
Apps4all

Советы по оптимизации использования памяти приложениями Android

Выделение и освобождение памяти в Android всегда достигались высокой ценой. Китайская пословица гласит: «После бедности легко привыкнуть к роскоши, но после роскоши трудно привыкнуть к бедности». Эта фраза вполне применима и к использованию памяти приложениями.

Представим себе наихудший возможный сценарий: идет компиляция приложения с миллионами строк кода, и внезапно происходит сбой из-за нехватки памяти (ООМ). Вы начинаете отлаживать приложение и анализировать файл hprof. Если повезет, то вы сможете найти причину сбоя и устранить проблему нехватки памяти. Но иногда везение обходит вас стороной, вы обнаруживаете, что множество мелких переменных и временных объектов выделяет память таким образом, что не существует простого способа исправить эту проблему. Это означает, что придется перестраивать код, что всегда связано с потенциальным риском, и лишь ради того, чтобы сэкономить несколько килобайт, а иногда и несколько байт памяти.

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

Управление памятью в Android

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

В виртуальной машине Dalvik размер кучи для процессов приложений ограничен. Приложения начинают работу с 2МБ, а максимальный размер выделения, называемый largeHeap, составляет до 36МБ (в зависимости от конфигурации конкретного устройства). Примеры приложения с большой кучей: редакторы фото и видео, камера, галерея, домашний экран.

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

Как улучшить использование памяти

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

1. Будьте осторожны при использовании «абстракций». Впрочем, с точки зрения архитектуры абстракция может помочь в достижении более высокой гибкости. В мобильных решениях у абстракции может быть побочный эффект, который заключается в выполнении добавочного кода, из-за чего расходуется больше ресурсов ЦП и памяти. Использовать абстракцию следует лишь в том случае, если она дает вашему приложению ощутимое преимущество.

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

3. Попробуйте использовать оптимизированные контейнеры SparseArray, SparseBooleanArray и LongSparseArray вместо HashMap. HashMap выделяет объект вхождения при каждом сопоставлении. Это очень неэффективно с точки зрения памяти и с точки зрения производительности, поскольку сопровождается множеством операций автоматической упаковки и распаковки. Контейнеры наподобие SparseArray сопоставляют ключи с простыми массивами. Но помните, что такие оптимизированные контейнеры непригодны для большого количества элементов; при выполнении действий добавления, удаления и поиска они медленнее, чем Hashmap, если набор данных содержит несколько тысяч записей.

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

5. Проверяйте доступность кучи вашего приложения. Вызывайте ActivityManager::getMemoryClass(), чтобы запросить, какой объем кучи (в МБ) доступен для приложения. При попытке выделить больше памяти, чем доступно, возникнет исключение OutofMemory. Если приложение объявляет largeHeap в AndroidManifest.xml, то можно вызвать ActivityManager::getLargeMemoryClass() для запроса предполагаемого размера большой кучи.

6. Скоординируйте работу с системой с помощью обратного вызова onTrimMemory(). Используйте ComponentCallbacks2::onTrimMemory(int) в Activity/Service/ContentProvider для постепенного высвобождения памяти согласно последним ограничениям системы. Благодаря onTrimMemory(int) повышается общая скорость отклика системы, но процессы дольше поддерживаются в активном состоянии.

Когда возникает событие TRIM_MEMORY_UI_HIDDEN, это означает, что весь пользовательский интерфейс вашего приложения был скрыт и нужно высвободить ресурсы пользовательского интерфейса. Когда приложение работает в режиме переднего плана, могут появиться сообщения TRIM_MEMORY_RUNNING[MODERATE/LOW/CRITICAL], а в фоновом режиме — TRIM_MEMORY_[BACKGROUND/MODERATE/COMPLETE]. Можно освободить память от второстепенных ресурсов на основе стратегии высвобождения памяти при ее нехватке.

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

Если же вы создаете приложение, которое должно быть запущено в течение длительного периода времени, например службу музыкального проигрывателя, его следует разделить на два процесса: один для пользовательского интерфейса, а другой для фоновой службы. Для этого нужно задать свойство службы android:process в файле AndroidManifest.xml. Ресурсы в процессе пользовательского интерфейса можно высвобождать после скрытия приложения, а фоновая служба воспроизведения продолжит работать. Помните, что процесс фоновой службы ни в коем случае не должен взаимодействовать с пользовательским интерфейсом. В противном случае объем потребляемой памяти может вырасти вдвое или втрое!

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

9. Используйте растровые рисунки правильного разрешения. Загружайте растровые рисунки нужного разрешения или уменьшайте разрешение, если у исходных растровых рисунков оно слишком большое.

10. Используйте Proguard* и zipalign. Средство Proguard удаляет неиспользуемый код и маскирует классы, методы и поля. При этом код становится более компактным, снижается количество требуемых распределяемых страниц оперативной памяти. Средство zipalign перестраивает APK-файл. Если средство zipalign не запущено, потребуется больше памяти, поскольку из APK невозможно распределять файлы ресурсов.

Как избежать утечек памяти

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

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

2. Не забудьте вызвать unregisterReceiver() после вызова registerReceiver().

3. Избегайте утечки контекста. Если объявить статическую переменную Drawable в Activity, а затем вызвать view.setBackground(drawable) в onCreate(), то после поворота экрана будет создан новый экземпляр Activity, а отменить выделение памяти прежнему экземпляру Activity будет невозможно, поскольку представление задано как обратный вызов и представление ссылается на Activity (Context). Утечка в экземпляре Activity означает утечку значительного объема памяти с высокой вероятностью возникновения ошибки «Недостаточно памяти».

Избежать такой утечки можно двумя способами.

  • Не храните долгосрочные ссылки на context-activity. Жизненный цикл ссылки на действие должен быть таким же, как у самого действия.
  • Попробуйте использовать context-application вместо context-activity.

4. Избегайте нестатических внутренних классов в Activity. В Java нестатические анонимные классы содержат неявную ссылку на их внешний класс. При небрежности сохранение этой ссылки может повлечь удержание действия в памяти, тогда как в противном случае его обработал бы сборщик мусора. Поэтому используйте статические внутренние классы и слабые ссылки на содержащиеся внутри действия.

5. Будьте осторожны при работе с потоками. Потоки в Java находятся вне сборки мусора. Другими словами, виртуальная машина Dalvik (DVM) хранит жесткие ссылки на все активные потоки в системе выполнения, поэтому все потоки, оставшиеся запущенными, не попадают под действие сборки мусора. Потоки в Java сохраняются в памяти до тех пор, пока либо они не будут закрыты явным образом, либо весь процесс не будет завершен системой Android. В среде приложений Android поддерживается множество классов, предназначенных для повышения удобства фоновой работы с потоками.

  • Используйте Loader вместо потока для выполнения краткосрочных асинхронных фоновых запросов вместе с жизненным циклом Activity.
  • Используйте Service и передавайте результаты в Activity с помощью BroadcastReceiver.
  • Используйте AsyncTask для краткосрочных операций.

Как проанализировать использование памяти

Для получения дополнительных сведений о статистике использования памяти в интерактивном и автономном режимах можно просмотреть основной журнал Android с помощью команды logcat в Android Debug Bridge (ADB), информацию дампа памяти для определенного имени пакета или с помощью других инструментов, таких как Dalvik Debug Monitor Server (DDMS) и Memory Analyzer Tool (MAT). Вот краткое описание анализа использования памяти вашим приложением.

1. Для получения дополнительных сведений о статистике использования памяти в интерактивном и автономном режимах можно просмотреть основной журнал Android с помощью команды logcat в Android Debug Bridge (ADB), информацию дампа памяти для определенного имени пакета или с помощью других инструментов, таких как Dalvik Debug Monitor Server (DDMS) и Memory Analyzer Tool (MAT). Вот краткое описание анализа использования памяти вашим приложением.

55929dd7b273f5.01564564.jpg

  • Причина сборщика мусора. Что запустило сборку мусора и какого типа эта сборка? Причины могут быть следующими.
    • §GC_CONCURRENT: одновременная сборка мусора, высвобождающая память по мере заполнения кучи.
    • §GC_FOR_ALLOC: сборка мусора, которая запускается из-за того, что приложение попыталось выделить память при уже полной куче, поэтому системе пришлось остановить приложение и вернуть память.
    • §GC_HPROF_DUMP_HEAP: сборка мусора, возникающая при создании файла HPROF для анализа кучи.
    • §GC_EXPLICIT: запущенная явным образом сборка мусора, например при вызове gc() (чего следует избегать; нужно полагаться на то, что сборщик мусора сам запустится в нужный момент).

  • Освобожденный объем: объем памяти, освобожденный после этой сборки мусора.
  • Статистика кучи: процент свободной памяти и (количество действующих объектов/общий размер кучи).
  • Статистика внешней памяти: внешняя выделенная память на уровне API 10 и ниже (объем выделенной памяти/предел, по достижении которого начнется сборка мусора).
  • Время приостановки: у более крупных куч время приостановки будет больше. При одновременной приостановке отображаются две приостановки: одна в начале сборки, а другая ближе к окончанию.

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

2. Используйте DDMS для просмотра обновлений кучи и отслеживания выделения памяти.

Удобно проверять выделение кучи в реальном времени для определенного процесса с помощью DDMS. Попробуйте поработать с приложением и следите за обновлением выделения кучи на вкладке Heap. Это поможет определить, какие действия используют слишком много памяти. На вкладке Allocation Tracker отображаются все недавние выделения памяти с указанием типа объектов, потока, класса, файла и строки. Дополнительные сведения об использовании DDMS для анализа кучи см. в разделе справочных материалов в конце этой статьи. На следующем снимке экрана показана программа DDMS с текущими процессами и статистикой кучи памяти для определенного процесса.

55929e2613dab9.68839436.jpg

3. Просмотр общего выделения памяти.

С помощью команды adb — adbshelldumpsysmeminfo <имя_пакета>— можно просмотреть всю текущую выделенную память приложения (в КБ).

55929e3c9ee3e4.22454770.jpg

Как правило, следует обращать внимание только на столбцы Pss Total и Private Dirty. В столбце Pss Total включено выделение всех объектов Zygote (с установкой весового коэффициента согласно их совместному использованию процессами в соответствии с определением PSS выше). В столбце Private Dirty — объем фактической оперативной памяти, предоставленной куче приложения, выделенным областям и всем страницам Zygote, измененным с момента ответвления процесса приложения из объекта Zygote.

Кроме того, в столбце ViewRootImpl отображается количество корневых представлений, действующих в вашем процессе. Каждое корневое представление связано с окном, поэтому это поможет выявить утечки памяти, связанные с диалоговыми или другими окнами. На вкладках AppContexts и Activities отображается количество объектов Context и Activity приложения, используемых в вашем процессе. Это может пригодиться для выявления утечек объектов Activity, к которым невозможно применить сборку мусора из-за наличия в них статических ссылок, что бывает довольно часто. С такими объектами зачастую связано множество других ресурсов, и так можно удобно отслеживать крупные утечки памяти.

4. Сохраните дамп кучи и проанализируйте его в программе Eclipse Memory Analyzer Tool (MAT).

Записать дамп кучи можно непосредственно с помощью DDMS, а для более точных результатов можно вызвать Debug::dumpHprofData() в коде приложения. Затем потребуется применить программу hprof-conv для получения преобразованного файла HPROF. На следующем снимке экрана показан результат анализа памяти в МАТ.

55929e5982dc04.43504594.jpg

Заключение

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

Справочные материалы

Об авторе

Бин Чжу (Bin Zhu) — инженер по разработке приложений в команде Intel® Atom™ Processor Mobile Enabling Team в подразделении Developer Relations Division отдела Software and Solutions Group (SSG). Он отвечает за поддержку приложений Android на процессорах Intel Atom, а также занимается технологиями мультимедиа на платформах Android x86.

Дополнительные сведения об оптимизации компиляторов см. в нашем уведомлении об оптимизации.

 
Intel
разработка
разработчикам
0 0 0

Чтобы оставлять комментарии вам необходимо зарегистрироваться