我為何棄用Jetpack的App Startup?

前言

最近Jetpack又添加了新成員App Startup,官方聲明這是一個在Android應(yīng)用啟動時筋遭,針對初始化組件進(jìn)行優(yōu)化的依賴庫涤姊。本人第一次聽到后非常高興,因為自己負(fù)責(zé)的項目在啟動時需要初始化的東西實在是太多崩哩,而且有點雜亂無章,都耦合在一起了言沐。對于可以異步初始化的組件也沒有進(jìn)行異步處理邓嘹,而對于已經(jīng)處理過的異步組件它們之間的依賴關(guān)系或者多個異步之后的統(tǒng)一邏輯處理也沒有一個很好的統(tǒng)一規(guī)范。所以針對這種情況早就想找個方案來優(yōu)化了险胰,這次終于等到了App Startup吴超。

但是,當(dāng)我元氣滿滿的去查看官方文檔時鸯乃,并沒有找到預(yù)想中的結(jié)果。官方文檔中只提到了可以通過一個ContentProvider來統(tǒng)一管理需要初始化的組件,同時通過dependencies()方法解決組件間初始化的依賴順序缨睡,然后呢鸟悴?沒了?等等官方你是不是漏了什么奖年?

異步處理呢细诸?雖然我們可以在create()方法中手動創(chuàng)建子線程進(jìn)行異步任務(wù),但一個異步任務(wù)依賴另一個異步任務(wù)又該如何處理呢陋守?多個異步任務(wù)完成之后震贵,統(tǒng)一邏輯處理又在哪里呢?依賴任務(wù)完成后的回調(diào)又在哪里水评?亦或者是依賴任務(wù)完成后的通知猩系?

我有點不相信,所以又去查看了App Startup的源碼中燥,源碼很簡單寇甸,也就幾個文件,最后發(fā)現(xiàn)確實只支持上面的那幾個功能疗涉。

如果你的項目都是同步初始化的話拿霉,并且使用到了多個ContentProviderApp Startup可能有一定的優(yōu)化空間咱扣,畢竟統(tǒng)一到了一個ContentProvider中绽淘,同時支持了簡單的順序依賴。

值得一提的是闹伪,App Startup中只提供了使用反射來獲取初始化的組件實例沪铭,這對于一些沒有過多依賴的初始化項目來說,盲目使用App Startup來優(yōu)化是否會對啟動速度進(jìn)一步造成影響呢祭往?

所以細(xì)想了一下伦意,不禁讓我想起了三國時的一個名詞:雞肋。食之無味硼补,棄之可惜驮肉。

但最終我還是決定放棄使用它。

放棄之后有點不甘心已骇,可能更多的是它沒有解決我當(dāng)前的項目場景离钝。都分析了這么多,源碼都看了褪储,總不能半途而廢吧卵渴,所以自己咬咬牙再補(bǔ)充一點唄。

所以堅持一下鲤竹,就有了下面這個庫浪读,App Startup的進(jìn)階版Android Startup

Android Startup

Android Startup提供一種在應(yīng)用啟動時能夠更加簡單、高效的方式來初始化組件碘橘。開發(fā)人員可以使用Android Startup來簡化啟動序列互订,并顯式地設(shè)置初始化順序與組件之間的依賴關(guān)系。
與此同時痘拆,Android Startup支持同步與異步等待仰禽,并通過有向無環(huán)圖拓?fù)渑判?/a>的方式來保證內(nèi)部依賴組件的初始化順序。

由于Android Startup是基于App Startup進(jìn)行的擴(kuò)展纺蛆,所以它的使用方式與App Startup有點類似吐葵,該有的功能基本上都有,同時額外還附加其它功能桥氏。

下面是一張與google的App Startup功能對比的表格温峭。

指標(biāo) App Startup Android Startup
手動配置 ? ?
自動配置 ? ?
依賴支持 ? ?
閉環(huán)處理 ? ?
線程控制 ? ?
異步等待 ? ?
依賴回調(diào) ? ?
拓?fù)鋬?yōu)化 ? ?

下面簡單介紹一下Android Startup的使用。

添加依賴

將下面的依賴添加到build.gradle文件中:

dependencies {
    implementation 'com.rousetime.android:android-startup:1.0.1'
}

依賴版本的更新信息: Release

快速使用

android-startup提供了兩種使用方式识颊,在使用之前需要先定義初始化的組件诚镰。

定義初始化的組件

每一個初始化的組件都需要實現(xiàn)AndroidStartup<T>抽象類,它實現(xiàn)了Startup<T>接口祥款,它主要有以下四個抽象方法:

  • callCreateOnMainThread(): Boolean用來控制create()方法調(diào)時所在的線程清笨,返回true代表在主線程執(zhí)行。

  • waitOnMainThread(): Boolean用來控制當(dāng)前初始化的組件是否需要在主線程進(jìn)行等待其完成刃跛。如果返回true抠艾,將在主線程等待,并且阻塞主線程。

  • create(): T?組件初始化方法,執(zhí)行需要處理的初始化邏輯赴肚,支持返回一個T類型的實例。

  • dependencies(): List<Class<out Startup<*>>>?返回Startup<*>類型的list集合齐苛。用來表示當(dāng)前組件在執(zhí)行之前需要依賴的組件。

例如桂塞,下面定義一個SampleFirstStartup類來實現(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
    }
 
}

因為SampleFirstStartup在執(zhí)行之前不需要依賴其它組件凹蜂,所以它的dependencies()方法可以返回空,同時它會在主線程中執(zhí)行阁危。

