Anr что это на андроиде
Все о функциях разработчика в вашем телефоне
Константин Иванов
Настройки, которые используются для отладки и для разработки приложений, спрятаны в вашем телефоне – спрятаны в прямом смысле слова. Многие из нас идут в соответствующий раздел меню, чтобы запустить отладку USB или переключиться к рабочему модулю ART, но кроме этого, здесь имеется целый список настроек. Большая часть никогда вам не понадобится, но разве не интересно узнать, что скрывается в недрах вашего устройства?
«Разблокируем» функции разработчика в телефоне
Как говорилось выше, эти функции изначально скрыты. Это имеет смысл, поскольку найти их просто, а большинству людей они попросту не нужны. Для того, чтобы добраться до них, идем в раздел «Об устройстве» и ищем там пункт «Номер сборки». После пяти быстрых тапов появляется диалоговое окно – теперь устройство считает вас разработчиком. Только попробуйте ничего не испортить, ладно? Ну, или делайте что хотите – тоже вариант. Так или иначе, возможность заставить ваш телефон перестать работать всегда имеется.
А теперь посмотрим на предложенные функции повнимательнее.
Настройки
Понятно, что большинству пользователей все эти настройки ни на что не сдались. Кроме того, лезть туда и нажимать на пункты меню ради самого процесса — не лучшая идея. Но всегда стоит знать, что вообще можно сделать, хотя бы и просто для того, чтобы не делать этого никогда.
Надеемся, что наш рассказ просветил вас немного по вопросу этих настроек и опций, записанных непонятными словами. Кстати, в зависимости от выбранного языка системы, производителя и версии ОС Android, набор пунктов может несколько отличаться разделами и их названиями.
Android-как я могу исследовать ANR?
есть ли способ узнать, где мое приложение бросило ANR (приложение не отвечает). Я взглянул на следы.txt-файл в /data, и я вижу трассировку для своего приложения. Это то, что я вижу в трассировке.
10 ответов:
ANR происходит, когда какая-то длительная операция происходит в «главном» потоке. Это поток цикла событий, и если он занят, Android не может обрабатывать дальнейшие события GUI в приложении и, таким образом, вызывает диалог ANR.
теперь, в трассировке вы опубликовали, основной поток, кажется, все в порядке, нет никаких проблем. Это простаивания в очереди, ждет еще одно сообщение сюда. В вашем случае ANR, скорее всего, была более длительной операцией, а не чем-то, что заблокировал поток навсегда, поэтому поток событий восстановился после завершения операции, и ваша трассировка прошла после ANR.
обнаружение, где происходят ANRs, легко, если это постоянный блок (например, тупик, приобретающий некоторые блокировки), но сложнее, если это просто временная задержка. Во-первых, просмотрите свой код и найдите vunerable пятна и длительные операции. Примеры могут включать использование сокетов, блокировок, спящих потоков и других операций блокировки из события нитка. Вы должны убедиться, что все это происходит в отдельных потоках. Если ничего не кажется проблемой, используйте DDMS и включите представление потока. Это показывает все потоки в вашем приложении, похожие на трассировку, которую вы имеете. Воспроизвести ANR, и обновить основной поток в то же время. Это должно показать вам точно, что происходит во время ANR
Вы можете включить StrictMode в API уровня 9 и выше.
StrictMode чаще всего используется для захвата случайного диска или сети доступ к основному потоку приложения, где выполняются операции пользовательского интерфейса полученные и анимации имеют место. Сохраняя основной поток вашего приложения отзывчивый, ты тоже запретить диалоги ANR от показа пользователям.
используя penaltyLog() вы можете посмотреть вывод АБР logcat в то время как вы используйте приложение, чтобы увидеть нарушения, как они происходят.
вам интересно, какая задача содержит поток пользовательского интерфейса. Файл трассировки дает вам подсказку, чтобы найти задачу. вам нужно исследовать состояние каждого потока
состояние потока
фокус на приостановлено, состояние монитора. Состояние монитора указывает, какой поток исследуется, и приостановленное состояние потока, вероятно, является основной причиной взаимоблокировки.
основные шаги исследования
трассировка не всегда содержит «ожидание блокировки». в в этом случае трудно найти главную причину.
Я изучал android в течение последних нескольких месяцев, поэтому я далек от эксперта, но я был очень разочарован документацией по ANRs.
большинство советов, похоже, направлены на то, чтобы избежать их или исправить их, слепо просматривая ваш код, что здорово, но я ничего не мог найти при анализе трассировки.
есть три вещи, которые вам действительно нужно искать с журналами ANR.
1) взаимоблокировки: когда поток находится в ожидании государство, вы можете посмотреть через детали, чтобы найти, кто это «heldby=». Большую часть времени он будет удерживаться сам по себе, но если он удерживается другой нитью, это, вероятно, будет знаком опасности. Иди посмотри на эту нить и посмотри, за что она держится. Вы можете найти петлю, которая является явным признаком, что что-то пошло не так. Это довольно редко, но это первый момент, потому что, когда это происходит, это кошмар
2) ожидание основного потока: Если ваш основной поток находится в состоянии ожидания, проверьте, удерживается ли он по другой нитке. Этого не должно произойти, потому что ваш поток пользовательского интерфейса не должен удерживаться фоновым потоком.
оба этих сценария означают, что вам нужно значительно переработать свой код.
3) тяжелые операции на основной поток: это наиболее распространенная причина ANRs, но иногда один из труднее найти и исправить. Посмотрите на основные детали потока. Прокрутите вниз трассировку стека и пока не увидите классы, которые вы узнаете (из вашего приложения). Посмотрите на методы в трассировке и выяснить, если вы делаете звонков внутри сети, БД и т. д. в таких местах.
наконец, и я прошу прощения за бесстыдно подключив свой собственный код, вы можете использовать анализатор журнала python, который я написал в https://github.com/HarshEvilGeek/Android-Log-Analyzer это будет проходить через ваши файлы журналов, открывать файлы ANR, находить взаимоблокировки, находить ожидающие основные потоки, находить неперехваченные исключения в журналах агентов и печатать все это на экране относительно легко читать. Читать ReadMe файл (который я собираюсь добавить), чтобы узнать, как его использовать. Это помогло мне тонну на прошлой неделе!
всякий раз, когда вы анализируете вопросы синхронизации, отладка часто не помогает, так как замораживание приложения в точке останова сделает проблему уйти.
лучше всего, чтобы вставить много протоколирования вызовов (Log.XXX ()) в различные потоки и обратные вызовы приложения и посмотреть, где задержка находится. Если вам нужен stacktrace, создайте новое исключение (просто создайте экземпляр) и запишите его.
что вызывает ANR?
как правило, система отображает ANR, если приложение не может ответить на ввод пользователя.
в любой ситуации, когда ваше приложение выполняет потенциально длительную операцию, вы не должны выполнять работу в потоке пользовательского интерфейса, а вместо этого создать рабочий поток и выполнять большую часть работы там. Это сохраняет поток пользовательского интерфейса (который управляет циклом событий пользовательского интерфейса) и предотвращает вывод системы о том, что ваш код заморожен.
Как избежать ANRs
приложений Android обычно полностью работают на один поток по умолчанию «поток пользовательского интерфейса» или «основной поток»). Это означает, что все, что ваше приложение делает в потоке пользовательского интерфейса, которое занимает много времени, может вызвать диалоговое окно ANR, потому что ваше приложение не дает себе возможности обрабатывать входные события или передачи намерений.
поэтому любой метод, который выполняется в потоке пользовательского интерфейса, должен выполнять как можно меньше работы этот поток. В частности, мероприятия должны делать как можно меньше для настройки в ключевых методах жизненного цикла, таких как onCreate() и onResume(). Потенциально длительные операции, такие как операции с сетью или базой данных, или вычислительно дорогостоящие вычисления, такие как изменение размера растровых изображений, должны выполняться в рабочем потоке (или в случае операций с базами данных с помощью асинхронного запроса).
код: рабочий поток с классом AsyncTask
Код: Выполнить Рабочий поток
чтобы выполнить этот рабочий поток, просто создайте экземпляр и вызовите execute ():
Приложение отвечает: как мы уменьшили количество ANRs в шесть раз. Часть 2, про исправление ошибок
В первой части статьи мы поговорили о том, что такое ANR (Application Not Responding), и рассмотрели несколько способов сбора информации об этих ошибках. А сегодня я расскажу о проблемах, которые мы обнаружили в нашем приложении, о том, как мы их исправляли и что из этого в итоге получилось.
Время запуска приложения
Первое, что можно сделать, чтобы уменьшить количество ANR-ошибок, — попробовать найти самые частые причины их возникновения. Начать можно с консоли Google Play — проанализировать самые большие группы ошибок. Наша консоль выглядела примерно так:
Почти каждая группа содержала заголовок “Broadcast of Intent < act=com.google.android.c2dm.intent.RECEIVE >…”, но на первый взгляд все они были разные, так как стек-трейс основного потока каждой группы указывал на разные места в приложении. Мы попытались найти что-то общее между этими группами и в итоге, воспользовавшись локальными отчётами, которые мы получили с помощью скрапера, выяснили, что около 60% ANR-ошибок возникало во время исполнения метода Application.onCreate.
Этот метод является частью критического пути запуска приложения, то есть любые задержки в его исполнении будут влиять на время холодного старта. Мы хотели понять, могут ли эти задержки как-то влиять на ANR.
Мы добавили искусственную задержку в Application.onCreate и поэкспериментировали с несколькими сценариями на последних версиях Android. Вот что мы выяснили:
когда пользователь вручную запускает приложение через лаунчер, то ANR-диалог не появляется и приложение стартует в нормальном режиме, даже если мы добавляем задержку на несколько минут;
когда процесс приложения запускается через BroadcastReceiver, то ошибка ANR возникает при задержке от 10 секунд. Это не очень жёсткое ограничение, но в любом случае оно гораздо строже, чем в предыдущем сценарии.
Важное примечание ко второму сценарию: по умолчанию, если приложение находится в фоновом режиме во время появления ANR-ошибки, Android не показывает ANR-диалог (его отображение можно включить с помощью опции “Enable background ANR dialogs” в настройках для разработчиков). Фактически это означает, что пользователи, скорее всего, ничего не заметят и ошибка не будет заметно влиять на пользовательский опыт.
Поэтому мы пришли к выводу, что основной причиной возникновения наших ANR-ошибок, вероятно, является длительное время выполнения метода Application.onCreate. И судя по информации из консоли Google Play, в большинстве случаев это происходило во время исполнения Firebase Cloud Messaging BroadcastReceiver: иногда мы выходили за 10-секундный лимит, что приводило к фоновым ANR-ошибкам.
Мы изучили нашу внутреннюю аналитику, в которой отслеживаем время, прошедшее с момента создания класса Application до завершения метода Application.onCreate (мы называем это холодным запуском). В нашем приложении эта часть процесса запуска самая большая, она включает в себя инициализацию всех ContentProvider’ов и, собственно, сам метод onCreate.
Также мы разделили данные на две части: фоновый холодный запуск и не фоновый. Фоновый запуск мы определяли простым путём: проверяли, был включён экран или нет. В Android, к сожалению, нет простых способов определить причину запуска процесса при исполнении метода Application.onCreate, но в любом случае, скорее всего, этот метод определения фонового запуска даёт более или менее правдивую картину. Вот что мы получили:
Длительность фонового холодного запуска приложения Длительность холодного запуска приложения
Можно заметить, что обычный запуск занимал в среднем 2,2 секунды, а фоновый — 5 секунд. Около 3% фоновых запусков длилось дольше 10 секунд. Это говорит о том, что вполне вероятно, в этих случаях могли возникать ANR-ошибки. Чтобы подтвердить или опровергнуть эту гипотезу, мы решили попытаться ускорить запуск приложения.
Ускорение запуска приложения
Чтобы найти потенциальные места для оптимизации на критическом пути запуска приложения, проще всего начать с анализа трассировки вызовов методов. Для этого в Android Studio есть неплохие встроенные инструменты.
В профилировщике Android Studio есть кнопки для запуска приложения с профилированием и для запуска/остановки профилирования во время работы приложения. Кроме того, есть специальные методы, вызвав которые можно начинать или прекращать профилирование из кода. Для получения консистентных результатов между запусками нам лучше подходит второй способ. Можно воспользоваться вот этими методами:
Начать трассировку можно в статическом инициализаторе класса Application (или в конструкторе), а завершить её либо в конце метода Application.onCreate, либо в onResume первого Activity. Например, класс Application может выглядеть как-то так:
После этого файл с трассировкой можно открыть в Android Studio:
https://habrastorage.org/webt/sv/ze/jv/svzejva_ajvhkc_nhukxcjkhfiq.png
При анализе результатов важно помнить о паре важных моментов.
Разница между sample-based- и method-based-трассировкой. При использовании второго подхода можно столкнуться с достаточно большими искажениями в тех случаях, когда есть большое количество вызовов довольно быстрых методов. Sample-based-трассировка работает точнее, но при её использовании могут теряться некоторые вызовы методов. В обоих случаях нет смысла использовать абсолютные значения времени, но sample-based-трассировка даёт более подходящие результаты для относительного анализа.
Разница между debug- и release-сборкой приложения. При профайлинге запуска приложения имеет смысл выполнять тестирование в окружении, которое как можно ближе к продакшен-версии. Для этого желательно отключить все отладочные инструменты (такие как Leak Canary) либо собрать релизную сборку приложения.
Правильный выбор жизненного цикла для компонентов
Одним из простейших способов уменьшения времени запуска приложения является ленивая инициализация всех компонентов. Довольно часто может возникать соблазн создавать различные компоненты и структуры данных в application scope, так как это удобно в том плане, что они будут доступны в течение всего жизненного цикла приложения. Но по возможности лучше этого избегать. Правильный выбор набора компонентов в application scope может не только ускорить запуск, но и уменьшить потребление памяти, потому что вы не будете хранить ссылки на «лишние» объекты в течение всего времени работы приложения.
Фоновая инициализация и инициализация с задержкой
Если при запуске приложения вам нужно инициализировать какие-либо структуры или объекты, подумайте, возможно ли это сделать немного позже и не на основном потоке. Если же код обязательно должен быть выполнен на основном потоке, то может помочь обычная задержка в несколько секунд (Handler.postDelayed). Распределение задач по времени также снижает вероятность блокирования главного потока.
Сторонние ContentProvider’ы
Некоторые сторонние библиотеки могут инициализировать код с использованием ContentProvider. Всегда важно смотреть, что у вас содержится в итоговом AndroidManifest.xml, поскольку по умолчанию инструменты сборки Android будут автоматически добавлять ContentProvider из всех сторонних библиотек, что может повлиять на время запуска приложения.
Если для каких-то библиотек вам не нужна автоматическая инициализация ContentProvider, вы можете отключить её, добавив в AndroidManifest.xml такую запись:
Это бывает полезно, если вы хотите сами контролировать процесс инициализации. Например, вы можете инициализировать библиотеку, вызвав тот же код, что и в ContentProvider, но только тогда, когда вам действительно нужно использовать предоставляемую библиотекой функциональность. Однако надо проявить осторожность и провести тщательное тестирование после внесения этих изменений, так как библиотека может ожидать инициализации в Application.onCreate.
Когда мы выпустили обновление с нашими оптимизациями, 95-й перцентиль холодного запуска приложения уменьшился примерно на 50% (с
А что насчёт количества ANR-ошибок? Вот что мы увидели в консоли Google Play:
Источник: сериал «Офис»
Итак, мы выяснили, что в нашем случае скорость запуска приложения напрямую влияет на количество ANR-ошибок. Но порог “Bad Behaviour” всё равно был превышен, так что мы продолжили искать способы решения этой проблемы.
SharedPreferences и метод apply()
При анализе стек-трейсов в Google Play и изучении внутренних отчётов мы заметили вот такие странные группы:
Это говорит о том, что есть много случаев, когда главный поток блокируется где-то в недрах Android, в месте, связанном с SharedPreferences.
По общему правилу не рекомендуется выполнять запись на диск в главном потоке, так как во время дисковых операций могут возникать непредсказуемые задержки, способные привести к зависанию интерфейса или ANR-ошибкам.
Мы проверили свою кодовую базу и не обнаружили использования метода commit, который выполняет запись на диск в блокирующем режиме. Все операции записи в SharedPreferences были сделаны с применением метода apply. Почему же тогда мы столкнулись с этой проблемой, если мы используем неблокирующий API?
Найти причину поможет внимательное изучение исходного кода Android. Стандартная реализация SharedPreferences находится в SharedPreferencesImpl.java. При внесении изменений в SharedPreferences записи о них сохраняются во временную HashMap, которая затем при вызове commit или apply применяется к кешу, хранящемуся в оперативной памяти. Во время обновления кеша также вычисляется, какие ключи были изменены, чтобы потом, когда нам нужно будет записать эти изменения на диск, мы не делали лишнюю работу. Информация об изменениях хранится в MemoryCommitResult.
Если посмотреть на тело метода apply(), то мы увидим, что он просто отправляет лямбду записи на диск в метод enqueueDiskWrite(), что выглядит довольно безобидно и намекает на то, что эта лямбда будет выполнена на фоновом потоке. Вот упрощённая реализация метода apply():
Здесь сначала создаётся объект Runnable — лямбда, которая может синхронно выполнить запись. Этот Runnable добавляется в QueuedWork. Если мы посмотрим JavaDoc для QueueWork класса, то увидим примерно следующее:
Internal utility class to keep track of process-global work that’s outstanding and hasn’t been finished yet.
This was created for writing SharedPreference edits out asynchronously so we’d have a mechanism to wait for the writes in Activity.onPause and similar places, but we may use this mechanism for other things in the future.
Этот класс хранит в себе список всех отложенных асинхронных операций с возможностью исполнить их синхронно в случае наступления таких событий, как Activity.onStop, Service.onStartCommand или Service.onDestroy.
Это означает, что простое использование стандартных компонентов Android может приводить к выполнению всех отложенных дисковых операций на главном потоке. И если любая из этих операций выполняется сразу после вызова метода apply(), то он фактически становится синхронным методом commit().
Мы подозреваем, что это было сделано для снижения риска потери данных, когда процесс приложения убивается по разным причинам. Но насколько вероятна такая ситуация? Обычно мы используем SharedPreferences, когда приложение находится на переднем плане и у него мало шансов быть остановленным системой. Более того, часто мы используем SharedPreferences для кеширования значений, которые можно легко восстановить с сервера, так что потеря таких данных не особо страшна.
Чтобы узнать, как SharedPreferences влияют на количество ANR-ошибок и полезна ли эта синхронная логика apply, мы решили провести A/B-тест, который меняет поведение этого метода. Для этого мы заменили все операции создания SharedPreferences на функцию-фабрику:
Теперь мы можем управлять реализацией SharedPreferences в нашем приложении и использовать любую другую альтернативную реализацию вместо системной. Мы создали простой класс-имплементацию SharedPreferences, который делегирует в настоящую реализацию всё, кроме метода apply(): вместо него мы вызываем метод commit() на фоновом потоке. Эта реализация очень похожа на то, что сделано в другой библиотеке-альтернативе SharedPreferences — Binary Preferences, только в нашем случае мы не меняем механизм сериализации-десериализации для упрощения обратной миграции в случае возникновения проблем.
В итоге код с новой реализацией в A/B-тесте выглядел примерно так:
Затем мы начали медленно раскатывать A/B-тест, следя за основными показателями. У нас достаточно хорошее покрытие продуктовыми и техническими метриками и есть инструменты, которые уведомляют разработчиков о значительных отклонениях в любой из них.
https://habrastorage.org/webt/pz/kx/hm/pzkxhmwesuucvusye6qlix_2ffa.png
В результате мы не обнаружили никаких проблем, а общее количество ANR-ошибок уменьшилось примерно на 4% по сравнению с контрольной группой A/B теста. Неплохо, но это всё ещё выше порогового значения.
Обработка push-уведомлений
Поскольку мы не достигли своей цели, нам требовалось найти способы дальнейшего снижения ANR rate.
До этого мы выяснили, что существует практически линейная корреляция между длительностью запуска приложения и ANR rate. Скорее всего, дело в том, что большинство ANR-ошибок в нашем приложении возникало при обработке BroadcastReceiver, на стадии Application.onCreate. К сожалению, у нас больше не осталось простых способов ускорения запуска — всё остальное требовало серьёзного рефакторинга и большого объёма работ.
В ходе анализа репортов мы также заметили, что большинство запусков нашего процесса выполнялось при обработке BroadcastReceiver для push-уведомлений. И у нас возникла идея: может быть, можно обрабатывать все уведомления в отдельном процессе, который не нуждается в инициализации компонентов, необходимых при обычном запуске приложения?
По умолчанию в Android ваше приложение выполняется внутри одного процесса. Когда вы кликаете по иконке, система создаёт для приложения процесс. В случае получения BroadcastReceiver-интента система запустит процесс, только если он ещё не запущен. Каждый раз при запуске процесса приложения Android вызывает метод Application.onCreate:
Но есть способы запустить некоторые части приложения в отдельных процессах. В таких случаях будут создаваться отдельные экземпляры класса Application для каждого процесса. Это может помочь нам в решении задачи, так как даёт возможность убрать практически всё содержимое метода Application.onCreate, перенеся в отдельный процесс только код, связанный с обработкой push-уведомлений. Это должно значительно ускорить обработку BroadcastReceiver и снизить вероятность возникновения ANR-ошибок:
Вы можете контролировать процесс, в котором будет запускаться компонент, с помощью AndroidManifest.xml. Например, для запуска BroadcastReceiver в нестандартном процессе нужно добавить название процесса в тег “receiver” с помощью атрибута “android:process”. Но как это сделать, если мы используем внешнюю библиотеку вроде Firebase Cloud Messaging?
Есть специальные теги, которые контролируют процесс объединения манифестов из библиотек. Можно пропатчить исходное объявление манифеста FCM BroadcastReceiver с помощью атрибута tools:node=”replace”. Помимо FCM BroadcastReceiver, за обработку push-уведомлений отвечает ещё и FirebaseMessagingService, и нам нужно запускать его в том же процессе. В итоге нужно добавить в манифест следующие записи:
Теперь, когда сервисы Google Play отправляют broadcast с новым сообщением, мы можем выключить обычную инициализацию в Application.onCreate, проверив, находимся ли мы в главном процессе:
Есть много способов имплементации этой проверки. Один из них можно посмотреть здесь.
Если вы не делаете ничего особенного с push-уведомлениями, то, вероятнее всего, этого будет достаточно и всё будет работать из коробки. Ваш код, отвечающий за отображение уведомлений, будет исполняться в отдельном процессе, а Activity из PendingIntent будут запускаться в основном.
После того как мы это реализовали, уведомления стали отображаться значительно быстрее:
Мы выпустили обновление с этими изменениями и начали постепенно его раскатывать, следя за основными метриками.
Примерно через неделю большинство наших пользователей обновили приложение — и вот что мы получили:
доля ANR-ошибок в Badoo уменьшилась с 0,8% до 0,41% и в конце концов стала ниже, чем у пиров*, достигнув отметки в 0,28%:
*в Google Play имеется возможность выбрать произвольный набор приложений-пиров и выполнять сравнение ваших метрик с медианой пиров.
абсолютное количество ANR-ошибок в день уменьшилось более чем вдвое.
Хотя в нашем случае эти изменения сильно повлияли на ANR rate, стоит иметь в виду несколько вещей, прежде чем реализовывать что-то похожее в своём приложении:
Разработчики FCM библиотек могли не предусматривать изменение её поведения с помощью патчинга манифеста и работу в отдельном процессе. Это не описано в официальной документации, так что есть риск, что в любой момент этот функционал может быть сломан, поэтому он требует тщательного тестирования после каждого обновления библиотек.
Обновлять FCM-библиотеки нужно крайне осторожно, так как при каждом обновлении необходимо проверять, соответствуют ли записи пропатченного XML-манифеста исходным.
Добавление дополнительного процесса требует больше ресурсов памяти и процессора
Учитывая всё это, я рекомендую использовать этот подход только в качестве крайней меры или временного решения. Если у вас есть возможность ускорить запуск приложения, то лучше сосредоточиться на этом, потому что это поможет не только снизить ANR rate, но и уменьшить длительность холодного запуска.
Общие результаты
В результате всех изменений мы уменьшили ANR rate и общее количество ANR-ошибок примерно в шесть раз:
Надеюсь, наш опыт поможет вам снизить ANR rate и повысить качество вашего приложения.
Кто сталкивался с интересными багами, связанными с ANR? Расскажите о своём опыте в комментариях.