25、App Startup:App Startup原理、Initializer、自动初始化、手动初始化、实战:优化启动速度
聊到App启动优化,很多同学第一反应就是「把初始化放到子线程」。嗯,这个思路没错,但实际操作起来坑特别多。我记得有一次接手一个老项目,光Application的onCreate里就排了十几个初始化任务,有的依赖网络,有的依赖数据库,还有的必须跑在主线程。结果呢?启动白屏时间直奔3秒,用户都以为App卡死了。
后来我引入了App Startup这个组件,才算是把这一团乱麻理顺了。说白了,App Startup就是帮我们管理初始化任务的「调度中心」。它解决了两个核心问题:一是自动梳理依赖关系,二是把初始化时机从「一股脑全启动」变成「按需加载」。
App Startup 的核心原理
App Startup 本质上是一个内容提供器(ContentProvider)。你可能会问:为什么用ContentProvider?因为Android系统在Application.attachBaseContext()之后、Application.onCreate()之前,就会自动初始化所有注册的ContentProvider。App Startup就是利用这个机制,在系统启动早期就把初始化任务跑起来。
它的工作流程其实很简单:
- 每个初始化任务封装成一个Initializer
- Initializer之间可以声明依赖关系
- App Startup根据依赖关系构建一个有向无环图(DAG)
- 按拓扑排序的顺序依次执行初始化
我画了一张图,帮你理解这个流程:
Initializer 接口详解
App Startup 的核心接口就是 Initializer<T>。它只有两个方法:
public interface Initializer<T> {
// 执行初始化,返回初始化结果
T create(@NonNull Context context);
// 声明依赖的其他Initializer
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
}
create()方法里写你的初始化逻辑。dependencies()返回这个任务依赖哪些其他任务。举个例子:
// 初始化网络库
class NetworkInitializer : Initializer<OkHttpClient> {
override fun create(context: Context): OkHttpClient {
// 这里做网络库的初始化
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// 网络库不依赖其他初始化
return emptyList()
}
}
// 初始化数据库,依赖网络库
class DatabaseInitializer : Initializer<AppDatabase> {
override fun create(context: Context): AppDatabase {
// 这里可能用到网络配置
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"my_db"
).build()
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// 数据库初始化依赖网络库先完成
return listOf(NetworkInitializer::class.java)
}
}
自动初始化 vs 手动初始化
App Startup 提供了两种初始化模式,各有各的适用场景。
自动初始化
自动初始化是最省事的方式。你只需要在AndroidManifest中注册一个ContentProvider:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.app.NetworkInitializer"
android:value="androidx.startup" />
</provider>
这样App启动时,App Startup会自动扫描所有meta-data,找到Initializer并执行。你想想看,是不是特别方便?但方便归方便,有个问题:所有注册的Initializer都会在启动时执行,不管你现在用不用得到。
手动初始化
手动初始化给了我们更大的控制权。你可以在AndroidManifest中标记某个Initializer为「不自动初始化」:
<meta-data
android:name="com.example.app.ImageLoaderInitializer"
android:value="androidx.startup"
tools:node="remove" />
然后在真正需要的时候再手动调用:
// 在某个Activity或Fragment中
AppInitializer.getInstance(context)
.initializeComponent(ImageLoaderInitializer::class.java)
这样做的好处是:图片加载库只在用户进入图片列表页时才初始化,而不是App一启动就加载。我之前的项目里,光这一项改动就省掉了200ms的启动时间。
| 对比项 | 自动初始化 | 手动初始化 |
|---|---|---|
| 启动时机 | Application.onCreate()之前 | 按需调用时 |
| 适用场景 | 崩溃收集、日志、核心网络库 | 图片加载、推送、广告SDK |
| 依赖管理 | 自动处理 | 需手动保证依赖已初始化 |
| 启动耗时 | 增加冷启动时间 | 不增加冷启动时间 |
实战:优化启动速度
好了,理论说完了,咱们来点实际的。我之前接手的一个电商App,启动时要做这些事:
- 初始化崩溃收集(Bugly)
- 初始化网络库(Retrofit + OkHttp)
- 初始化数据库(Room)
- 初始化图片加载(Glide)
- 初始化推送(极光)
- 初始化统计(友盟)
- 初始化广告SDK
原来的代码全写在Application.onCreate()里,串行执行,耗时约1.8秒。我用App Startup重构后,变成了这样:
// 第一梯队:必须尽早启动的
class BuglyInitializer : Initializer<Unit> {
override fun create(context: Context) {
Bugly.init(context, "app_id", false)
}
override fun dependencies() = emptyList()
}
class NetworkInitializer : Initializer<OkHttpClient> {
override fun create(context: Context): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
override fun dependencies() = emptyList()
}
// 第二梯队:依赖网络库的
class DatabaseInitializer : Initializer<AppDatabase> {
override fun create(context: Context): AppDatabase {
return Room.databaseBuilder(
context, AppDatabase::class.java, "shop_db"
).build()
}
override fun dependencies() = listOf(NetworkInitializer::class.java)
}
// 第三梯队:按需加载的(手动初始化)
class GlideInitializer : Initializer<Unit> {
override fun create(context: Context) {
Glide.init(context, GlideBuilder().setLogLevel(Log.ERROR))
}
override fun dependencies() = emptyList()
}
然后在AndroidManifest中,我只让Bugly和Network自动初始化:
<provider ...>
<meta-data
android:name="com.example.app.BuglyInitializer"
android:value="androidx.startup" />
<meta-data
android:name="com.example.app.NetworkInitializer"
android:value="androidx.startup" />
<!-- Database 由 Network 依赖触发,不需要单独注册 -->
<!-- Glide 标记为不自动初始化 -->
<meta-data
android:name="com.example.app.GlideInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
Glide只在用户进入首页时手动初始化:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 按需初始化Glide
AppInitializer.getInstance(this)
.initializeComponent(GlideInitializer::class.java)
setContentView(R.layout.activity_main)
}
}
优化结果:冷启动时间从1.8秒降到了0.6秒。推送和广告SDK甚至放到了用户登录之后才初始化,又省了300ms。整个启动过程变得非常清爽。
避坑指南
我用App Startup也踩过一些坑,分享给你:
- 我曾经在Initializer里直接做了网络请求,结果因为主线程阻塞导致ANR。记住,create()默认跑在主线程,耗时操作要自己切线程。
- 我曾经忘记在dependencies()里声明依赖,结果两个Initializer同时执行,一个读数据库一个写数据库,直接崩了。依赖关系一定要写全。
- 注意ContentProvider的初始化顺序在不同Android版本上可能不一样。别假设A一定比B先执行,老老实实用dependencies()声明。
嗯,App Startup其实不复杂,但它解决了一个很实际的问题:让初始化变得有序、可控。你想想看,一个App几十个SDK,如果没有统一的调度机制,启动代码迟早变成一锅粥。用上App Startup之后,至少你能清楚地知道:哪个任务先跑,哪个后跑,哪个可以晚点跑。
最后说一句,启动优化不是一锤子买卖。每次引入新的SDK,都要问自己一句:这个初始化真的需要放在启动时吗?如果答案是否定的,就用手动初始化。久而久之,你的App启动速度会越来越快。
公众号:蓝海资料掘金营,微信deep3321