После года стараний я наконец-то добился 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. Подписывайтесь на соцсети или рассылку, чтобы не потерять вторую часть.