androidx.startup in 2026: ordering library initialization without a stack of ContentProviders
A practical guide to Android's App Startup library — consolidating initializers into one ContentProvider, declaring dependencies, and the lazy-init cases it can't replace.
Every library that needs to run code before your first Activity does it the same way: a ContentProvider with no query logic, whose only job is to fire during app startup. WorkManager does it. Firebase does it. Any analytics or crash-reporting SDK you add does it too. Each one is invisible in your own code, and each one is doing full component initialization — binder registration, a new ContentResolver call — before your app has drawn a single pixel. androidx.startup exists to collapse all of that into one ContentProvider, with an explicit dependency graph instead of an accidental one.
I ran into this while trimming cold-start time on Mintly, where WorkManager, a small logging shim, and my own preload step were each registering their own provider. Here’s what App Startup actually buys you, and where manual lazy init still beats it.
The problem it’s solving
ContentProvider.onCreate() runs on the main thread, before Application.onCreate() returns, for every provider declared in the manifest — in an order Android does not guarantee. That’s fine when there’s one. It stops being fine once three or four libraries each ship their own, because:
- Each provider is a full component the system has to instantiate and register, with its own fixed overhead.
- You have no control over ordering. If your logging shim needs WorkManager’s
Configurationto already exist, you’re relying on manifest-merge order, which is not a contract. - You can’t easily disable one for a build variant — a debug build that doesn’t need crash reporting still pays for that provider’s
onCreate().
Multiply the fixed cost of provider instantiation by every SDK in your build.gradle, and it adds up on exactly the metric — cold start — you’re usually trying to protect.
What App Startup replaces it with
androidx.startup defines one InitializationProvider, and your library or app registers Initializer<T> implementations with it instead of shipping a provider of their own. WorkManager itself moved to this model years ago — its WorkManagerInitializer runs through the same InitializationProvider your own initializers do, not a separate one.
An initializer for a small logging setup looks like this:
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()
}
Declared once in the manifest, under the single InitializationProvider merge point:
<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>
Every library using App Startup merges its <meta-data> entries into this same provider block via manifest merging. You end up with one ContentProvider instance doing the work that used to take one per library.
Declaring real dependencies
The part manifest-order luck used to paper over is dependencies(). If a session-tracking initializer needs WorkManager configured first, say so explicitly:
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 builds a dependency graph from these declarations and topologically sorts it, so WorkManagerInitializer.create() always finishes before SessionTrackingInitializer.create() runs — regardless of merge order, regardless of which Gradle module declared which dependency first. That guarantee is the actual feature; consolidating providers is just the mechanism that makes it possible.
Turning one off without deleting the library
The other common need is disabling a specific initializer for a build variant — a debug build that shouldn’t start a production analytics pipeline, for instance. You don’t fork the dependency; you remove its <meta-data> entry in that variant’s manifest:
<!-- 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>
The library’s code stays untouched; only the trigger that runs it at startup is gone for that variant.
Where it doesn’t help
App Startup only removes the overhead of having a provider. It does nothing about the overhead of what the initializer’s create() method actually does — and create() still runs synchronously on the main thread by default, same as the old ContentProvider.onCreate() did. If an SDK’s initialization genuinely doesn’t need to finish before your first frame, App Startup isn’t the fix; deferring that work to a background coroutine launched from Application.onCreate(), or lazily on first use, is. I moved Mintly’s session-tracking initializer to a WorkManager one-time request that fires after the first frame is drawn, rather than an Initializer at all — it needed to happen soon, not before anything else could.
The rule I ended up with: use androidx.startup for anything a library must configure before other code can safely touch it — WorkManager’s own case, or a dependency injection container other initializers assume exists. Use deferred, lazy, or background init for anything that’s merely convenient to start early. Consolidating providers and ordering the ones that must run early is real value; treating App Startup as a place to put every SDK’s boilerplate just moves the same main-thread cost into a different single provider instead of several.
// Related reading
More from the journal
CameraX in 2026: binding Preview and ImageAnalysis without leaking a camera session
A practical guide to CameraX on Android: binding Preview and ImageAnalysis to the lifecycle, the right backpressure strategy, and the crash rotation causes.
The Android In-App Update API in 2026: flexible vs. immediate, and when to force one
A practical guide to Google's In-App Update API — flexible vs. immediate flows, update priority, staleness days, and the resume check everyone forgets to write.
The Android Photo Picker API in 2026: letting users attach a photo without granting gallery access
A practical guide to Android's Photo Picker API — single and multi-select, MIME filtering, the pre-Android-13 backport, and why it beats READ_MEDIA_IMAGES for privacy and approval.