После года стараний я наконец-то добился 0% ANR в Respawn. Привожу полный гайд о том, как я это сделал.

Начнем с 12 советов, которые нужно адресовать первым делом, а в следующем посте я расскажу про три скрытых источника ANR, в существование которых мои коллеги до сих пор не верят.

1. Добавьте логирование событий в Crashlytics

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

2. Полностью удалите SharedPreferences из вашего проекта

Особенно зашифрованные. Они являются причиной №1 ANR. Вместо этого используйте DataStore с Kotlin Serialization. Я расскажу, почему я так ненавижу префы, в отдельном посте позже.

3. Поэкспериментируйте с обработкой событий UI в фоновом потоке

Если вы имеете дело со сторонним SDK, вызывающим сбои, это не решит задержку, но замаскирует ANR, уведя долгую операцию с мейна раньше.

4. Избегайте использования библиотек GMS на мейн-потоке

Это доисторические библиотеки на джаве и колбеках, внутри которых нет понимания даже концепции потоков, не то что каких-то действий против ANR. Создайте абстракции на основе корутин и вызывайте с фоновых диспетчеров.

5. Проверьте использование Bitmap / Drawable

Растровые изображения при неправильном размещении (например, не используя drawable-nodpi) могут привести к загрузке слишком больших изображений и ANR.

Неочевидный момент: Это на самом деле краш с OOM, но каждая Out of Memory Error может проявляться не как падение, а как АНР!

6. Включите StrictMode и агрессивно устраняйте все операции ввода-вывода в основном потоке

Охереете, насколько их у вас много. Всегда держите StrictMode включенным.

Важно: включайте StrictMode в контент-провайдере с приоритетом Int.MAX_VALUE, а не в Application.onCreate(). В следующем посте раскрою библиотеки, которые выносят ANR в контент-провайдеры, чтобы вы не заметили.

7. Ищите утечки памяти

**Никогда не используйте конструкторы скоупов корутин (CoroutineScope(Job())). Добавляйте таймауты ко всем suspend-функциям с вводом-выводом. Добавляйте обработку ошибок. Используйте LeakCanary. Профилируйте использование памяти. Анализируйте аналитику из шага 1, чтобы найти действия пользователя, приводящие к ANR.

80% моих ANR были вызваны утечками памяти и происходили во время огромной GC-паузы. Если у вас в консоли появляются мистические ANR во время долгих сессий, крайне вероятно, что это просто GC-пауза из-за утечки.

8. Не верьте стектрейсам

Они вводят в заблуждение, всё время указывая на какой-то левый код. 90% ANR вызваны вами. Я достиг 0.01% ANR после того, как серьезно отнесся к их поиску и перестал винить queue.NativePollOnce во всех своих проблемах.

9. Избегайте загрузки файлов в память

Запретите использование File().readBytes() полностью. Всегда используйте потоковую передачу для JSON, бинарных данных и файлов, рядов базы данных, шифруйте данные через Output/InputStream, и стримьте ответы бэкенда.

10. Используйте Compose и избегайте тяжелых макетов

Некоторые устройства настолько плохи, что отрисовка UI вызывает ANR. Сделайте UI легким и загружайте его постепенно. Следите, чтобы не было циклов рекомпозиций - их тяжело заметить.

11. Вызывайте goAsync() в броадкаст-ресиверах

Ставьте таймаут (обязательно!) и выполняйте работу в корутине. Это позволит избежать ANR из-за того, что броадкаст-ресиверы часто выполняются системой под огромной нагрузкой (во время BOOT_COMPLETED сотни приложений гоняют броадкасты), и вам может прилететь ANR просто потому, что залагал телефон.

Не выполняйте никакую работу в броадкаст-ресиверах синхронно. Так у вас меньше шанс, что система обвинит вас в ANR.

12. Избегайте байндеров сервисов вообще (bindService())

Выгоднее будет отправлять события через application-класс. Байндеры к сервисам всегда будут вызывать ANR, хоть ты что сделай. Это нативный код, который на Xiaomi топовых за свои деньги будет вступать в contention за системные вызовы на их древнем чипсете, а прилетать будет вам.

Далее расскажу про неочевидные проблемы с чтением файлов, которые нам нужно будет решить, если мы хотим действительно 0% ANR. Подписывайтесь на соцсети или рассылку, чтобы не потерять вторую часть.