注意:?雖然waitOnMainThread()返回了false玛痊,但由于它是在主線程中執(zhí)行,而主線程默認(rèn)是阻塞的狂打,所以callCreateOnMainThread()返回true時擂煞,該方法設(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í)行耗時
        Thread.sleep(5000)
        return true
    }
 
    override fun dependencies(): List<Class<out Startup<*>>>? {
        return listOf(SampleFirstStartup::class.java)
    }
 
}

dependencies()方法中返回了SampleFirstStartup,所以它能保證SampleFirstStartup優(yōu)先執(zhí)行完畢官辽。
它會在子線程中執(zhí)行蛹磺,但由于waitOnMainThread()返回了true,所以主線程會阻塞等待直到它執(zhí)行完畢同仆。

例如,你還定義了SampleThirdStartupSampleFourthStartup

Manifest中自動配置

第一種初始化方法是在Manifest中進(jìn)行自動配置裙品。

在Android Startup中提供了StartupProvider類俗批,它是一個特殊的content provider,提供自動識別在manifest中配置的初始化組件市怎。
為了讓其能夠自動識別岁忘,需要在StartupProvider中定義<meta-data>標(biāo)簽。其中的name為定義的組件類区匠,value的值對應(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)簽中驰弄。這是因為在SampleFourthStartup中麻汰,它的dependencies()中依賴了這些組件。StartupProvider會自動識別已經(jīng)聲明的組件中依賴的其它組件戚篙。

Application中手動配置

第二種初始化方法是在Application進(jìn)行手動配置五鲫。

手動初始化需要使用到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)行項目之后乱灵,將會在控制臺中輸出經(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 ================================================

完整的代碼實例,你可以通過查看app獲取痛倚。

更多

可選配置

  • LoggerLevel: 控制Android Startup中的日志輸出规婆,可選值包括LoggerLevel.NONE, LoggerLevel.ERROR and LoggerLevel.DEBUG

  • AwaitTimeout: 控制Android Startup中主線程的超時等待時間状原,即阻塞的最長時間聋呢。

Manifest中配置

使用這些配置,你需要定義一個類去實現(xiàn)StartupProviderConfig接口颠区,并且實現(xiàn)它的對應(yīng)方法削锰。

class SampleStartupProviderConfig : StartupProviderConfig {
 
    override fun getConfig(): StartupConfig =
        StartupConfig.Builder()
            .setLoggerLevel(LoggerLevel.DEBUG)
            .setAwaitTimeout(12000L)
            .build()
}

與此同時,你還需要在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會自動解析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?): 該方法會在每一個依賴執(zhí)行完畢之后進(jìn)行回調(diào)。

實戰(zhàn)測試

AwesomeGithub中使用了Android Startup唆姐,優(yōu)化配置的初始化時間與組件化開發(fā)的配置注入時機(jī)拗慨,使用前與使用后時間對比:

狀態(tài) 啟動頁面 消耗時間
使用前 WelcomeActivity 420ms
使用后 WelcomeActivity 333ms

AwesomeGithub

AwesomeGithub是基于Github的客戶端,純練習(xí)項目奉芦,支持組件化開發(fā)赵抢,支持賬戶密碼與認(rèn)證登陸。使用Kotlin語言進(jìn)行開發(fā)声功,項目架構(gòu)是基于JetPack&DataBinding的MVVM烦却;項目中使用了Arouter、Retrofit先巴、Coroutine其爵、Glide、Dagger與Hilt等流行開源技術(shù)伸蚯。

除了Android原生版本摩渺,還有基于Flutter的跨平臺版本flutter_github

如果你喜歡我的文章朝卒,你可以關(guān)注我的微信公眾號:【Android補(bǔ)給站】或者掃描下方二維碼進(jìn)行關(guān)注证逻,當(dāng)然你也可以直接關(guān)注當(dāng)前網(wǎng)站的帳號。主要區(qū)別就是微信能夠更方法互動抗斤。

公眾號更新不會很頻繁囚企,但一旦更新必定是純干貨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瑞眼,一起剝皮案震驚了整個濱河市龙宏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伤疙,老刑警劉巖银酗,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異徒像,居然都是意外死亡黍特,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門锯蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灭衷,“玉大人,你說我怎么就攤上這事旁涤∠枨” “怎么了迫像?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞳遍。 經(jīng)常有香客問我闻妓,道長,這世上最難降的妖魔是什么掠械? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任由缆,我火速辦了婚禮,結(jié)果婚禮上猾蒂,老公的妹妹穿的比我還像新娘犁功。我一直安慰自己,他們只是感情好婚夫,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著署鸡,像睡著了一般案糙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靴庆,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天时捌,我揣著相機(jī)與錄音,去河邊找鬼炉抒。 笑死奢讨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焰薄。 我是一名探鬼主播拿诸,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼塞茅!你這毒婦竟也來了亩码?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤野瘦,失蹤者是張志新(化名)和其女友劉穎描沟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞭光,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡吏廉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惰许。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片席覆。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖啡省,靈堂內(nèi)的尸體忽然破棺而出娜睛,到底是詐尸還是另有隱情髓霞,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布畦戒,位于F島的核電站方库,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏障斋。R本人自食惡果不足惜纵潦,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垃环。 院中可真熱鬧邀层,春花似錦、人聲如沸遂庄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涛目。三九已至秸谢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霹肝,已是汗流浹背估蹄。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留沫换,地道東北人臭蚁。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像讯赏,于是被迫代替她去往敵國和親垮兑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345