Aller au contenu
Tous les articles

androidx.startup en 2026 : ordonner l'initialisation des bibliothèques sans empiler les ContentProvider

Un guide pratique de la bibliothèque App Startup d'Android — regrouper les initializers dans un seul ContentProvider, déclarer les dépendances, et les cas d'init paresseuse qu'elle ne remplace pas.

MFKAPPS 5 min de lecture

Chaque bibliothèque qui doit exécuter du code avant votre première Activity procède de la même façon : un ContentProvider sans logique de requête, dont le seul rôle est de se déclencher au démarrage de l’application. WorkManager le fait. Firebase le fait. Tout SDK d’analytics ou de crash reporting que vous ajoutez le fait aussi. Chacun est invisible dans votre propre code, et chacun effectue une initialisation complète de composant — enregistrement binder, un nouvel appel ContentResolver — avant que votre application n’ait dessiné le moindre pixel. androidx.startup existe pour regrouper tout cela dans un seul ContentProvider, avec un graphe de dépendances explicite au lieu d’un graphe accidentel.

Je suis tombé sur ce problème en réduisant le temps de démarrage à froid sur Mintly, où WorkManager, une petite couche de logging et ma propre étape de préchargement enregistraient chacun leur propre provider. Voici ce qu’App Startup apporte réellement, et où l’initialisation paresseuse manuelle reste supérieure.

Le problème résolu

ContentProvider.onCreate() s’exécute sur le thread principal, avant que Application.onCreate() ne retourne, pour chaque provider déclaré dans le manifeste — dans un ordre qu’Android ne garantit pas. C’est acceptable avec un seul provider. Ça cesse de l’être dès que trois ou quatre bibliothèques expédient chacune le leur, car :

  • Chaque provider est un composant complet que le système doit instancier et enregistrer, avec son propre coût fixe.
  • Vous n’avez aucun contrôle sur l’ordre. Si votre couche de logging a besoin que la Configuration de WorkManager existe déjà, vous dépendez de l’ordre de fusion du manifeste, qui n’est pas un contrat.
  • Vous ne pouvez pas désactiver facilement l’un d’eux pour une variante de build — un build debug qui n’a pas besoin du crash reporting paie quand même le coût du onCreate() de ce provider.

Multipliez le coût fixe de l’instanciation d’un provider par chaque SDK de votre build.gradle, et cela s’accumule exactement sur la métrique — le démarrage à froid — que vous essayez habituellement de protéger.

Ce qu’App Startup met à la place

androidx.startup définit un seul InitializationProvider, et votre bibliothèque ou application y enregistre des implémentations Initializer<T> au lieu d’expédier son propre provider. WorkManager lui-même est passé à ce modèle il y a des années — son WorkManagerInitializer passe par le même InitializationProvider que vos propres initializers, pas par un provider séparé.

Un initializer pour une petite configuration de logging ressemble à ceci :

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()
}

Déclaré une seule fois dans le manifeste, sous le point de fusion unique 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>

Chaque bibliothèque utilisant App Startup fusionne ses entrées <meta-data> dans ce même bloc provider via la fusion de manifeste. Vous vous retrouvez avec une seule instance de ContentProvider faisant le travail qui nécessitait auparavant une instance par bibliothèque.

Déclarer de vraies dépendances

La partie que la chance de l’ordre du manifeste dissimulait auparavant, c’est dependencies(). Si un initializer de suivi de session a besoin que WorkManager soit configuré au préalable, dites-le explicitement :

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 construit un graphe de dépendances à partir de ces déclarations et le trie topologiquement, de sorte que WorkManagerInitializer.create() termine toujours avant que SessionTrackingInitializer.create() ne s’exécute — indépendamment de l’ordre de fusion, indépendamment du module Gradle qui a déclaré quelle dépendance en premier. Cette garantie est la vraie fonctionnalité ; regrouper les providers n’est que le mécanisme qui la rend possible.

Désactiver un initializer sans supprimer la bibliothèque

L’autre besoin courant est de désactiver un initializer spécifique pour une variante de build — un build debug qui ne devrait pas démarrer un pipeline d’analytics de production, par exemple. Vous ne forkez pas la dépendance ; vous supprimez son entrée <meta-data> dans le manifeste de cette variante :

<!-- 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>

Le code de la bibliothèque reste intact ; seul le déclencheur qui l’exécute au démarrage disparaît pour cette variante.

Là où ça n’aide pas

App Startup ne supprime que le coût du fait d’avoir un provider. Il ne fait rien contre le coût de ce que la méthode create() de l’initializer fait réellement — et create() s’exécute toujours de façon synchrone sur le thread principal par défaut, comme le faisait l’ancien ContentProvider.onCreate(). Si l’initialisation d’un SDK n’a réellement pas besoin de se terminer avant votre première frame, App Startup n’est pas la solution ; reporter ce travail vers une coroutine en arrière-plan lancée depuis Application.onCreate(), ou de façon paresseuse à la première utilisation, l’est. J’ai déplacé l’initializer de suivi de session de Mintly hors du modèle Initializer complètement, vers une requête WorkManager à usage unique déclenchée après l’affichage de la première frame — cela devait arriver bientôt, pas avant tout le reste.

La règle à laquelle je suis arrivé : utilisez androidx.startup pour tout ce qu’une bibliothèque doit configurer avant que d’autre code puisse y toucher en toute sécurité — le cas de WorkManager lui-même, ou un conteneur d’injection de dépendances dont d’autres initializers supposent l’existence. Utilisez une initialisation différée, paresseuse ou en arrière-plan pour tout ce qui est simplement pratique à démarrer tôt. Regrouper les providers et ordonner ceux qui doivent s’exécuter tôt, c’est de la vraie valeur ; traiter App Startup comme un endroit où mettre le code de démarrage de chaque SDK ne fait que déplacer le même coût sur le thread principal vers un provider unique différent au lieu de plusieurs.