在看Jetpack的官網(wǎng)時(shí)焚挠,發(fā)現(xiàn)在Jetpack中新加了一個(gè)App Startup組件膏萧,查了一下是最近和Hilt、Paging 3一起更新的蝌衔。
為什么需要App Startup呢榛泛?
在我們實(shí)際的開發(fā)工作中,一些第三方庫需要在App啟動(dòng)的時(shí)候初始化并不少見噩斟,比如WorkManager和LifeCycle在App啟動(dòng)時(shí)通過ContentProvider進(jìn)行初始化曹锨。通過ContentProvider,一旦App冷啟動(dòng)后剃允,在調(diào)用Application.onCreate( )之前沛简,ContentProvider就可以自動(dòng)執(zhí)行初始化齐鲤。
App Startup是什么?
Google定義:App Startup這個(gè)庫在App啟動(dòng)的時(shí)候提供了一個(gè)直接椒楣、高效的方式來初始化組件给郊。所有的library開發(fā)者和app開發(fā)者能夠使用App Startup這個(gè)組件來簡化啟動(dòng)順序并且顯示地設(shè)置初始化順序。通過App Startup這個(gè)組件撒顿,您無需為需要初始化的每個(gè)組件定義單獨(dú)的content providers丑罪,而是允許通過共享一個(gè)content provider來定義組件初始化器,從而提高應(yīng)用的啟動(dòng)速度.
由上可知凤壁,App Startup提供了一個(gè)ContentProvider來完成項(xiàng)目需要的一些組件的初始化吩屹,避免每個(gè)第三方的庫(比如友盟統(tǒng)計(jì)、埋點(diǎn)等)單獨(dú)通過ContentProvider 進(jìn)行初始化拧抖。我們可以通過通過App Startup這個(gè)組件將所有第三方需要在Application中初始化的一些庫都通過ContentProvider來初始化煤搜,有點(diǎn)將第三方庫初始化這個(gè)過程進(jìn)行了封裝的意味。
如何使用App Startup唧席?
我們可以通過定義組件初始化器完成組件的初始化擦盾,那么如何定義組件初始化器呢?Android為我們提供了Initializer接口淌哟,通過實(shí)現(xiàn)接口并實(shí)現(xiàn)接口中的兩個(gè)方法就可以實(shí)現(xiàn)組件初始化器的定義了迹卢。
我們來看下面這兩個(gè)方法:
create() : 包含了初始化組件,并且返回T的實(shí)例的所有必要操作徒仓;
dependencies() : 此方法返回一個(gè)初始化程序依賴的其他Initializer對(duì)象的列表腐碱。可以使用此方法來控制應(yīng)用程序啟動(dòng)時(shí)初始化的順序掉弛。
在沒有使用App Startup的時(shí)候症见,如何保證content providers的初始化順序呢?其實(shí)很簡單殃饿,在配置清單中將先啟動(dòng)的content provider的標(biāo)簽放在前面即可谋作。
下面看下如何進(jìn)行初始化。
通過App Startup來運(yùn)行依賴項(xiàng)的初始化有兩種方式:
自動(dòng)初始化(automatic initialization)
手動(dòng)初始化(manually initialization)
無論是自動(dòng)初始化還是手動(dòng)初始化都需要在app或者library中的build.gradle文件中添加如下依賴:
dependencies {
implementation " androidx. startup:star tup- runtime:1.0.0-alpha01"
}
實(shí)現(xiàn)自動(dòng)初始化
假設(shè)APP依賴了WorkManager乎芳,并且需要在程序一開始啟動(dòng)時(shí)就初始化WorkManager遵蚜,定義一個(gè)WorkManagerInitializer類并且實(shí)現(xiàn)Initializer接口:
// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
override fun create(context: Context): WorkManager {
val configuration = Configuration.Builder().build()
WorkManager.nitialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
//No dependencies on other l ibraries.
return emptyList()
}
}
如圖中所示,dependencies()方法返回了一個(gè)空列表奈惑,意思是我WorkManager實(shí)例化誰也不需要依賴谬晕,我自己個(gè)就能行。
假設(shè)我們的應(yīng)用依賴了另一個(gè)叫做ExampleLogger的庫携取,這個(gè)庫依賴于WorkManager。這也就意味著帮孔,初始化這個(gè)庫必須先確保WorkManager的實(shí)例已經(jīng)被初始化了才可以雷滋。那么如何做呢不撑?我們看下面代碼:
// Initializes ExampleLogger ,
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
override fun create(context: Context): ExampleLogger {
// WorkManager.getInstance() is non-null only after
// WorkManager is initialized .
return ExampleLogger(WorkManager.getInstance(context))
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// Defines a dependency on Wor kManagerInitializer so it can be
// initialized after WorkManager is initialized.
return listOf(WorkManagerInitializer::class.java)
}
}
代碼中定義了一個(gè)ExampleLoggerInitializer類并且實(shí)現(xiàn)了Initializer接口。這個(gè)時(shí)候我們看到dependencies()方法返回的就不是空列表了晤斩,而是包含了WorkManagerInitializer的一個(gè)列表焕檬,這樣ExampleLogger要想初始化,必須先初始化WorkManager澳泵。
提示:如果App中之前使用content providers來初始化應(yīng)用程序中的組件实愚,請(qǐng)確保使用App Startup時(shí)刪除這些content providers
App Startup包含了一個(gè)名為InitializationProvider的特殊的content provider,它用來找到并且調(diào)用你的組件初始化器兔辅。那么這個(gè)過程是什么樣的呢腊敲?
首先,通過檢查InitializationProvider清單標(biāo)簽下的標(biāo)簽维苔,找到組件初始化器碰辅;
App Startup調(diào)用它找到的所有組件初始化器的dependencies()方法。
這就意味著如果想要讓App Startup找到組件初始化器介时,必須滿足下面的一個(gè)條件:
組件初始化器在InitializationProvider清單標(biāo)簽下配置了相應(yīng)的標(biāo)簽没宾;
組件初始化器在一個(gè)已被找到的組件初始化器的dependencies()方法中被列出;
從上面寫過的WorkManagerInitializer和ExampleLoggerInitializer這兩個(gè)例子中來說沸柔,為了確保初始化器能被實(shí)現(xiàn)循衰,需要在清單文件中配置如下代碼:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleLoggerInitializer discoverable. -->
<meta-data
android:name="com.example.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
我們只配置了ExampleLoggerInitializer的標(biāo)簽,因?yàn)樗且蕾囉赪orkManager的褐澎,并且滿足了第二條:dependencies()方法中列出了它依賴于WorkManager会钝,如果ExampleLoggerInitializer能被找到,那么WorkManagerInitializer一定也能被找到乱凿。
tools:node="merge"屬性是為了確保清單合并工具可能造成的沖突問題
App Startup庫包含了一系列的lint規(guī)則顽素,通過這些規(guī)則,你能夠檢查是否正確定義了組件初始化器徒蟆。你可以在終端通過./gradlew :app:lintDebug命令執(zhí)行l(wèi)int檢查胁出。
實(shí)現(xiàn)手動(dòng)初始化
通常來說,當(dāng)你使用App Startup時(shí)段审,InitializationProvider對(duì)象就會(huì)使用AppInitializer在App啟動(dòng)時(shí)來自動(dòng)尋找并且運(yùn)行初始化器全蝶。然而,你也可以直接調(diào)用寺枉。AppInitializer來手動(dòng)初始化不需要在啟動(dòng)時(shí)就調(diào)用的組件初始化器抑淫。這個(gè)操作被稱作懶初始化,它能夠減少程序啟動(dòng)的時(shí)間姥闪。要想實(shí)現(xiàn)手動(dòng)初始化始苇,必須先禁止掉你想要手動(dòng)初始化的組件的自動(dòng)初始化功能。
為了禁止掉單個(gè)組件的自動(dòng)初始化功能筐喳,可以在清單文件中移除那個(gè)組件的標(biāo)簽催式,舉例來說函喉,我們想禁止ExampleLogger的自動(dòng)初始化:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleLoggerInitializer discoverable. -->
<meta-data
android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
</provider>
使用tools:node="remove"而不是直接移除這個(gè)標(biāo)簽是為了確保清單合并工具能夠移除所有合并文件的這個(gè)標(biāo)簽。
提示: 禁用組件的自動(dòng)初始化也會(huì)禁用該組件的依賴項(xiàng)的自動(dòng)初始化荣月,比如我禁用了ExampleLogger管呵,而ExampleLogger依賴了WorkManager,那么WorkManager也不會(huì)自動(dòng)初始化了哺窄。
我們現(xiàn)在知道如何禁止單個(gè)組件的自動(dòng)初始化捐下,那么如何禁止全部組件的自動(dòng)初始化,轉(zhuǎn)而手動(dòng)初始化呢萌业?
<provider
android:name="androidx.startup.InitializationProvider "
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
禁用自動(dòng)初始化后坷襟,你可以使用AppInitializer手動(dòng)初始化組件和它的依賴。
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
通過上述代碼咽白,App Startup也對(duì)WorkManager進(jìn)行了初始化啤握,因?yàn)镋xampleLogger依賴了WorkManager。
源碼分析
App Startup包中代碼并不多晶框,只有五個(gè)類
其中最核心的類就是InitializationProvider排抬,它是繼承了ContentProvider,這樣我們就懂了授段,在onCreate()方法中蹲蒲,可以看到它其實(shí)是調(diào)用了AppInitializer這個(gè)類中的discoverAndInitialize()方法,我們簡單看下這個(gè)代碼:
@NonNull
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
<T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
synchronized (sLock) {
boolean isTracingEnabled = Trace.isEnabled();
try {
if (isTracingEnabled) {
// Use the simpleName here because section names would get too big otherwise.
Trace.beginSection(component.getSimpleName());
}
if (initializing.contains(component)) {
String message = String.format(
"Cannot initialize %s. Cycle detected.", component.getName()
);
throw new IllegalStateException(message);
}
Object result;
if (!mInitialized.containsKey(component)) {
initializing.add(component);
try {
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
List<Class<? extends Initializer<?>>> dependencies =
initializer.dependencies();
if (!dependencies.isEmpty()) {
for (Class<? extends Initializer<?>> clazz : dependencies) {
if (!mInitialized.containsKey(clazz)) {
doInitialize(clazz, initializing);
}
}
}
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initializing %s", component.getName()));
}
result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
initializing.remove(component);
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
result = mInitialized.get(component);
}
return (T) result;
} finally {
Trace.endSection();
}
}
}
代碼很簡單侵贵,就是解析出來metadata中的數(shù)據(jù)届搁,然后遍歷metadata拿到配置的初始化器,然后調(diào)用每個(gè)初始化器的初始化方法窍育,也就是doInitialize()方法卡睦。接下來再看下這個(gè)方法:
@NonNull
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
<T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
synchronized (sLock) {
boolean isTracingEnabled = Trace.isEnabled();
try {
if (isTracingEnabled) {
// Use the simpleName here because section names would get too big otherwise.
Trace.beginSection(component.getSimpleName());
}
if (initializing.contains(component)) {
String message = String.format(
"Cannot initialize %s. Cycle detected.", component.getName()
);
throw new IllegalStateException(message);
}
Object result;
if (!mInitialized.containsKey(component)) {
initializing.add(component);
try {
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
List<Class<? extends Initializer<?>>> dependencies =
initializer.dependencies();
if (!dependencies.isEmpty()) {
for (Class<? extends Initializer<?>> clazz : dependencies) {
if (!mInitialized.containsKey(clazz)) {
doInitialize(clazz, initializing);
}
}
}
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initializing %s", component.getName()));
}
result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
initializing.remove(component);
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
result = mInitialized.get(component);
}
return (T) result;
} finally {
Trace.endSection();
}
}
}
可以看到在執(zhí)行初始化的時(shí)候,先判斷了是否有依賴項(xiàng)漱抓,有的話先執(zhí)行依賴項(xiàng)的初始化表锻。
掘金鏈接 Android Jetpack組件之App Startup