1 背景
- 想必大家都了解,我们在做
Android 开发的时候,我们都会在 Application 中的 attachBaseContext() 方法或者 onCreate() 方法中去进行初始化操作,并顺便获取到 Application 的上下文,这里的初始化包含我们项目本身的类初始化或第三方库初始化或第三方 SDK 初始化。
- 但是上述这种常规操作,只能满足一些简单的业务需求,比较复杂的情况就不太优雅了,比如说组件化等场景。
- 那么有没有一种在应用启动时能够更加简单、高效的方式来初始化组件,且能适配各种复杂场景呢?
2 ContentProvider 方案进行初始化
2.1 概念
- 利用
ContentProvider 进行初始化,定义一个 ContentProvider,然后在 onCreate() 方法中拿到上下文,然后在此进行初始化操作。很多第三方库就用到了这种办法,例如:LeakCanary 2.4、Picasso 2.7、AutoSize 1.1.2 等等。
ContentProvider 初始化流程图:

2.2 优缺点
- 优点:可以偷偷摸摸就进行初始化操作,而不是在
Application 中书写代码进行初始化操作。
- 缺点:如果
ContentProvider 过多,启动过多的 ContentProvider 会增加应用的启动时间。
2.3 使用步骤
class TestInitializer: ContentProvider() {
override fun onCreate(): Boolean {
val application = context !!.applicationContext as Application
SamplesLogger.i("ContentProvider Test onCreate: ")
return true
}
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
throw IllegalStateException("Not allowed.")
}
override fun getType(p0: Uri): String? {
throw IllegalStateException("Not allowed.")
}
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
throw IllegalStateException("Not allowed.")
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
throw IllegalStateException("Not allowed.")
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
throw IllegalStateException("Not allowed.")
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx">
<application
...>
<provider
android:name="xxx.TestInitializer"
android:authorities="${applicationId}.TestInitializer"
android:exported="false" />
</application>
</manifest>
3 Initializer 方案进行初始化
3.1 概念
- 定义
Initializer 接口,然后新增一个类实现该接口,并在 AndroidManifest.xml 文件内,添加其对应的 meta-data 标签信息,最终在 Application 中的 attachBaseContext() 方法或者 onCreate() 方法中进行 init 操作。
Initializer 初始化流程图:

