Grab — ведущий суперапп Юго-Восточной Азии, который предлагает доставку, транспортные и финансовые услуги миллионам пользователей в восьми странах через одно приложение. За 10 лет кодовая база выросла до более чем 10 млн строк кода и сотен сторонних библиотек, а сборка использовала гибридную связку Bazel и Gradle. В итоге команда столкнулась с тремя растущими проблемами: высоким уровнем ошибок Application Not Responding (ANR), постоянно увеличивающимся размером приложения и постепенно ухудшающимся временем запуска. Включив оптимизацию R8, доработав keep rules и внедрив Baseline Profiles и Startup Profiles, Grab снизил воспринимаемый пользователями уровень ANR на 32%, уменьшил размер загрузки приложения примерно на 30% и ускорил запуск на 35%.
Устранение всплесков ANR после выпуска обновления для более комфортной работы пользователей
До оптимизации процесса сборки Grab сталкивался с задержками холодного старта и всплесками ANR сразу после выхода новых обновлений. Основной причиной были накладные расходы на чтение большого количества DEX-файлов во время инициализации приложения.
Хотя Cloud Profiles Google Play оптимизировали стандартные сценарии для основной массы пользователей, они не покрывали все пути инициализации, необходимые для критически важных сценариев Grab — например, инициализацию графов зависимостей Dagger при запуске приложения. Для участков кода, не попавших в первоначальную оптимизацию, устройства полагались на JIT-компиляцию и локальную фоновую оптимизацию в течение последующих дней. Внедрение собственного Baseline Profile решило эту проблему: критически важные пользовательские сценарии стали напрямую передаваться в Cloud Profiles Google Play, благодаря чему нужные участки кода компилировались в машинный код сразу при установке.
Мы регулярно наблюдали всплески ANR после каждого еженедельного релиза приложения, — объясняет Нгуен Ван Минь, ведущий инженер команды Passenger App Core в Grab.
Чтобы устранить ANR и ускорить запуск, Grab внедрил оптимизации R8, Baseline Profiles и Startup Profiles. Профиль-ориентированная оптимизация вместе с R8 дала немедленное ускорение холодного старта на 12%, а Baseline Profiles добавили ещё 15% — в сумме запуск стал быстрее на 27%, а всплески ANR после релизов практически исчезли.
Grab провёл оптимизацию приложения в три стратегических этапа: сначала включил R8 в режиме совместимости, затем перешёл на R8 Full Mode, а после этого начал систематически уточнять keep rules с помощью Configuration Analyzer и внедрять контроль через CI/CD.
Этап 1: Включение оптимизации R8 (2025 год)
На первом этапе команда отказалась от устаревшего стандартного файла Proguard и перешла на proguard-android-optimize.txt, который включает оптимизации R8. Поскольку кодовой базе было больше десяти лет и она содержала свыше 10 млн строк, включение оптимизатора потребовало разбираться с устаревшими конфигурациями и активным использованием reflection.
Чтобы получить максимум от этого этапа, Grab выпустил обновление с R8 Compat mode одновременно с Baseline Profiles и Startup Profiles. Совместный эффект дал немедленное снижение воспринимаемых пользователями ANR на 25%, уменьшение размера загрузки приложения на 16% и ускорение холодного старта на 27%.
Этап 2: Полнофункциональный режим R8 и миграция (2026 год)
Через девять месяцев после первого этапа приложение перевели на R8 Full Mode, что открыло дополнительные возможности оптимизации. Стандартные библиотеки вроде Retrofit, Firebase и Jetpack Compose работали с R8 без проблем благодаря встроенным правилам, а основная сложность пришлась на код, активно использующий рефлекцию.
Для перехода на R8 Full Mode команда взяла consumer keep rules из свежей версии Gson, чтобы сохранить корректный JSON parsing. Отдельно пришлось разобраться с динамическими прокси в Java, которые через рефлекцию генерируют реализации интерфейсов во время выполнения. Поскольку R8 не может увидеть такую зависимость на этапе компиляции, он может удалить или переименовать нужные классы, из-за чего приложение начнёт падать. Вместо широких исключений, которые раздули бы размер приложения, команда изолировала конкретные классы, зависящие от reflection, и добавила очень точечные keep rules. Так удалось сохранить стабильность приложения и не пожертвовать эффективностью оптимизации.
В итоге R8 Full Mode принёс дополнительные серьёзные улучшения: размер загрузки приложения уменьшился ещё на 14%, запуск ускорился ещё на 10%, а воспринимаемый пользователями уровень ANR снизился ещё на 10%. После миграции Grab получил следующие показатели R8 Optimization:
- Optimization Score: 72%
- Shrinking Score: 73%
- Obfuscation Score: 79,6%
Эти метрики показали, насколько эффективно текущая конфигурация R8 позволяет оптимизировать, уменьшать и обфусцировать приложение, а также где keep rules всё ещё мешают.
Этап 3: Уточнение правил
На финальном этапе команда сосредоточилась на долгосрочном здоровье кодовой базы, постоянном мониторинге производительности и строгом контроле компиляции. Для систематической оценки качества правил и предотвращения регрессий Grab начал интегрировать R8 Configuration Analyzer в свой процесс проверки.
Благодаря переходу на R8 Full Mode и последовательному удалению устаревших keep rules команда дополнительно улучшила все три ключевых показателя:
- Optimization Score: с 72% до 78,2%
- Shrinking Score: с 73% до 79,1%
- Obfuscation Score: с 79,6% до 81,7%
Кроме роста этих диагностических показателей, дополнительная очистка keep rules дала ещё примерно 3,6% снижения размера загрузки приложения. Важнее то, что теперь команда получила объективные метрики, на основе которых можно строить автоматические pre-merge проверки в CI, чтобы новый код соответствовал строгим требованиям к keep rules, а достигнутые улучшения производительности не терялись со временем.
Результаты
Основные улучшения производительности после внедрения оптимизатора R8:
| Этап оптимизации | ANR | Размер | Холодный запуск |
|---|---|---|---|
| Этап 1: Включение оптимизации R8 | сокращение примерно на 25% | сокращение на 16% | улучшение примерно на 27% |
| Этап 2: Полнофункциональный режим R8 и миграция | сокращение на 10% | сокращение на 14% | улучшение на 10% |
| Этап 3: Оптимизация keep rules | — | сокращение на 3,6% | — |
| Общий результат | сокращение примерно на 32,5% | сокращение примерно на 30,4% | улучшение примерно на 35% |
Технические вызовы
Основные технические трудности при внедрении R8 в десятилетнюю кодовую базу Grab были следующими.
- Тестирование. За десять лет накопилось огромное количество рефлексий, что создавало много пограничных случаев при полной оптимизации. Протестировать все миллионы строк кода было невозможно, поэтому команда двигалась постепенно: в первую очередь тестировала именно тот код, который затрагивали сужаемые keep rules, и итеративно доводила правила до уровня конкретных классов, методов и полей, действительно необходимых для reflection.
- Задержки сборки и гибридная архитектура. Из-за того, что Grab использует Bazel для debug-сборок и Gradle для release-сборок с R8, эксперименты с настройками R8 могли занимать до двух часов в удалённом CI. Чтобы сократить цикл обратной связи, команда приблизила ежедневные QA-сборки к поведению production-сборок с R8 и автоматизировала сбор ключевых артефактов CI, включая
mapping.txtиconfiguration.txt. - Медленные циклы отладки. Поиск причин проблем среди сотен оптимизированных классов создавал серьёзную нагрузку на небольшую команду. Решением стала автоматическая отправка mapping-файлов в Firebase Crashlytics через Gradle и добавление deobfuscation mappings для Play Console, что позволило быстро и точно восстанавливать символы в runtime-проблемах.
Долгосрочное влияние на код при использовании R8
Помимо немедленного прироста производительности, включение R8 дало Grab и долгосрочное структурное преимущество. Команда использовала open-source инструмент app-sizer, чтобы измерить влияние R8, и увидела, что оптимизатор постоянно снижает стоимость каждой строки кода с точки зрения размера загрузки приложения.
В эпоху разработки с ИИ, когда объём кода растёт особенно быстро, это становится ещё важнее. Без R8 рост кодовой базы почти пропорционально увеличивает размер приложения. R8 позволяет при том же темпе роста кода сохранять значительно меньший итоговый объём.
Примечание по методологии: вклад Kotlin/Java в размер загрузки приложения, рассчитанный через app-sizer для репрезентативного устройства, делился на число production Kotlin LOC, посчитанных через cloc; тестовые, mock- и sample-модули исключались.
Обеспечивайте управление правилами и механизмы контроля
Чтобы keep rules со временем не деградировали и оптимизации R8 не терялись, Grab планирует встроить автоматические pre-merge проверки прямо в CI. По этой политике новые keep rules будут строго ограничиваться, а любые дополнительные правила должны иметь явное обоснование и проходить ревью. Команда уже отслеживает R8 optimization scores в CI и планирует использовать эти показатели как постоянный цикл обратной связи, чтобы ловить регрессии до мерджа.
CI-панель, отслеживающая показатели shrinking, optimization и obfuscation R8 во времени, чтобы заранее замечать и предотвращать регрессии в сборках Grab.
Столкнувшись с проблемами огромной и зрелой кодовой базы, команда Pax App Core в Grab системно взялась за растущее время запуска и всплески проблем после релизов с помощью Baseline Profiles и постоянной доработки конфигурации R8. В результате воспринимаемые пользователями ANR сократились примерно на 32,5%, размер загрузки приложения — примерно на 30,3%, а холодный старт ускорился примерно на 35%. При этом команда фундаментально улучшила способность кодовой базы масштабироваться без пропорционального роста размера и деградации производительности. P
Эти оптимизации сделали суперапп быстрее и стабильнее для миллионов пользователей в Юго-Восточной Азии, а инженерам Grab позволили быстрее добавлять новые функции без прежнего штрафа в виде стремительного роста размера приложения.
Чтобы начать работу с R8 и узнать больше, перейдите на d.android.com/r8.

