前言
最近Jetpack
又添加了新成員App Startup
,官方聲明這是一個(gè)在Android應(yīng)用啟動(dòng)時(shí),針對(duì)初始化組件進(jìn)行優(yōu)化的依賴庫(kù)。本人第一次聽到后非常高興,因?yàn)樽约贺?fù)責(zé)的項(xiàng)目在啟動(dòng)時(shí)需要初始化的東西實(shí)在是太多,而且有點(diǎn)雜亂無章,都耦合在一起了。對(duì)于可以異步初始化的組件也沒有進(jìn)行異步處理,而對(duì)于已經(jīng)處理過的異步組件它們之間的依賴關(guān)系或者多個(gè)異步之后的統(tǒng)一邏輯處理也沒有一個(gè)很好的統(tǒng)一規(guī)范。所以針對(duì)這種情況早就想找個(gè)方案來優(yōu)化了,這次終于等到了App Startup
。
但是,當(dāng)我元?dú)鉂M滿的去查看官方文檔時(shí),并沒有找到預(yù)想中的結(jié)果。官方文檔中只提到了可以通過一個(gè)ContentProvider
來統(tǒng)一管理需要初始化的組件,同時(shí)通過dependencies()
方法解決組件間初始化的依賴順序,然后呢?沒了?等等官方你是不是漏了什么?
異步處理呢?雖然我們可以在create()
方法中手動(dòng)創(chuàng)建子線程進(jìn)行異步任務(wù),但一個(gè)異步任務(wù)依賴另一個(gè)異步任務(wù)又該如何處理呢?多個(gè)異步任務(wù)完成之后,統(tǒng)一邏輯處理又在哪里呢?依賴任務(wù)完成后的回調(diào)又在哪里?亦或者是依賴任務(wù)完成后的通知?
我有點(diǎn)不相信,所以又去查看了App Startup
的源碼,源碼很簡(jiǎn)單,也就幾個(gè)文件,最后發(fā)現(xiàn)確實(shí)只支持上面的那幾個(gè)功能。
如果你的項(xiàng)目都是同步初始化的話,并且使用到了多個(gè)ContentProvider
,App Startup
可能有一定的優(yōu)化空間,畢竟統(tǒng)一到了一個(gè)ContentProvider
中,同時(shí)支持了簡(jiǎn)單的順序依賴。
值得一提的是,App Startup
中只提供了使用反射來獲取初始化的組件實(shí)例,這對(duì)于一些沒有過多依賴的初始化項(xiàng)目來說,盲目使用App Startup
來優(yōu)化是否會(huì)對(duì)啟動(dòng)速度進(jìn)一步造成影響呢?
所以細(xì)想了一下,不禁讓我想起了三國(guó)時(shí)的一個(gè)名詞:雞肋
。食之無味,棄之可惜。
但最終我還是決定放棄使用它。
放棄之后有點(diǎn)不甘心,可能更多的是它沒有解決我當(dāng)前的項(xiàng)目場(chǎng)景。都分析了這么多,源碼都看了,總不能半途而廢吧,所以自己咬咬牙再補(bǔ)充一點(diǎn)唄。
所以堅(jiān)持一下,就有了下面這個(gè)庫(kù),App Startup
的進(jìn)階版Android Startup
。
Android Startup
Android Startup提供一種在應(yīng)用啟動(dòng)時(shí)能夠更加簡(jiǎn)單、高效的方式來初始化組件。開發(fā)人員可以使用Android Startup
來簡(jiǎn)化啟動(dòng)序列,并顯式地設(shè)置初始化順序與組件之間的依賴關(guān)系。
與此同時(shí),Android Startup
支持同步與異步等待,并通過有向無環(huán)圖拓?fù)渑判?/a>的方式來保證內(nèi)部依賴組件的初始化順序。
由于Android Startup
是基于App Startup
進(jìn)行的擴(kuò)展,所以它的使用方式與App Startup
有點(diǎn)類似,該有的功能基本上都有,同時(shí)額外還附加其它功能。
下面是一張與google的App Startup功能對(duì)比的表格。
指標(biāo) | App Startup | Android Startup |
---|---|---|
手動(dòng)配置 | ? | ? |
自動(dòng)配置 | ? | ? |
依賴支持 | ? | ? |
閉環(huán)處理 | ? | ? |
線程控制 | ? | ? |
異步等待 | ? | ? |
依賴回調(diào) | ? | ? |
拓?fù)鋬?yōu)化 | ? | ? |
下面簡(jiǎn)單介紹一下Android Startup
的使用。
添加依賴
將下面的依賴添加到build.gradle
文件中:
dependencies {
implementation 'com.rousetime.android:android-startup:1.0.1'
}
依賴版本的更新信息: Release
快速使用
android-startup提供了兩種使用方式,在使用之前需要先定義初始化的組件。
定義初始化的組件
每一個(gè)初始化的組件都需要實(shí)現(xiàn)AndroidStartup<T>
抽象類,它實(shí)現(xiàn)了Startup<T>
接口,它主要有以下四個(gè)抽象方法:
callCreateOnMainThread(): Boolean
用來控制create()
方法調(diào)時(shí)所在的線程,返回true代表在主線程執(zhí)行。waitOnMainThread(): Boolean
用來控制當(dāng)前初始化的組件是否需要在主線程進(jìn)行等待其完成。如果返回true,將在主線程等待,并且阻塞主線程。create(): T?
組件初始化方法,執(zhí)行需要處理的初始化邏輯,支持返回一個(gè)T
類型的實(shí)例。dependencies(): List<Class<out Startup<*>>>?
返回Startup<*>
類型的list集合。用來表示當(dāng)前組件在執(zhí)行之前需要依賴的組件。
例如,下面定義一個(gè)SampleFirstStartup
類來實(shí)現(xiàn)AndroidStartup<String>
抽象類:
class SampleFirstStartup : AndroidStartup<String>() {
override fun callCreateOnMainThread(): Boolean = true
override fun waitOnMainThread(): Boolean = false
override fun create(context: Context): String? {
// todo something
return this.javaClass.simpleName
}
override fun dependencies(): List<Class<out Startup<*>>>? {
return null
}
}
因?yàn)?code>SampleFirstStartup在執(zhí)行之前不需要依賴其它組件,所以它的dependencies()
方法可以返回空,同時(shí)它會(huì)在主線程中執(zhí)行。
注意:?雖然
waitOnMainThread()
返回了false
,但由于它是在主線程中執(zhí)行,而主線程默認(rèn)是阻塞的,所以callCreateOnMainThread()
返回true
時(shí),該方法設(shè)置將失效。
假設(shè)你還需要定義SampleSecondStartup
,它依賴于SampleFirstStartup
。這意味著在執(zhí)行SampleSecondStartup
之前SampleFirstStartup
必須先執(zhí)行完畢。
class SampleSecondStartup : AndroidStartup<Boolean>() {
override fun callCreateOnMainThread(): Boolean = false
override fun waitOnMainThread(): Boolean = true
override fun create(context: Context): Boolean {
// 模仿執(zhí)行耗時(shí)
Thread.sleep(5000)
return true
}
override fun dependencies(): List<Class<out Startup<*>>>? {
return listOf(SampleFirstStartup::class.java)
}
}
在dependencies()
方法中返回了SampleFirstStartup
,所以它能保證SampleFirstStartup
優(yōu)先執(zhí)行完畢。
它會(huì)在子線程中執(zhí)行,但由于waitOnMainThread()
返回了true
,所以主線程會(huì)阻塞等待直到它執(zhí)行完畢。
例如,你還定義了SampleThirdStartup與SampleFourthStartup
Manifest中自動(dòng)配置
第一種初始化方法是在Manifest中進(jìn)行自動(dòng)配置。
在Android Startup中提供了StartupProvider
類,它是一個(gè)特殊的content provider,提供自動(dòng)識(shí)別在manifest中配置的初始化組件。
為了讓其能夠自動(dòng)識(shí)別,需要在StartupProvider
中定義<meta-data>
標(biāo)簽。其中的name
為定義的組件類,value
的值對(duì)應(yīng)為android.startup
。
<provider
android:name="com.rousetime.android_startup.provider.StartupProvider"
android:authorities="${applicationId}.android_startup"
android:exported="false">
<meta-data
android:name="com.rousetime.sample.startup.SampleFourthStartup"
android:value="android.startup" />
</provider>
你不需要將SampleFirstStartup
、SampleSecondStartup
與SampleThirdStartup
添加到<meta-data>
標(biāo)簽中。這是因?yàn)樵?code>SampleFourthStartup中,它的dependencies()
中依賴了這些組件。StartupProvider
會(huì)自動(dòng)識(shí)別已經(jīng)聲明的組件中依賴的其它組件。
Application中手動(dòng)配置
第二種初始化方法是在Application進(jìn)行手動(dòng)配置。
手動(dòng)初始化需要使用到StartupManager.Builder()
。
例如,如下代碼使用StartupManager.Builder()
進(jìn)行初始化配置。
class SampleApplication : Application() {
override fun onCreate() {
super.onCreate()
StartupManager.Builder()
.addStartup(SampleFirstStartup())
.addStartup(SampleSecondStartup())
.addStartup(SampleThirdStartup())
.addStartup(SampleFourthStartup())
.build(this)
.start()
.await()
}
}
如果你開啟了日志輸出,然后運(yùn)行項(xiàng)目之后,將會(huì)在控制臺(tái)中輸出經(jīng)過拓?fù)渑判騼?yōu)化之后的初始化組件的執(zhí)行順序。
D/StartupTrack: TopologySort result:
================================================ ordering start ================================================
order [0] Class: SampleFirstStartup => Dependencies size: 0 => callCreateOnMainThread: true => waitOnMainThread: false
order [1] Class: SampleSecondStartup => Dependencies size: 1 => callCreateOnMainThread: false => waitOnMainThread: true
order [2] Class: SampleThirdStartup => Dependencies size: 2 => callCreateOnMainThread: false => waitOnMainThread: false
order [3] Class: SampleFourthStartup => Dependencies size: 3 => callCreateOnMainThread: false => waitOnMainThread: false
================================================ ordering end ================================================
完整的代碼實(shí)例,你可以通過查看app獲取。
更多
可選配置
LoggerLevel
: 控制Android Startup中的日志輸出,可選值包括LoggerLevel.NONE
,LoggerLevel.ERROR
andLoggerLevel.DEBUG
。AwaitTimeout
: 控制Android Startup中主線程的超時(shí)等待時(shí)間,即阻塞的最長(zhǎng)時(shí)間。
Manifest中配置
使用這些配置,你需要定義一個(gè)類去實(shí)現(xiàn)StartupProviderConfig
接口,并且實(shí)現(xiàn)它的對(duì)應(yīng)方法。
class SampleStartupProviderConfig : StartupProviderConfig {
override fun getConfig(): StartupConfig =
StartupConfig.Builder()
.setLoggerLevel(LoggerLevel.DEBUG)
.setAwaitTimeout(12000L)
.build()
}
與此同時(shí),你還需要在manifest中進(jìn)行配置StartupProviderConfig
。
<provider
android:name="com.rousetime.android_startup.provider.StartupProvider"
android:authorities="${applicationId}.android_startup"
android:exported="false">
<meta-data
android:name="com.rousetime.sample.startup.SampleStartupProviderConfig"
android:value="android.startup.provider.config" />
</provider>
經(jīng)過上面的配置,StartupProvider
會(huì)自動(dòng)解析SampleStartupProviderConfig
。
Application中配置
在Application需要借助StartupManager.Builder()
進(jìn)行配置。
override fun onCreate() {
super.onCreate()
val config = StartupConfig.Builder()
.setLoggerLevel(LoggerLevel.DEBUG)
.setAwaitTimeout(12000L)
.build()
StartupManager.Builder()
.setConfig(config)
...
.build(this)
.start()
.await()
}
方法
AndroidStartup
createExecutor(): Executor
: 如果定義的組件沒有運(yùn)行在主線程,那么可以通過該方法進(jìn)行控制運(yùn)行的子線程。onDependenciesCompleted(startup: Startup<*>, result: Any?)
: 該方法會(huì)在每一個(gè)依賴執(zhí)行完畢之后進(jìn)行回調(diào)。
實(shí)戰(zhàn)測(cè)試
AwesomeGithub中使用了Android Startup,優(yōu)化配置的初始化時(shí)間與組件化開發(fā)的配置注入時(shí)機(jī),使用前與使用后時(shí)間對(duì)比:
狀態(tài) | 啟動(dòng)頁(yè)面 | 消耗時(shí)間 |
---|---|---|
使用前 | WelcomeActivity | 420ms |
使用后 | WelcomeActivity | 333ms |
AwesomeGithub
AwesomeGithub是基于Github的客戶端,純練習(xí)項(xiàng)目,支持組件化開發(fā),支持賬戶密碼與認(rèn)證登陸。使用Kotlin語(yǔ)言進(jìn)行開發(fā),項(xiàng)目架構(gòu)是基于JetPack&DataBinding的MVVM;項(xiàng)目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger與Hilt等流行開源技術(shù)。
除了Android原生版本,還有基于Flutter的跨平臺(tái)版本flutter_github。
如果你喜歡我的文章,你可以關(guān)注我的微信公眾號(hào):【Android補(bǔ)給站】或者掃描下方二維碼進(jìn)行關(guān)注,當(dāng)然你也可以直接關(guān)注當(dāng)前網(wǎng)站的帳號(hào)。主要區(qū)別就是微信能夠更方法互動(dòng)。
公眾號(hào)更新不會(huì)很頻繁,但一旦更新必定是純干貨。