一、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 依赖图可视化
下面这张图展示了典型的依赖关系:
三、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 负责库的初始化顺序管理。具体来说:
- 在 Hilt 的 Module 里提供 Initializer 所需的依赖
- 在 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