一、Startup 是什么?为什么我们需要它?

说实话,刚接触 Android 开发那会儿,我对 App 启动时的初始化工作挺头疼的。每个第三方库都要在 Application.onCreate() 里写一行初始化代码,时间长了,那个方法能写上百行。更麻烦的是,有些库之间有依赖关系——比如 Crash 上报库必须在日志库之后初始化,顺序错了就出问题。

Jetpack Startup 就是来解决这个问题的。它提供了一套标准化的初始化机制,让你能声明式地管理各个库的初始化顺序。说白了,你只需要告诉 Startup「谁依赖谁」,它自动帮你排好序,按顺序初始化。

核心价值:告别手动管理初始化顺序,让依赖关系自动解析。

二、Startup 的初始化原理与依赖图

2.1 核心接口:Initializer

Startup 的核心是一个叫 Initializer 的接口。你想想看,每个需要初始化的库,都实现这个接口,告诉 Startup 两件事:

  • create():怎么创建这个库的实例
  • dependencies():它依赖哪些其他 Initializer

我举个例子,假设我们有一个日志库和一个崩溃上报库,崩溃库依赖日志库:

class LoggerInitializer : Initializer<Logger> {
    override fun create(context: Context): Logger {
        return Logger.init(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList() // 没有依赖
    }
}

class CrashReporterInitializer : Initializer<CrashReporter> {
    override fun create(context: Context): CrashReporter {
        val logger = LoggerInitializer().create(context)
        return CrashReporter.init(context, logger)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return listOf(LoggerInitializer::class.java)
    }
}

2.2 依赖图是如何构建的?

Startup 内部维护了一个有向无环图(DAG)。它会遍历所有注册的 Initializer,根据 dependencies() 方法返回的依赖关系,构建出一张图。然后进行拓扑排序,确保每个 Initializer 都在它的依赖项之后初始化。

我在项目中遇到过一个问题:两个库互相依赖,结果 Startup 直接抛异常了。嗯,这里要注意,循环依赖是不允许的,Startup 会检测到并报错。

避坑指南:我曾经在 dependencies() 里写错了类名,导致编译没问题但运行时找不到依赖。建议用 ::class.java 而不是硬编码字符串。

2.3 依赖图可视化

下面这张图展示了典型的依赖关系:

LoggerInitializer 无依赖 CrashReporterInitializer 依赖 Logger AnalyticsInitializer 依赖 Logger 初始化顺序:Logger → CrashReporter → Analytics 拓扑排序保证依赖项先初始化 Startup 依赖图示例

三、ContentProvider 的自动初始化

Startup 默认使用 ContentProvider 来实现自动初始化。你想想看,ContentProvider 在 Application.onCreate() 之前就会被系统调用,所以 Startup 利用这个特性,在 ContentProvider 的 onCreate() 里完成所有初始化工作。

具体来说,你需要在 AndroidManifest.xml 里注册一个 ContentProvider:

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

这样,App 启动时 Startup 会自动扫描所有 meta-data,找到注册的 Initializer,构建依赖图,然后按顺序初始化。

注意:ContentProvider 的初始化发生在主线程,所以不要在 create() 里做耗时操作。我曾经见过一个团队把网络请求放在初始化里,结果 ANR 了。

四、手动初始化与延迟加载

自动初始化虽然方便,但有时候我们不想让某些库在 App 启动时就加载。比如一个只在特定页面才用到的分析库,提前初始化纯属浪费。

Startup 提供了手动初始化的方式。你只需要在 meta-data 里加上 tools:node="remove",告诉 Startup 不要自动初始化这个库:

<meta-data
    android:name="com.example.AnalyticsInitializer"
    android:value="androidx.startup"
    tools:node="remove" />

然后在需要的时候,手动调用:

val analytics = AppInitializer.getInstance(context)
    .initializeComponent(AnalyticsInitializer::class.java)

我个人习惯把这种延迟初始化的逻辑封装在 Repository 或 ViewModel 里,等用户真正进入相关页面时才触发。这样 App 的冷启动速度能快不少。

经验之谈:我曾经把一个图片加载库也放到了自动初始化里,结果启动时间多了 200ms。后来改成按需初始化,用户体验明显提升。

五、Startup 与 Hilt 的配合使用

Hilt 是 Google 官方推荐的依赖注入框架,它本身也有初始化逻辑。那么问题来了:Startup 和 Hilt 怎么共存?

其实很简单。Hilt 的初始化也是通过 ContentProvider 实现的,它内部有一个 HiltInitProvider。如果你同时使用 Startup 和 Hilt,需要注意它们的初始化顺序。

我建议的做法是:让 Hilt 负责依赖注入,Startup 负责库的初始化顺序管理。具体来说:

  1. 在 Hilt 的 Module 里提供 Initializer 所需的依赖
  2. 在 Initializer 的 create() 方法里通过 Hilt 获取实例

举个例子:

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideLogger(): Logger {
        return Logger.init(applicationContext)
    }
}

class CrashReporterInitializer @Inject constructor(
    private val logger: Logger
) : Initializer<CrashReporter> {
    override fun create(context: Context): CrashReporter {
        return CrashReporter.init(context, logger)
    }

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

这里要注意,Hilt 的初始化必须在 Startup 之前完成。好在 Hilt 的 ContentProvider 优先级默认比 Startup 高,所以一般没问题。但如果你自定义了 ContentProvider 的优先级,就要小心了。

最佳实践:用 Hilt 管理对象的创建和生命周期,用 Startup 管理初始化顺序和时机。两者各司其职,配合默契。

六、总结

Startup 这个库,说白了就是帮你把初始化这件事「结构化」了。它用依赖图解决了顺序问题,用 ContentProvider 解决了时机问题,用手动初始化解决了性能问题。

我在多个项目里实践下来,觉得最爽的一点是:再也不用在 Application 里写一大坨初始化代码了。每个库的初始化逻辑都封装在自己的 Initializer 里,代码清晰多了。

如果你还在手动管理初始化,不妨试试 Startup。相信我,你会爱上这种「声明式」的编程体验。


公众号:蓝海资料掘金营,微信deep3321