
點(diǎn)贊關(guān)注,不再迷路,你的支持對(duì)我意義重大!
?? Hi,我是丑丑。本文「Android 路線」| 導(dǎo)讀 —— 從零到無(wú)窮大 已收錄。這里有 Android 進(jìn)階成長(zhǎng)路線筆記 & 博客,歡迎跟著彭丑丑一起成長(zhǎng)。(聯(lián)系方式在 GitHub)
前言
- 2020 年 10 月 28 日,JetPack | App Startup 1.0.0 終于迎來(lái)正式發(fā)布,正好最近在總結(jié)組件化架構(gòu)專題,所以也專門學(xué)習(xí)下 App Startup 的工作原理;
- 在這篇文章里,我將帶你總結(jié) App Startup 的使用方法 & 實(shí)現(xiàn)原理 & 源碼分析。如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。
目錄

前置知識(shí)
這篇文章的內(nèi)容會(huì)涉及以下前置 / 相關(guān)知識(shí),貼心的我都幫你準(zhǔn)備好了,請(qǐng)享用~
- ContentProvider 組件解析: Android | ContentProvider 的工作過(guò)程
1. 為什么要使用 App Startup?
這一節(jié),我們來(lái)討論為什么要使用 App Startup ,也就是 App Startup 解決了什么問(wèn)題。
在我之前寫(xiě)過(guò)的一篇文章里,我曾經(jīng)講過(guò)一種 基于 ContentProvider 啟動(dòng)機(jī)制實(shí)現(xiàn)的無(wú)侵入獲取 Contex 的方法:《Android | 使用 ContentProvider 無(wú)侵入獲取 Context》。在這里我簡(jiǎn)單復(fù)述一下:
- 1、在二方庫(kù)或三方庫(kù)中,經(jīng)常需要獲取 Context 進(jìn)行初始化;
- 2、因?yàn)?ContentProvider 會(huì)在應(yīng)用啟動(dòng)的時(shí)候初始化,所以很多庫(kù)都利用了 ContentProvider 的啟動(dòng)機(jī)制,在
Application#onCreate()中進(jìn)行初始化,例如 LeakCanary 2.4:
AppWatcherInstaller.java
internal sealed class AppWatcherInstaller : ContentProvider() {
internal class MainProcess : AppWatcherInstaller()
internal class LeakCanaryProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
// 其他方法直接 return
}
- 3、這種做法的風(fēng)險(xiǎn)是 ContentProvider 過(guò)多,啟動(dòng)過(guò)多的 ContentProvider 會(huì)增加應(yīng)用的啟動(dòng)時(shí)間。

- 4、AppStartup 的做法是:合并所有用于初始化的ContentProvider ,減少創(chuàng)建 ContentProvider,并提供全局管理。