3.2 优缺点
- 优点:可以简化启动序列并显式设置初始化依赖顺序,且简单、高效,比较符合国内合规隐私要求。
- 缺点:还是需要依赖在壳
App 的 Application 中进行 AppInitializer.initialize(this) 操作。
3.3 源代码
interface Initializer {
fun initialize(context: Context)
fun dependencies(): List<Class<out Initializer>>
}
object AppInitializer {
private val mInitialized = mutableSetOf<Class<out Initializer>>()
private val mDiscovered = mutableSetOf<Class<out Initializer>>()
fun initialize(context: Context) {
discoveryAndInitialize(context)
}
fun initializeComponent(context: Context, component: Class<out Initializer>) {
doInitialize(context, component, mutableSetOf())
}
private fun discoveryAndInitialize(context: Context) {
try {
val applicationInfo: ApplicationInfo = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
val metaData: Bundle = applicationInfo.metaData
val initialize = context.getString(R.string.open_initialize)
val initializing = mutableSetOf<Class<*>>()
metaData.keySet().forEach {key ->
val value = metaData.get(key)
if (initialize == value) {
val clazz = Class.forName(key)
if (Initializer::class.java.isAssignableFrom(clazz)) {
val component = clazz as Class<out Initializer>
mDiscovered.add(component)
InitializerLogger.i("Discovered $key")
doInitialize(context, component, initializing)
}
}
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
InitializerLogger.e("NameNotFoundException ${e.localizedMessage}")
}
}
@Synchronized
private fun doInitialize(context: Context, component: Class<out Initializer>, initializing: MutableSet<Class<*>>) {
if (initializing.contains(component)) {
InitializerLogger.e("Cannot initialize ${component.name}")
return
}
if (! mInitialized.contains(component)) {
initializing.add(component)
try {
val instance = component.getDeclaredConstructor().newInstance()
val initializer = instance as Initializer
val dependencies = initializer.dependencies()
if (dependencies.isNotEmpty()) {
dependencies.forEach {clazz ->
if (! mInitialized.contains(clazz)) {
doInitialize(context, clazz, initializing)
}
}
}
InitializerLogger.i("Initializing ${component.name}")
initializer.initialize(context)
InitializerLogger.i("Initialized ${component.name}")
initializing.remove(component)
mInitialized.add(component)
} catch (e: Throwable) {
InitializerLogger.i("Initialize error -> ${e.localizedMessage}")
}
}
}
}
@RestrictTo(RestrictTo.Scope.LIBRARY)
object InitializerLogger {
private const val TAG = "InitializerLogger"
private const val DEBUG = true
fun i(message: String) {
if (DEBUG) Log.i(TAG, message)
}
fun w(message: String) {
if (DEBUG) Log.w(TAG, message)
}
fun e(message: String) {
if (DEBUG) Log.e(TAG, message)
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="open_initialize" translatable="false">com.open.initialize</string>
</resources>
3.4 使用步骤
class TestInitializer: Initializer {
override fun initialize(context: Context) {
SamplesLogger.i("Initializer Test initialize: ")
}
override fun dependencies(): List<Class<out Initializer>> {
SamplesLogger.i("Initializer Test dependencies: ")
return emptyList()
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx">
<application
android:name=".MyApplication"
...>
<meta-data
android:name="xxx.TestInitializer"
android:value="com.open.initialize" />
</application>
</manifest>
class MyApplication: Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
}
override fun onCreate() {
super.onCreate()
AppInitializer.initialize(this)
}
}
4 Jetpack App Startup 方案进行初始化
4.1 概念
Android Jetpack 提供的 App Startup 方案有点像 ContentProvider 中初始化 和 Initializer 中初始化 方案的结合体,相对来说属于更优的实现(推荐)。
Jetpack App Startup 初始化流程图:

4.2 优缺点
- 优点:使用
App StartUp 框架,可以简化启动序列并显式设置初始化依赖顺序,在简单、高效这方面,App Startup 基本满足需求。
- 缺点:
App Startup 框架的不足也是因为它太简单了,提供的特性太过简单,往往并不能完美契合商业化需求。例如以下特性 App Startup 就无法满足:
- 1)缺乏异步等待:同步等待指的是在当前线程先初始化所依赖的组件,再初始化当前组件,
App Startup 是支持的,但是异步等待就不支持了。举个例子,所依赖的组件需要执行一个耗时的异步任务才能完成初始化,那么 App Startup 就无法等待异步任务返回。
- 2)缺乏依赖回调:当前组件所依赖的组件初始化完成后,未发出回调。
4.3 使用步骤:
4.3.1 dependencies 配置
dependencies {
implementation "androidx.startup:startup-runtime:1.1.1"
}
4.3.2 实现 Initializer 接口
class TestInitializer: Initializer<Unit> {
override fun create(context: Context) {
SamplesLogger.i("Startup Test onCreate: ")
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
SamplesLogger.i("Startup Test dependencies: ")
return mutableListOf()
}
}
4.3.3 自动初始化
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx">
<application
...>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="xxx.TestInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>
- 注意点:
- 1)组件名必须是
androidx.startup.InitializationProvider;
- 2)需要声明
android:exported="false",以限制其他应用访问此组件;
- 3)要求
android:authorities 在整个手机唯一,通常使用 ${applicationId} 作为前缀;
- 4)需要声明
tools:node="merge",确保 manifest merger tool 能够正确解析冲突的节点;
- 5)
meta-data name 为组件的 Initializer 实现类全限定名,value 为 androidx.startup。
4.3.4 手动初始化
- 在组件需要进行懒加载时(耗时任务),可以进行手动初始化。需要手动初始化的
Initializer 不需要在 AndroidManifest 中进行声明,也不应该被其它组件依赖。调用以下方即可进行手动初始化:
AppInitializer.getInstance(context).initializeComponent(TestInitializer::class.java)
- 注意事项:
App Startup 中会缓存初始化后的结果,重复调用 initializeComponent() 不会导致重复初始化。
4.3.5 取消自动化
- 假如有些库已经配置了自动初始化,而我们又希望进行懒加载时,就需要利用
manifest merger tool 的合并规则来移除这个库对应的 Initializer。具体如下:
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx">
<application
...>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="xxx.TestInitializer"
tools:node="remove" />
</provider>
</application>
</manifest>
4.3.6 禁止所有的自动初始化
- 假如需要禁止
App Startup 自动初始化,同样也需要利用 manifest merger tool 的合并规则:
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx">
<application
...>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
</manifest>
5、Android Startup 方案进行初始化
5.1 概念
Android Startup 提供一种在应用启动时能够更加简单、高效的方式来初始化组件。开发人员可以使用 Android Startup 来简化启动序列,并显式地设置初始化顺序与组件之间的依赖关系。 与此同时 Android Startup 支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。
Android Startup 初始化流程图:

