Resposta rápida: use AndroidX App Startup para organizar inicializadores de bibliotecas e componentes que precisam ficar prontos no começo do processo Android, substituindo vários ContentProvider independentes por um mecanismo único e declarativo. Crie classes Initializer<T>, declare dependências com dependencies() e mantenha em startup apenas o que é realmente necessário antes da primeira tela. Para analytics, cache, sincronização e SDKs não críticos, prefira inicialização sob demanda com AppInitializer ou carregamento depois do primeiro frame. A biblioteca melhora a governança da inicialização; ela não torna código pesado automaticamente rápido.

Um Application.onCreate() com quinze SDKs, acesso a disco, migração de banco e configuração de rede é um dos caminhos mais curtos para uma abertura lenta. O problema piora quando cada biblioteca adiciona seu próprio ContentProvider: o Android cria esses providers antes de chamar Application.onCreate(), e o time perde visibilidade sobre ordem, dependências e custo total.

O App Startup, parte do Android Jetpack, cria uma forma comum de descobrir e executar inicializadores. Neste guia, você vai aprender o setup com Kotlin, a API Initializer, como controlar a ordem, como desativar inicialização automática, quando usar modo lazy, como integrar com injeção de dependências, como testar e como medir o resultado com Baseline Profiles e Macrobenchmark.

O que é AndroidX App Startup?

AndroidX App Startup é uma biblioteca para inicialização de componentes no começo do processo do app. O artefato fornece duas peças principais:

  • Initializer<T>: contrato que cria um componente e informa suas dependências;
  • AppInitializer: orquestrador que descobre inicializadores declarados no Manifest e também permite inicialização manual.

A descoberta automática acontece por meio de um único InitializationProvider incluído pela biblioteca. Os inicializadores do app aparecem como entradas <meta-data> dentro desse provider no Manifest mesclado. Assim, em vez de cada SDK manter um provider exclusivo só para executar código cedo, vários componentes compartilham a infraestrutura do App Startup.

Isso traz três ganhos:

  1. ordem explícita, pois um inicializador pode depender de outro;
  2. menos providers de bootstrap, reduzindo mecanismos duplicados;
  3. auditoria mais simples, porque os pontos de startup seguem o mesmo contrato.

Mas atenção: se um inicializador lê um arquivo enorme, abre banco, faz reflexão pesada ou bloqueia a main thread, ele continua caro. App Startup organiza o trabalho; a redução de latência vem de remover, adiar ou otimizar esse trabalho.

Quando vale usar?

Use App Startup quando o componente:

  • precisa existir antes de uma feature crítica ser acessada;
  • é compartilhado por diferentes partes do app;
  • depende de outro serviço de bootstrap;
  • hoje está escondido em um ContentProvider próprio;
  • precisa oferecer auto-inicialização em uma library interna ou pública.

Exemplos razoáveis incluem configuração mínima de logging, registro de um monitor de ciclo de vida, criação de um registry leve ou instalação de uma política global necessária cedo.

Não coloque automaticamente no startup:

  • sincronização de dados — use WorkManager;
  • abertura antecipada de telas ou navegação;
  • prefetch grande de rede;
  • leitura de preferências que só uma tela futura usa;
  • analytics que pode começar depois do consentimento;
  • banco de dados aberto “por garantia”;
  • SDK de chat ou marketing que não participa da primeira experiência.

A pergunta correta é: este componente precisa estar pronto antes do primeiro frame útil? Se a resposta for “não”, inicialize sob demanda ou depois da renderização inicial.

Dependência no Gradle Kotlin DSL

No módulo Android que declara os inicializadores:

// build.gradle.kts

dependencies {
    implementation("androidx.startup:startup-runtime:<versao-atual>")
}

Use o version catalog do projeto para centralizar a versão, em vez de espalhar pins pelos módulos. O placeholder evita que este guia congele uma versão que pode mudar; consulte a release estável compatível com o seu compileSdk, Android Gradle Plugin e restante do Jetpack.

Criando o primeiro Initializer em Kotlin

Imagine um registry de logs que precisa ser configurado cedo, mas cujo trabalho é pequeno:

package br.dev.kotlin.startup

import android.content.Context
import androidx.startup.Initializer

data class LoggingConfig(
    val applicationId: String,
    val debug: Boolean,
)

object AppLogger {
    lateinit var config: LoggingConfig
        private set

    fun install(config: LoggingConfig) {
        this.config = config
    }
}

class LoggingInitializer : Initializer<LoggingConfig> {

    override fun create(context: Context): LoggingConfig {
        val appContext = context.applicationContext
        val config = LoggingConfig(
            applicationId = appContext.packageName,
            debug = isDebugBuild(appContext),
        )

        AppLogger.install(config)
        return config
    }

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