2. 使用步驟
這一節(jié),我們來(lái)總結(jié) App Startup 的使用步驟,依賴如下:
build.gradle
implementation "androidx.startup:startup-runtime:1.0.0"
2.1 為組件實(shí)現(xiàn) Initializer 接口
Initializer接口是 Startup 封裝的組件接口,用于指定組件的初始化邏輯和初始化順序(也就是依賴關(guān)系)。
Initializer.java
public interface Initializer<T> {
1、初始化操作,返回的初始化結(jié)果將被緩存
@NonNull
T create(@NonNull Context context);
2、依賴關(guān)系,返回值是一個(gè)依賴組件的列表
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
}
- 1、
create(...)初始化操作: 返回的初始化結(jié)果將被緩存,其中context參數(shù)是 Application; - 2、
dependencies()依賴關(guān)系: 返回值是一個(gè)依賴組件的列表,如果不需要依賴于其它組件,返回一個(gè)空列表。App Startup 在初始化當(dāng)前組件時(shí),會(huì)保證所依賴的組件已經(jīng)完成初始化。
2.2 自動(dòng)初始化
前面提到,App Startup 合并所有用于初始化的 ContentProvider,合并后的 ContentProvider 就是 InitializationProvider,我們需要在AndroidManifest中進(jìn)行聲明,例如:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
要點(diǎn)如下:
- 1、組件名必須是
androidx.startup.InitializationProvider; - 2、需要聲明
android:exported="false",以限制其他應(yīng)用訪問(wèn)此組件; - 3、要求
android:authorities在整個(gè)手機(jī)唯一,通常使用${applicationId}作為前綴; - 4、需要聲明
tools:node="merge",確保manifest merger tool能夠正確解析沖突的節(jié)點(diǎn); - 5、meta-data
name為組件的 Initializer 實(shí)現(xiàn)類全限定名,value為androidx.startup。
提示: 為什么要將
androidx.startup設(shè)置為value,而不是name?因?yàn)殒I值對(duì)中,name是唯一的,而value是允許重復(fù)的。
關(guān)于AndroidManifest中聲明組件后,App Startup 是如何自動(dòng)執(zhí)行初始化的,我在 第 3 節(jié)說(shuō)。
2.3 手動(dòng)初始化
在組件需要進(jìn)行懶加載時(shí)(耗時(shí)任務(wù)),可以進(jìn)行手動(dòng)初始化。需要手動(dòng)初始化的 Initializer 不需要在AndroidManifest中進(jìn)行聲明,也不應(yīng)該被其它組件依賴。調(diào)用以下方即可進(jìn)行手動(dòng)初始化:
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
需要注意的是,App Startup 中會(huì)緩存初始化后的結(jié)果,重復(fù)調(diào)用initializeComponent()不會(huì)導(dǎo)致重復(fù)初始化。關(guān)于 App Startup 手動(dòng)執(zhí)行初始化部分的源碼分析,我在 第 3 節(jié)說(shuō)。
2.4 取消自動(dòng)初始化
假如有些庫(kù)已經(jīng)使用 第 2.2 節(jié) 的方法配置了自動(dòng)初始化,而我們又希望進(jìn)行懶加載時(shí),就需要利用manifest merger tool的合并規(guī)則來(lái)移除這個(gè)庫(kù)對(duì)應(yīng)的 Initializer。具體如下:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>
2.5 禁止自動(dòng)初始化
假如需要禁止 App Startup 自動(dòng)初始化,同樣也需要利用manifest merger tool的合并規(guī)則:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
3. 源碼分析
3.1 InitializationProvider 分析
前面我們提到,在AndroidManifest文件中配置的組件名必須為androidx.startup.InitializationProvider,現(xiàn)在我們來(lái)看這個(gè)類的源碼:
InitializationProvider.java
已簡(jiǎn)化
public final class InitializationProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context context = getContext();
if (context != null) {
初始化
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
@Override
public Cursor query(...) {
throw new IllegalStateException("Not allowed.");
}
@Override
public String getType(...) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public Uri insert(...) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int delete(...) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int update(...) {
throw new IllegalStateException("Not allowed.");
}
}
可以看到:
- 1、
InitializationProvider其實(shí)也是利用了 ContentProvider 的啟動(dòng)機(jī)制,在ContentProvider#onCreate(...)中執(zhí)行初始化; - 2、由于 ContentProvider 的其他方法是沒(méi)有意義的,所以都拋出了
IllegalStateException。
3.2 自動(dòng)初始化源碼分析
從一節(jié)可以看到,App Startup 在 ContentProvider 中調(diào)用了AppInitializer#discoverAndInitialize()執(zhí)行自動(dòng)初始化。AppInitializer是 App StartUp 框架的核心類,整個(gè) App Startup 框架的代碼其實(shí)非常少,其中很大部分核心代碼都在 AppInitializer 類中。
AppInitializer.java
final Set<Class<? extends Initializer<?>>> mDiscovered;
已簡(jiǎn)化
void discoverAndInitialize() {
1、獲取 androidx.startup.InitializationProvider 組件信息
ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA);
2、androidx.startup 字符串
String startup = mContext.getString(R.string.androidx_startup);
3、獲取組件信息中的 meta-data 數(shù)據(jù)
Bundle metadata = providerInfo.metaData;
4、遍歷 meta-data 數(shù)據(jù)
if (metadata != null) {
Set<Class<?>> initializing = new HashSet<>();
Set<String> keys = metadata.keySet();
for (String key : keys) {
String value = metadata.getString(key, null);
4.1 判斷 meta-data 數(shù)據(jù)中,value 為 androidx.startup 的鍵值對(duì)
if (startup.equals(value)) {
Class<?> clazz = Class.forName(key);
4.2 檢查指定的類是 Initializer 接口的實(shí)現(xiàn)類
if (Initializer.class.isAssignableFrom(clazz)) {
Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;
4.3 將 Class 添加到 mDiscovered Set 中
mDiscovered.add(component);
4.4 初始化此組件
doInitialize(component, initializing);
}
}
}
}
}
-> 4.3 mDiscovered 用于判斷組件是否已經(jīng)自動(dòng)啟動(dòng)
public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {
return mDiscovered.contains(component);
}
上面的代碼已經(jīng)非常簡(jiǎn)化了,主要關(guān)注以下幾點(diǎn):
- 1、獲取
androidx.startup.InitializationProvider組件信息(在各個(gè) Module 中聲明的組件信息,會(huì)在manifest merger tool的處理下合并); - 2、
androidx.startup字符串 - 3、獲取組件信息中的 meta-data 數(shù)據(jù)
- 4、遍歷 meta-data 數(shù)據(jù)
- 4.1 判斷 meta-data 數(shù)據(jù)中,value 為 androidx.startup 的鍵值對(duì)
- 4.2 檢查指定的類是 Initializer 接口的實(shí)現(xiàn)類
- 4.3 將 Class 添加到 mDiscovered Set 中,這將用于后續(xù) 判斷組件是否已經(jīng)自動(dòng)啟動(dòng)
- 4.4 初始化此組件
AppInitializer.java
private static final Object sLock = new Object();
緩存每個(gè)組件的初始化結(jié)果
final Map<Class<?>, Object> mInitialized;
-> 4.4 初始化此組件
已簡(jiǎn)化
<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
1、對(duì) sLock 加鎖,我后文再說(shuō)。
Object result;
2、判斷 initializing 中存在當(dāng)前組件,說(shuō)明存在循環(huán)依賴
if (initializing.contains(component)) {
String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());
throw new IllegalStateException(message);
}
3、檢查當(dāng)前組件是否已初始化
if (!mInitialized.containsKey(component)) {
3.1 當(dāng)前組件未初始化
3.1.1 記錄正在初始化
initializing.add(component);
3.1.2 通過(guò)反射實(shí)例化 Initializer 接口實(shí)現(xiàn)類
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
3.1.3 遍歷所依賴的組件
List<Class<? extends Initializer<?>>> dependencies = initializer.dependencies();
if (!dependencies.isEmpty()) {
for (Class<? extends Initializer<?>> clazz : dependencies) {
如果所依賴的組件未初始化,遞歸執(zhí)行初始化
if (!mInitialized.containsKey(clazz)) {
doInitialize(clazz, initializing); 注意:這里將 initializing 作為參數(shù)傳入
}
}
}
3.1.4 (到這里,所依賴的組件已經(jīng)初始化完成)初始化當(dāng)前組件
result = initializer.create(mContext);
3.1.5 移除正在初始化記錄
initializing.remove(component);
3.1.6 緩存初始化結(jié)果
mInitialized.put(component, result);
} else {
3.2 當(dāng)前組件已經(jīng)初始化,直接返回
result = mInitialized.get(component);
}
return (T) result;
}
上面的代碼已經(jīng)非常簡(jiǎn)化了,主要關(guān)注以下幾點(diǎn):
- 1、對(duì) sLock 加鎖,我后文再說(shuō)。
- 2、判斷 initializing 中存在當(dāng)前組件,說(shuō)明存在循環(huán)依賴(這是因?yàn)檫f歸初始化所依賴的組件時(shí),會(huì)將 initializing 作為參數(shù)傳入,如果 initializing 中存在當(dāng)前組件,說(shuō)明依賴關(guān)系形成回環(huán),如果不拋出異常,將形成無(wú)限遞歸。)
- 3、檢查當(dāng)前組件是否已初始化,如果已經(jīng)初始化過(guò),則直接返回(3.2),否則:
- 3.1.1 記錄正在初始化
- 3.1.2 通過(guò)反射實(shí)例化 Initializer 接口實(shí)現(xiàn)類
- 3.1.3 遍歷所依賴的組件,如果所依賴的組件未初始化,遞歸調(diào)用
doInitialize(...)執(zhí)行初始化 - 3.1.4 (到這里,所依賴的組件已經(jīng)初始化完成)初始化當(dāng)前組件
- 3.1.5 移除正在初始化記錄
- 3.1.6 緩存初始化結(jié)果
3.3 手動(dòng)初始化源碼分析
現(xiàn)在我們來(lái)看手動(dòng)初始化(懶加載)的源碼分析:
AppInitializer.java
public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
調(diào)用 doInitialize(...) 方法:
return doInitialize(component, new HashSet<Class<?>>());
}
其實(shí)非常簡(jiǎn)單,就是調(diào)用上一節(jié)的doInitialize(...)執(zhí)行初始化。需要注意的是,這個(gè)方法是允許在子線程調(diào)用的,換句話說(shuō),自動(dòng)初始化與手動(dòng)初始化是存在線程同步問(wèn)題的,那么 App Startup 是如何解決的呢?
還記得我們前面有一個(gè)sLock沒(méi)有說(shuō)嗎?其實(shí)它就是用來(lái)保證線程同步的鎖:
AppInitializer.java
<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
1、對(duì) sLock 加鎖
synchronized (sLock) {
...
}
}
4. 總結(jié)
優(yōu)點(diǎn):使用 App Startup 框架,可以簡(jiǎn)化啟動(dòng)序列并顯式設(shè)置初始化依賴順序,在簡(jiǎn)單、高效這方面,App Startup 基本滿足需求。
-
不足:App Startup 框架的不足也是因?yàn)樗?jiǎn)單了,提供的特性太過(guò)簡(jiǎn)單,往往并不能完美契合商業(yè)化需求。例如以下特性 App Startup 就無(wú)法滿足:
- 缺乏異步等待:同步等待指的是在當(dāng)前線程先初始化所依賴的組件,再初始化當(dāng)前組件,App Startup 是支持的,但是異步等待就不支持了。舉個(gè)例子,所依賴的組件需要執(zhí)行一個(gè)耗時(shí)的異步任務(wù)才能完成初始化,那么 App Startup 就無(wú)法等待異步任務(wù)返回;
- 缺乏依賴回調(diào):當(dāng)前組件所依賴的組件初始化完成后,未發(fā)出回調(diào)。

參考資料
- 《App Startup》 —— Android Developers
- 《合并多個(gè)清單文件》 —— Android Developers
- 《AndroidX: App Startup》 —— Husayn Hakeem 著
- 《Jetpack新成員,App Startup 一篇就懂》 —— 郭霖 著
- 《我為何棄用 Jetpack 的 App Startup?》 —— 午后一小憩 著
- 《更快!這才是我想要的 Android Startup 庫(kù)!》 —— idisfkj 著
- 《組件化:代碼隔離也難不倒組件的按序初始化》 —— leobert-lan 著
- 《從源碼看 Jetpack(5)Startup 源碼詳解》 —— 葉志陳 著
創(chuàng)作不易,你的「三連」是丑丑最大的動(dòng)力,我們下次見(jiàn)!
