Перейти к содержимому
Все записи

Baseline Profiles в Android: что на самом деле влияет на время холодного запуска в 2026 году

Практическое руководство по Android Baseline Profiles — генерация через Macrobenchmark, подключение в Gradle, честное измерение выигрыша и ещё три вещи, которые сильнее влияют на холодный запуск.

MFKAPPS 4 мин чтения

Холодный запуск — это тот показатель производительности, который чувствует каждый пользователь и почти никто не измеряет. Вы нажимаете на иконку, на мгновение появляется пустой экран или растянутый splash-экран, а затем приложение открывается. Меньше 500 мс — никто не замечает. Больше секунды — воспринимается как “медленно”, даже если каждый следующий экран открывается мгновенно: первое впечатление запоминается, а иконка приложения — это и есть первое впечатление.

Бо́льшая часть этого показателя не имеет отношения ни к вашей верстке, ни к запросу к базе данных. Это JIT-компилятор выполняет холодную, интерпретируемую работу над самыми горячими участками кода приложения, не имея никакого профиля для оптимизации. Baseline Profiles существуют именно для того, чтобы пропустить этот разогрев, и это один из немногих инструментов производительности Android, который стоит один вечер работы, а окупается при каждой последующей установке.

Что такое Baseline Profile на самом деле

Baseline Profile — это текстовый файл baseline-prof.txt, перечисляющий классы и методы, к которым обращается приложение в своих самых распространённых сценариях: при холодном запуске и в любых других путях, которые вы решите профилировать. Вы поставляете его внутри APK/AAB, и Android Runtime (ART) использует его, чтобы скомпилировать эти методы заранее (ahead-of-time), а не интерпретировать или JIT-компилировать их при первом использовании.

Эффект узкий, но реальный: он не делает код быстрее, он убирает налог на интерпретацию именно того кода, который выполняется при запуске. По данным Google в Play, это обычно сокращает время холодного запуска на 20-30% у приложений, у которых профиля раньше не было — ваш реальный выигрыш полностью зависит от того, какая доля пути запуска действительно покрыта.

Генерация профиля с Macrobenchmark

Профиль должен получиться из реального, инструментированного запуска — написать его вручную с пользой не получится. Для этого используется библиотека androidx.benchmark.macro:

// build.gradle.kts (модуль baselineprofile)
plugins {
    id("androidx.baselineprofile")
}

dependencies {
    baselineProfile(project(":app"))
}
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() = rule.collect(
        packageName = "com.mfk.hydrame",
        includeInStartupProfile = true,
    ) {
        pressHome()
        startActivityAndWait()
        // Прогоняйте не только splash, а сценарии, которые стоит скомпилировать заранее.
        device.findObject(By.text("Log a glass")).click()
        device.waitForIdle()
    }
}

Запустите Gradle-задачу :app:generateBaselineProfile на реальном устройстве или достаточно быстром эмуляторе (для генерации профиля нужна сборка с инструментацией почти на уровне root, но плагин берёт это на себя). Она фиксирует каждый класс, задействованный во время этого сценарного прогона, и записывает baseline-prof.txt в app/src/main/, откуда плагин AGP baseline profile автоматически подхватывает его для release-сборок.

Часть, которую многие пропускают: сценарий должен охватывать больше, чем splash-экран. Если реальное узкое место — это первый рендер списка с данными из Room или первый вызов провайдера виджета, включите это взаимодействие в блок collect. Профиль, покрывающий только onCreate(), упускает бо́льшую часть реального выигрыша.

Честное измерение выигрыша

Не доверяйте секундомеру и своему пальцу. Используйте StartupTimingMetric в отдельном macrobenchmark-тесте, запустите его на сборке без профиля и на сборке с профилем, и сравните медианы на достаточном числе итераций, чтобы шум усреднился в обе стороны:

@Test
fun startupCompilationModes() {
    benchmarkRule.measureRepeated(
        packageName = "com.mfk.hydrame",
        metrics = listOf(StartupTimingMetric()),
        iterations = 10,
        startupMode = StartupMode.COLD,
        compilationMode = CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require),
    ) {
        pressHome()
        startActivityAndWait()
    }
}

Для базового сравнительного прогона замените compilationMode на CompilationMode.None(). Десять итераций — разумный минимум: тепловое состояние устройства и фоновые процессы добавляют достаточно шума, чтобы три прогона обманули вас в любую сторону.

Что Baseline Profiles не исправляют

Они не заменяют реальное сокращение работы при запуске. Если ваш Application.onCreate() инициализирует три SDK, заранее открывает базу данных и раздувает тяжёлый первый экран, профиль лишь ускорит интерпретацию этой работы — он не сделает так, чтобы работы стало меньше. Выигрыш складывается вместе с этим, а не вместо:

  • Отложенная инициализация некритичного. Всё, что не нужно для первого кадра — аналитические SDK, второстепенное планирование WorkManager, получение удалённой конфигурации — выполняется в фоновом диспетчере после того, как UI уже виден, а не в onCreate().
  • Режим R8 full mode в release-сборках. Уменьшение и оптимизация байткода означают, что ART в принципе придётся проходить меньше кода, независимо от наличия профиля.
  • Ленивые синглтоны вместо инициализируемых заранее. Экземпляр базы данных by lazy, инициализируемый при первом обращении к DAO, ничего не стоит при запуске, если первому экрану он пока не нужен.

Я обнаружил это, разрабатывая виджет главного экрана для Hydrame: собственный холодный запуск виджета (отдельный контекст процесса, без тёплого Application, на который можно опереться) оказался медленнее, чем у приложения, потому что ни один путь кода виджета не попал в профиль. Добавление в скрипт Macrobenchmark отдельного сценария для виджета — а не только для главной activity приложения — закрыло бо́льшую часть этого разрыва.

Когда этот вечер стоит потраченного времени

Если приложение уже быстрое, Baseline Profile — приятное, но не обязательное дополнение. Если холодный запуск — единственная метрика, горящая жёлтым в панели vitals Play Console, это обычно самый выгодный вечер работы, который можно потратить: без изменений архитектуры, без функциональных рисков — только сгенерированный файл и плагин Gradle. Генерируйте его раз на релиз из реальных критических путей, держите тест Macrobenchmark в CI, чтобы редизайн незаметно не вышел за рамки того, что покрывает профиль, и относитесь к показателю холодного запуска как к любой другой регрессии — как к тому, за чем следят, а не к тому, что чинят один раз и забывают.

// По теме

Ещё из журнала

MFKAPPS 4 мин чтения

Jetpack DataStore в 2026 году: переход с SharedPreferences без потери ни одной настройки

Практическое руководство по переходу с SharedPreferences на Jetpack DataStore на Android — асинхронные ловушки, путь миграции, сохраняющий существующие значения, и как это протестировать.

#android #engineering #kotlin
MFKAPPS 4 мин чтения

Разработка Mintly: как удержать точность таймера фокуса, когда Android хочет убить процесс

У работающего таймера Pomodoro проблема с надёжностью сложнее, чем у одноразового напоминания. Вот как Mintly переживает режим Doze, гибель процесса и дрейф при выключенном экране благодаря foreground-сервису и времени окончания по системным часам.

#android #engineering #mobile
MFKAPPS 5 мин чтения

Сканирование штрихкодов с ML Kit на Android в 2026 году: как Stocky добавляет товар в кладовую меньше чем за секунду

Практическое руководство 2026 года по сканированию штрихкодов на устройстве с ML Kit и CameraX: настройка форматов, офлайн-поиск товара и математика частичного расхода в приложении для кладовой.

#android #engineering #kotlin