    private fun isDebugBuild(context: Context): Boolean =
        (context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
}

O método create() recebe um Context; prefira applicationContext para objetos que sobrevivem ao ciclo de vida de Activity. O valor retornado fica associado ao inicializador e pode ser obtido pelo AppInitializer quando necessário.

O método dependencies() retorna outros inicializadores que precisam rodar antes. Não use essa lista para representar dependências normais de domínio; ela serve para ordem de bootstrap.

Declarando no AndroidManifest.xml

Para descoberta automática, adicione o inicializador como metadata do provider:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application>
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">

            <meta-data
                android:name="br.dev.kotlin.startup.LoggingInitializer"
                android:value="androidx.startup" />
        </provider>
    </application>
</manifest>

Em muitos projetos, o provider já chega pelo Manifest da dependência. O tools:node="merge" deixa explícito que sua metadata deve ser mesclada nele. Sempre abra o Merged Manifest no Android Studio para confirmar o resultado real, principalmente em apps com vários módulos e bibliotecas.

O valor androidx.startup não é uma descrição livre: é o marcador usado pela biblioteca para reconhecer a entrada.

Como declarar dependências e controlar a ordem

Suponha que a telemetria dependa do logger:

class TelemetryInitializer : Initializer<Telemetry> {

    override fun create(context: Context): Telemetry {
        return Telemetry.install(
            context = context.applicationContext,
            logger = AppLogger,
        )
    }

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

O App Startup garante que LoggingInitializer seja criado antes de TelemetryInitializer. Você não precisa declarar o logger novamente no Manifest se ele for alcançado como dependência de um inicializador descoberto; ainda assim, declarar cada raiz de forma consciente ajuda a entender o grafo.

Evite dependências circulares:

LoggingInitializer -> TelemetryInitializer -> LoggingInitializer

Esse desenho normalmente revela responsabilidades misturadas. Extraia uma configuração mínima comum ou mova a integração entre os serviços para uma camada posterior.

Inicialização automática ou lazy?

A descoberta automática é simples, mas executa no startup. Para componente não crítico, remova sua metadata e inicialize quando a feature realmente precisar:

import android.content.Context
import androidx.startup.AppInitializer

fun obterTelemetry(context: Context): Telemetry =
    AppInitializer.getInstance(context)
        .initializeComponent(TelemetryInitializer::class.java)

A inicialização é coordenada pelo AppInitializer, que respeita dependências e evita recriar o mesmo componente dentro do processo.

Um padrão útil é encapsular o acesso:

class TelemetryProvider(
    private val context: Context,
) {
    val instance: Telemetry by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        AppInitializer.getInstance(context.applicationContext)
            .initializeComponent(TelemetryInitializer::class.java)
    }
}

Use lazy com cuidado: se a primeira chamada ocorrer durante um clique crítico e o inicializador fizer trabalho pesado, você apenas moveu o travamento para outro ponto. O objetivo é colocar o custo em um momento apropriado — por exemplo, após consentimento, durante idle ou ao entrar numa feature que consegue mostrar loading.

Como desativar a inicialização automática de um componente

Para controlar manualmente um inicializador trazido por uma biblioteca, remova a metadata correspondente no Manifest do app:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">

    <meta-data
        android:name="com.exemplo.sdk.ExemploInitializer"
        tools:node="remove" />
</provider>

Depois, inicialize o componente por AppInitializer no momento escolhido.

Também é possível remover o provider inteiro com tools:node="remove", mas isso desativa a descoberta automática de todos os inicializadores que dependem dele. Faça isso somente após auditar o Merged Manifest e entender bibliotecas transitivas. Remover uma metadata “que parece inútil” pode quebrar funcionalidades como perfis, lifecycle, tracing ou outro componente instalado indiretamente.

App Startup substitui Application.onCreate()?

Não completamente. Application.onCreate() continua sendo parte do ciclo de vida do processo. App Startup é melhor para componentes isolados e com dependências declarativas; Application ainda pode coordenar configuração realmente pertencente ao app.

Uma divisão saudável:

LugarResponsabilidade
InitializerBootstrap pequeno e reutilizável de um componente
Application.onCreate()Configuração global mínima e específica do app
DI (Hilt/Koin)Construção e ciclo de vida das dependências de negócio
Primeira Activity/ComposeTrabalho ligado à primeira experiência visual
WorkManagerTrabalho persistente e adiável

Se o seu Application tem centenas de linhas, não migre cada linha mecanicamente para um Initializer. Primeiro classifique: obrigatório, adiável, específico de feature, trabalho de background ou código legado sem necessidade atual.

Integração com Hilt e Koin

App Startup não é um container de injeção de dependências. Ele resolve descoberta e ordem de inicialização. Hilt e Koin resolvem criação e fornecimento de objetos.

Com Hilt, existe um detalhe de ordem: o graph do app e componentes internos têm seus próprios passos de bootstrap. Evite criar um Initializer que espera injetar uma Activity ou um objeto que só existe depois. Para componentes Android modernos, veja Hilt com módulos, scopes e testes e a comparação Koin vs Hilt.

Princípios práticos:

  • um initializer pode instalar uma dependência técnica leve;
  • o container pode expor um facade que usa essa dependência;
  • não transforme AppInitializer em service locator espalhado pelo código;
  • mantenha a chamada manual atrás de uma interface ou provider testável;
  • documente quem é dono do ciclo de vida.

Se a configuração depende de consentimento, sessão ou remote config, ela provavelmente não deve rodar automaticamente antes da UI.

Impacto em startup: o que realmente medir

O custo de abertura não é apenas Application.onCreate(). A sequência inclui criação do processo, inicialização de providers, Application, Activity, inflação ou composição e primeiro frame.

Por isso, meça pelo menos:

  • time to initial display: quando o primeiro frame aparece;
  • time to full display: quando a tela fica realmente utilizável;
  • duração de ContentProvider.onCreate() e inicializadores;
  • I/O na main thread;
  • classes carregadas e SDKs instalados cedo;
  • regressão entre releases.

Use Perfetto/System Trace para enxergar a timeline. Use Macrobenchmark para comparar cenários controlados antes e depois. Se o app também usa SplashScreen API, não prolongue a splash para esconder inicialização desnecessária; reduza o caminho crítico.

Uma estratégia de refatoração:

  1. capture um benchmark de cold startup;
  2. liste providers no Merged Manifest;
  3. trace o Application.onCreate();
  4. remova inicializações sem consumidor na primeira tela;
  5. transforme componentes restantes em inicializadores pequenos;
  6. torne lazy o que não é crítico;
  7. rode o mesmo benchmark e compare distribuição, não apenas a melhor execução.

Evite I/O e rede na main thread

Initializer.create() normalmente participa do caminho inicial do processo. Trate-o como código de orçamento limitado.

Evite:

class CatalogInitializer : Initializer<Catalog> {
    override fun create(context: Context): Catalog {
        val json = context.assets.open("catalogo-gigante.json")
            .bufferedReader()
            .readText()
        return Catalog.parse(json)
    }

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

Prefira criar apenas o objeto leve e carregar os dados quando necessário:

class CatalogInitializer : Initializer<CatalogRepository> {
    override fun create(context: Context): CatalogRepository =
        CatalogRepository(
            assets = context.applicationContext.assets,
            dispatcher = kotlinx.coroutines.Dispatchers.IO,
        )

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

Construir o repository é barato; ler e parsear pode acontecer fora da main thread quando a feature solicitar. A mesma ideia vale para Room, DataStore e arquivos. Para preferências leves, consulte o tutorial de DataStore com Kotlin; para banco, veja migrações do Room.

Testando um Initializer

Inicializadores devem ser finos. Coloque lógica complexa em classes comuns e teste-as diretamente. Ainda assim, vale validar criação e dependências.

class FeatureFlagsInitializer : Initializer<FeatureFlags> {
    override fun create(context: Context): FeatureFlags =
        FeatureFlags.from(context.applicationContext)

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

O que testar:

  • dependencies() declara a ordem esperada;
  • create() usa applicationContext, não guarda Activity;
  • inicialização repetida pelo orquestrador devolve o componente já criado;
  • erro de configuração falha de forma observável;
  • o app funciona quando o componente lazy ainda não foi inicializado;
  • o Manifest final contém apenas as entradas pretendidas.

Em teste instrumentado, use um Manifest específico de teste quando precisar remover ou trocar metadata. Evite testes que dependem acidentalmente da ordem global do runner. Para uma estratégia ampla, combine testes Android com Compose e Maestro com testes unitários das classes de configuração.

Modularização e bibliotecas internas

App Startup é especialmente útil em projetos modularizados. Uma library pode fornecer seu próprio Initializer, mas deve ser conservadora: auto-inicialização transfere custo para todo app consumidor.

Para uma biblioteca interna:

