19、App Startup:App Startup原理、ContentProvider优化、初始化顺序控制

说实话,Android 开发做到一定阶段,你一定会遇到一个头疼的问题——App 启动太慢了。尤其是那些第三方 SDK,一个比一个积极,全都在 Application 的 onCreate 里抢着初始化。我见过一个项目,光 ContentProvider 就注册了十几个,启动时间直接飙到 3 秒多。嗯,今天我们就来聊聊 App Startup 这个组件,看看它是怎么帮我们解决这个问题的。

ContentProvider 的「隐形成本」

先说说为什么会有 App Startup 这个东西。你想想看,每个第三方库为了能自动初始化,都会在 AndroidManifest 里注册一个 ContentProvider。比如这样:

<provider
    android:name=".MyInitProvider"
    android:authorities="${applicationId}.myinit"
    android:exported="false" />

这个 ContentProvider 会在 Application 的 onCreate() 之前被调用。也就是说,你的 App 还没跑起来,十几个 ContentProvider 已经排着队执行了。每个 ContentProvider 的 onCreate() 里可能都在做网络请求、数据库初始化、文件读写……

注意:ContentProvider 的初始化是串行的。系统会按优先级顺序依次调用每个 Provider 的 onCreate()。这意味着,一个耗时的初始化会阻塞后面所有的 Provider,包括 Application 的创建。

我曾经在一个项目中排查启动耗时,发现有个第三方推送库的 ContentProvider 初始化竟然花了 800ms。它只是在里面做了个网络长连接。但问题是,这个连接完全可以延迟到真正需要推送的时候再建立。

App Startup 的核心原理

App Startup 说白了就做了一件事:把多个 ContentProvider 合并成一个。它自己只注册一个 ContentProvider,然后由它来统一管理所有初始化任务。

它的核心类就两个:

  • Initializer<T>:定义初始化逻辑的接口
  • AppInitializer:负责调度执行所有 Initializer

我们来看一个具体的例子。假设你的 App 需要初始化 WorkManager 和 CrashReport:

class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val config = Configuration.Builder()
            .setMinimumLoggingLevel(Log.INFO)
            .build()
        WorkManager.initialize(context, config)
        return WorkManager.getInstance(context)
    }

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

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

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

注意看 dependencies() 方法。它返回了当前 Initializer 依赖的其他 Initializer。App Startup 会根据这个依赖关系,自动决定执行顺序。上面的例子中,CrashReport 依赖 WorkManager,所以 WorkManager 会先初始化。

核心思想:App Startup 通过有向无环图(DAG)来管理初始化顺序。它会先执行没有依赖的 Initializer,然后逐层推进。如果存在循环依赖,会在编译期就报错。

初始化顺序控制

控制初始化顺序,是 App Startup 最实用的功能之一。我个人的习惯是,把初始化任务分成三类:

优先级 类型 示例
立即执行 核心组件 日志、崩溃收集、性能监控
延迟执行 业务组件 推送、广告、统计
按需执行 非核心组件 图片加载库、数据库升级

对于「立即执行」的组件,我们直接通过 ContentProvider 自动初始化。对于「延迟执行」的,可以在 Application 的 onCreate() 里手动调用:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 手动触发延迟初始化
        AppInitializer.getInstance(this)
            .initializeComponent(PushInitializer::class.java)
    }
}

对于「按需执行」的,那就更简单了——用到的时候再初始化。比如图片加载库,可以在用户第一次打开图片页面时再初始化。

SVG 流程图:App Startup 初始化流程

App Startup 初始化流程 App 启动 系统调用 App Startup 的 ContentProvider 解析 DAG 依赖图 按拓扑顺序执行 Initializer Application.onCreate() 执行 只有一个 ContentProvider 自动处理依赖关系 无依赖的先执行

如何迁移到 App Startup

迁移过程其实很简单。我建议你按以下步骤来:

  1. 添加依赖:在 build.gradle 里引入 androidx.startup:startup-runtime:1.1.1
  2. 创建 Initializer:把每个 ContentProvider 的初始化逻辑提取到 Initializer 中
  3. 删除 ContentProvider:从 AndroidManifest 中移除旧的 Provider 声明
  4. 配置合并:在 AndroidManifest 中配置 App Startup 的 Provider,并设置 android:authorities

这里有个细节要注意。如果你原来的 ContentProvider 里用到了 android:authorities 做唯一标识,迁移后需要确保新的 Provider 的 authorities 不与其他库冲突。我一般会用 ${applicationId}.startup 这种格式。

小技巧:如果你不想让某个 Initializer 自动执行,可以在 AndroidManifest 中通过 meta-data 来禁用:
<meta-data android:name=".MyInitializer" android:value="false" />

避坑指南

我曾经在一个项目里踩过一个坑。有个第三方库的 Initializer 在 create() 方法里直接做了网络请求,结果因为网络超时,整个 App 启动被阻塞了 30 秒。嗯,这里要记住:Initializer 的 create() 方法是在主线程执行的。任何耗时操作都应该放到子线程,或者干脆延迟执行。

另外,如果你使用了 Hilt 或 Dagger,要注意 Initializer 的创建时机。我建议在 Initializer 里不要依赖注入框架,因为此时 DI 容器可能还没初始化好。可以用 ContentResolver 或者 SharedPreferences 来做一些轻量级的初始化。

最后说一个容易被忽略的点:App Startup 的 ContentProvider 本身也是有开销的。虽然它只注册了一个 Provider,但这个 Provider 的 onCreate() 方法里会遍历所有 Initializer 并构建 DAG。如果你的 Initializer 数量特别多(比如超过 50 个),这个遍历过程本身也会消耗一些时间。不过对于大多数 App 来说,这个开销可以忽略不计。

好了,关于 App Startup 的原理和用法,就聊到这里。记住一句话:启动优化没有银弹,但 App Startup 至少帮你把「多个 ContentProvider」这个坑填平了

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