5.2 优缺点
- 优点:使用
Andorid StartUp 框架,可以简化启动序列并显式设置初始化依赖顺序,且支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。
- 缺点:稳定性怀疑,没有进行验证。
5.3 使用步骤:
5.3.1 dependencies 配置
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.idisfkj:android-startup:1.1.0'
}
5.3.2 实现 AndroidStartup 接口
class TestInitializer: AndroidStartup<String>() {
override fun callCreateOnMainThread(): Boolean = true
override fun waitOnMainThread(): Boolean = false
override fun create(context: Context): String? {
SamplesLogger.i("Android Startup Test onCreate: ")
return this.javaClass.simpleName
}
override fun dependenciesByName(): List<String>? {
SamplesLogger.i("Android Startup Test dependenciesByName: ")
return super.dependenciesByName()
}
}
- 注意:
dependenciesByName() 方法会被回调多次。
5.3.3 自动初始化
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx">
<application
...>
<provider
android:name="com.rousetime.android_startup.provider.StartupProvider"
android:authorities="${applicationId}.android_startup"
android:exported="false">
<meta-data
android:name="xxx.TestInitializer"
android:value="android.startup" />
</provider>
</application>
</manifest>
5.3.4 手动初始化
StartupManager.Builder()
.addStartup(TestInitializer())
.build(this)
.start()
.await()
5.3.5 更多请参考
6、上述四种方案对比
- 基于上述四种方案,简单写了一个
Demo 示例,以 10 为基数,然后分别冷启动 5 次,得出下述数据,仅供参考。
- 查看启动的时间,其对应的
adb 命令:
adb shell am start -W -n 包名/类名
6.1 ContentProvider 方案进行初始化时间对照表
| 类型 | TotalTime | WaitTime | MethodTime |
|---|
| 单次 | 386 | 388 | 22 |
| 单次 | 405 | 408 | 24 |
| 单次 | 388 | 390 | 23 |
| 单次 | 388 | 390 | 24 |
| 单次 | 397 | 400 | 25 |
| 平均 | 392.8 | 395.2 | 23.6 |
6.2 Initializer 方案进行初始化时间对照表
| 类型 | TotalTime | WaitTime | MethodTime |
|---|
| 单次 | 444 | 446 | 25 |
| 单次 | 452 | 454 | 25 |
| 单次 | 455 | 457 | 26 |
| 单次 | 433 | 435 | 25 |
| 单次 | 425 | 428 | 23 |
| 平均 | 441.8 | 444 | 24.8 |
6.3 Jetpack App Startup 方案进行初始化时间对照表
| 类型 | TotalTime | WaitTime | MethodTime |
|---|
| 单次 | 395 | 397 | 22 |
| 单次 | 385 | 387 | 23 |
| 单次 | 389 | 391 | 21 |
| 单次 | 394 | 396 | 23 |
| 单次 | 374 | 377 | 20 |
| 平均 | 387.4 | 389.6 | 21.8 |
6.4 Andorid Startup 方案进行初始化时间对照表
| 类型 | TotalTime | WaitTime | MethodTime |
|---|
| 单次 | 449 | 451 | 27 |
| 单次 | 431 | 433 | 27 |
| 单次 | 430 | 431 | 28 |
| 单次 | 432 | 434 | 27 |
| 单次 | 435 | 437 | 27 |
| 平均 | 435.4 | 437.2 | 27.2 |
6.5 对比总结
- 通过对比上述四种方案,其实都是有其存在的价值,具体取决于当前你的使用场景,然后根据场景选取不同的实现方案。
7 参考资料