  • documente por que precisa rodar cedo;
  • exponha opção de inicialização manual;
  • mantenha create() rápido e idempotente;
  • declare dependências via dependencies();
  • não faça rede;
  • permita que o app remova a metadata;
  • publique métricas de impacto em startup quando possível.

No app, audite o grafo junto com a estratégia de modularização Android com Kotlin e Compose. O fato de uma feature estar em módulo separado não significa que ela precisa inicializar no processo inteiro.

Erros comuns

Migrar código sem reduzir o caminho crítico

Mover dez chamadas de Application.onCreate() para dez Initializer deixa o diagrama mais bonito, mas pode manter a mesma latência. Classifique e adie antes de reorganizar.

Fazer trabalho pesado em create()

Parse grande, banco, rede, reflexão e leitura síncrona prejudicam o primeiro frame. Crie objetos leves; carregue dados fora da main thread.

Usar dependência para impor uma ordem arbitrária

dependencies() deve representar necessidade real. Se B só “costuma rodar depois de A”, talvez não exista dependência técnica.

Remover metadata sem conferir o Merged Manifest

Nomes e providers podem vir de dependências transitivas. Confirme o class name exato e teste variantes debug, release e flavors.

Inicializar analytics antes do consentimento

Além de custo, há governança de privacidade. Em apps com dados pessoais, alinhe consentimento e minimização conforme as práticas do guia de LGPD no Android.

Usar AppInitializer em todo lugar

Chamadas espalhadas criam um service locator implícito. Encapsule o acesso em um provider ou na DI.

Otimizar sem benchmark

Startup varia por dispositivo, estado de compilação e cache. Compare cold starts no mesmo ambiente e acompanhe percentis.

Checklist de produção

Antes de liberar a mudança:

  • o Merged Manifest foi revisado;
  • cada initializer tem justificativa para rodar cedo;
  • create() não faz rede nem I/O pesado na main thread;
  • dependências não têm ciclos;
  • componentes não críticos são lazy;
  • a remoção de metadata foi testada em release;
  • o Application.onCreate() ficou menor e compreensível;
  • startup foi medido antes e depois com Macrobenchmark ou Perfetto;
  • bibliotecas internas permitem opt-out;
  • consentimento e privacidade foram respeitados;
  • falhas de inicialização aparecem em logs e Crashlytics;
  • a primeira tela não depende de trabalho que poderia ocorrer depois.

Perguntas frequentes

AndroidX App Startup deixa o app mais rápido sozinho?

Não. Ele pode reduzir a sobrecarga de múltiplos providers e melhora a organização, mas o ganho principal vem de enxergar o grafo e adiar trabalho não crítico. Código pesado continua pesado.

Posso usar App Startup com Jetpack Compose?

Sim. A biblioteca opera no nível do processo Android, antes da UI. Compose não muda o contrato. O cuidado é não bloquear a chegada ao primeiro frame da Activity Compose.

App Startup substitui Hilt?

Não. App Startup coordena inicialização; Hilt fornece injeção e escopos. Eles podem coexistir, desde que a ordem e a responsabilidade de cada um estejam claras.

Preciso colocar todo SDK em um Initializer?

Não. Muitos SDKs já possuem bootstrap próprio; outros devem começar sob demanda. Audite documentação, Manifest mesclado, consentimento e impacto antes de encapsular.

Como faço inicialização sob demanda?

Remova a metadata automática do componente e chame AppInitializer.getInstance(context).initializeComponent(SuaClasse::class.java) quando a feature precisar dele.

Posso remover o InitializationProvider inteiro?

Pode tecnicamente, mas isso desativa toda descoberta automática baseada nele. É uma mudança de alto impacto. Prefira remover apenas metadata específica depois de auditar dependências.

Isso ajuda ANR?

Pode ajudar quando o ANR ou lentidão está ligado a trabalho síncrono no começo do processo, mas você precisa mover ou otimizar o trabalho. Para diagnóstico amplo, monitore ANRs e crashes com Firebase Crashlytics.

Conclusão

AndroidX App Startup com Kotlin é uma ferramenta de governança de inicialização: centraliza descoberta, torna dependências explícitas e facilita escolher entre bootstrap automático e lazy. A implementação básica é pequena; o valor real aparece quando o time usa esse contrato para questionar cada milissegundo antes do primeiro frame.

Comece pelo Merged Manifest e pelo benchmark, não pela refatoração. Identifique providers e trabalho no Application, preserve apenas o mínimo crítico, transforme componentes em inicializadores finos e adie o restante. Depois, valide em aparelho intermediário e em build release.

Para continuar a trilha de performance Android, leia Baseline Profiles e Macrobenchmark, SplashScreen no Android, Coil com Compose e o guia de performance em Kotlin. Em portfólio e entrevistas, mostrar um before/after de cold startup com Perfetto, Manifest auditado e inicialização lazy demonstra muito mais maturidade do que apenas citar a biblioteca.