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

androidx.startup в 2026 году: порядок инициализации библиотек без стопки ContentProvider'ов

Практическое руководство по библиотеке App Startup для Android — объединение initializer'ов в один ContentProvider, объявление зависимостей и случаи ленивой инициализации, которые она не заменяет.

MFKAPPS 4 мин чтения

Каждая библиотека, которой нужно выполнить код до запуска вашей первой Activity, делает это одинаково: ContentProvider без какой-либо логики запросов, единственная задача которого — сработать во время старта приложения. Так делает WorkManager. Так делает Firebase. Так делает любой SDK аналитики или отчётов о падениях, который вы добавляете. Каждый из них невидим в вашем собственном коде, и каждый выполняет полную инициализацию компонента — регистрацию binder’а, новый вызов ContentResolver — прежде чем ваше приложение отрисует хотя бы один пиксель. androidx.startup существует, чтобы свести всё это к одному ContentProvider, с явным графом зависимостей вместо случайно сложившегося.

Я столкнулся с этим, сокращая время холодного старта в Mintly, где WorkManager, небольшая прослойка логирования и мой собственный шаг предзагрузки каждый регистрировали свой провайдер. Вот что App Startup реально даёт и где ручная ленивая инициализация всё ещё выигрывает.

Какую проблему это решает

ContentProvider.onCreate() выполняется в главном потоке до возврата из Application.onCreate() для каждого провайдера, объявленного в манифесте — в порядке, который Android не гарантирует. Это нормально, когда провайдер один. Это перестаёт быть нормальным, как только три-четыре библиотеки начинают поставлять каждая свой, потому что:

  • Каждый провайдер — это полноценный компонент, который система должна создать и зарегистрировать, со своей фиксированной стоимостью.
  • У вас нет контроля над порядком. Если вашей прослойке логирования нужно, чтобы Configuration WorkManager уже существовала, вы полагаетесь на порядок слияния манифестов, а это не контракт.
  • Вы не можете легко отключить один из них для определённого варианта сборки — debug-сборка, которой не нужны отчёты о падениях, всё равно платит за onCreate() этого провайдера.

Умножьте фиксированную стоимость создания провайдера на каждый SDK в вашем build.gradle, и она накапливается ровно в той метрике — холодном старте, — которую вы обычно и пытаетесь защитить.

Что App Startup ставит на его место

androidx.startup определяет единственный InitializationProvider, и ваша библиотека или приложение регистрируют в нём реализации Initializer<T> вместо того, чтобы поставлять собственный провайдер. Сам WorkManager перешёл на эту модель ещё много лет назад — его WorkManagerInitializer проходит через тот же InitializationProvider, что и ваши собственные инициализаторы, а не через отдельный провайдер.

Инициализатор для небольшой настройки логирования выглядит так:

class LoggingInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        Log.setLogLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.WARN)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

Объявляется один раз в манифесте, под единой точкой слияния InitializationProvider:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.mfkapps.mintly.LoggingInitializer"
        android:value="androidx.startup" />
</provider>

Каждая библиотека, использующая App Startup, сливает свои записи <meta-data> в этот же блок провайдера через слияние манифестов. В итоге у вас остаётся один экземпляр ContentProvider, выполняющий работу, которая раньше требовала по одному экземпляру на библиотеку.

Объявление реальных зависимостей

Часть, которую раньше маскировала удача с порядком манифеста, — это dependencies(). Если инициализатору отслеживания сессии нужно, чтобы WorkManager был настроен заранее, скажите об этом явно:

class SessionTrackingInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        WorkManager.getInstance(context).enqueue(sessionHeartbeatRequest())
    }

    override fun dependencies(): List<Class<out Initializer<*>>> =
        listOf(WorkManagerInitializer::class.java)
}

App Startup строит граф зависимостей из этих объявлений и сортирует его топологически, так что WorkManagerInitializer.create() всегда завершается до запуска SessionTrackingInitializer.create() — независимо от порядка слияния, независимо от того, какой модуль Gradle объявил какую зависимость первым. Именно эта гарантия и есть настоящая функция; объединение провайдеров — лишь механизм, который делает её возможной.

Отключение одного инициализатора без удаления библиотеки

Другая распространённая потребность — отключить конкретный инициализатор для определённого варианта сборки, например debug-сборку, которая не должна запускать продакшн-пайплайн аналитики. Вы не форкаете зависимость — вы удаляете её запись <meta-data> в манифесте этого варианта:

<!-- src/debug/AndroidManifest.xml -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="merge">
    <meta-data
        android:name="com.mfkapps.mintly.analytics.AnalyticsInitializer"
        tools:node="remove" />
</provider>

Код библиотеки остаётся нетронутым; исчезает только триггер, который запускает его при старте для этого варианта.

Где это не помогает

App Startup устраняет только стоимость наличия провайдера. Он никак не влияет на стоимость того, что метод create() инициализатора реально делает — а create() по умолчанию всё так же выполняется синхронно в главном потоке, как это делал старый ContentProvider.onCreate(). Если инициализация SDK на самом деле не обязана завершиться до первого кадра, App Startup — не решение; решение — отложить эту работу в фоновую корутину, запущенную из Application.onCreate(), либо выполнить её лениво при первом использовании. Я перенёс инициализатор отслеживания сессии в Mintly полностью из модели Initializer в одноразовый запрос WorkManager, срабатывающий после отрисовки первого кадра — это должно было произойти скоро, но не раньше всего остального.

Правило, к которому я пришёл: используйте androidx.startup для всего, что библиотека обязана настроить до того, как другой код сможет безопасно к ней обратиться — случай самого WorkManager или контейнера внедрения зависимостей, существование которого предполагают другие инициализаторы. Используйте отложенную, ленивую или фоновую инициализацию для всего, что просто удобно запустить пораньше. Объединение провайдеров и упорядочивание тех, что должны выполняться рано, — реальная ценность; отношение к App Startup как к месту для шаблонного кода каждого SDK лишь переносит ту же стоимость главного потока в другой единственный провайдер вместо нескольких.

// По теме

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

MFKAPPS 4 мин чтения

CameraX в 2026 году: как привязать Preview и ImageAnalysis, не утекая сессией камеры

Практическое руководство по CameraX на Android: привязка Preview и ImageAnalysis к жизненному циклу, правильная стратегия backpressure и краш, который вызывает поворот экрана.

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

API обновлений внутри приложения Android в 2026 году: гибкое или немедленное, и когда его форсировать

Практическое руководство по In-App Update API от Google: гибкий и немедленный сценарии, приоритет обновления, дни устаревания и проверка возобновления, о которой все забывают.

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

Android Photo Picker API в 2026 году: прикрепить фото без доступа ко всей галерее

Практическое руководство по Photo Picker API в Android — одиночный и множественный выбор, фильтрация по MIME, бэкпорт для версий до Android 13 и почему это лучше READ_MEDIA_IMAGES.

#android #engineering #privacy