我為何棄用Jetpack的App Startup?

前言

最近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è)ContentProviderApp 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í)行完畢。

例如,你還定義了SampleThirdStartupSampleFourthStartup

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>

你不需要將SampleFirstStartupSampleSecondStartupSampleThirdStartup添加到<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 and LoggerLevel.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ì)很頻繁,但一旦更新必定是純干貨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。