本文へスキップ
すべての記事

2026年のandroidx.startup: ContentProviderの山を積まずにライブラリ初期化の順序を決める

AndroidのApp Startupライブラリの実践ガイド — initializerを1つのContentProviderに統合し、依存関係を宣言し、それでも置き換えられない遅延初期化のケースを解説する。

MFKAPPS 1 分で読めます

最初のActivityが動く前にコードを実行する必要があるライブラリは、どれも同じ方法を使う。クエリロジックを持たず、アプリ起動時に発火することだけを仕事とするContentProviderだ。WorkManagerはこれをやる。Firebaseもやる。追加するどんな解析SDKやクラッシュレポートSDKもやる。それぞれは自分のコードの中では見えないが、それぞれがアプリが1ピクセルも描画する前に、バインダー登録や新しいContentResolver呼び出しといったコンポーネントの完全な初期化を行っている。androidx.startupは、これらすべてを偶然できあがった依存グラフではなく明示的な依存グラフとともに、1つのContentProviderに集約するために存在する。

これに気づいたのは、Mintlyでコールドスタート時間を削っていたときだ。WorkManager、小さなロギング層、自分のプリロードステップがそれぞれ自分のproviderを登録していた。ここではApp Startupが実際に何をもたらすのか、そして手動の遅延初期化がまだ勝る場面を説明する。

解決する問題

ContentProvider.onCreate()は、マニフェストで宣言されたすべてのproviderについて、Application.onCreate()が戻る前にメインスレッド上で、Androidが保証しない順序で実行される。1つだけならそれで問題ない。3つ4つのライブラリがそれぞれ自分のproviderを送り込み始めると、問題になる。理由は次の通りだ。

  • 各providerはシステムがインスタンス化して登録しなければならない完全なコンポーネントであり、それぞれ固有の固定コストを持つ。
  • 順序を制御できない。ロギング層がWorkManagerのConfigurationが既に存在することを必要とする場合、契約ではないマニフェストのマージ順序に頼ることになる。
  • ビルドバリアントごとに1つを簡単に無効化できない。クラッシュレポートを必要としないデバッグビルドでも、そのproviderのonCreate()のコストは支払われる。

providerのインスタンス化にかかる固定コストをbuild.gradleにある各SDKの数だけ掛け合わせれば、通常あなたが守ろうとしている指標——コールドスタート——にまさに積み重なっていく。

App Startupが代わりに用意するもの

androidx.startupは単一のInitializationProviderを定義し、あなたのライブラリやアプリは自分のproviderを送り込む代わりにそこにInitializer<T>実装を登録する。WorkManager自身も何年も前にこのモデルに移行した——WorkManagerInitializerは独立したproviderではなく、あなた自身のinitializerと同じInitializationProviderを経由する。

小さなロギング設定用のinitializerはこうなる。

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>エントリをこの同じproviderブロックに統合する。最終的には、以前はライブラリごとに1つ必要だった仕事をこなす、単一のContentProviderインスタンスに落ち着く。

実際の依存関係を宣言する

これまでマニフェスト順序の運任せが覆い隠していた部分がdependencies()だ。セッション追跡initializerが先に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()は、マージ順序に関係なく、どのGradleモジュールが先にどの依存を宣言したかに関係なく、常にSessionTrackingInitializer.create()が実行される前に完了する。この保証こそが本当の機能であり、providerの統合はそれを可能にする仕組みにすぎない。

ライブラリを削除せずに1つだけ無効化する

もう一つよくある要望は、特定のビルドバリアントで特定のinitializerを無効化することだ——たとえば、本番の解析パイプラインを起動すべきでないデバッグビルドなど。依存関係をフォークするのではなく、そのバリアントのマニフェストで<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が取り除くのは「providerを持つこと」のオーバーヘッドだけだ。initializerのcreate()メソッドが実際に何をするかのオーバーヘッドについては何もしてくれない——そしてcreate()は、以前のContentProvider.onCreate()と同様に、デフォルトではやはりメインスレッド上で同期的に実行される。あるSDKの初期化が本当に最初のフレームより前に終わる必要がないのなら、App Startupは解決策ではない。その作業をApplication.onCreate()から起動したバックグラウンドコルーチンに委ねるか、初回使用時に遅延させるのが解決策だ。Mintlyのセッション追跡initializerはInitializerのモデルから完全に外し、最初のフレームが描画された後に発火する1回限りのWorkManagerリクエストに移した——それはすぐに起こる必要はあったが、他の何よりも先である必要はなかった。

行き着いたルールはこうだ。他のコードが安全に触れられるようになる前にライブラリが設定しなければならないもの——WorkManagerそのもののケースや、他のinitializerが存在を前提とする依存性注入コンテナなど——にはandroidx.startupを使う。単に早く始めるのが都合がよいだけのものには、遅延・オンデマンド・バックグラウンド初期化を使う。providerを一箇所に統合し、早く実行すべきものを順序づけることは本当の価値がある。App Startupを各SDKの定型コードを置くだけの場所として扱うのは、同じメインスレッドのコストを複数の場所から別の単一のproviderに移しているだけにすぎない。