Jetpack-App Startup

Android 11系統(tǒng)已經(jīng)來了闻鉴,隨之而來的是筛武,Jetpack家族也引入了許多新的成員。

其實以后Android的更新都會逐漸采用這種模式盗胀,即特定系統(tǒng)相關(guān)的API會越來越少,更多的編程API是以Jetpack Library的形式提供給我們的锄贼。這樣我們就不需要專門針對不同的系統(tǒng)版本去寫很多的適配邏輯票灰,而是統(tǒng)一用Jetpack提供的接口即可。Android也是在用這種方式去解決長期以來的碎片化問題。

而今年的Jetpack家族當中又加入了兩名重磅的新成員屑迂,一個是Hilt浸策,另一個是App Startup。

Hilt是一個依賴注入組件庫屈糊,功能非常強大的榛,但是由于想把依賴注入講清楚還是一個相對比較困難的工作,我準備過段時間再好好想想怎樣去寫好一篇關(guān)于Hilt的文章逻锐。

本篇文章的主題是App Startup夫晌。

App Startup是一個可以用于加速App啟動速度的一個庫。很多人一聽到可以加速App的啟動速度昧诱?那這是好東西啊晓淀,迫不及待地想要將這個庫引入到自己的項目當中,結(jié)果研究了半天盏档,發(fā)現(xiàn)越看越不明白凶掰,怎么學著學著還和ContentProvider扯上關(guān)系了?

所以蜈亩,在學習App Startup的用法之前懦窘,首先我們需要搞清楚的是,App Startup具體是用來解決什么問題的稚配。

關(guān)注我比較久的朋友應該都知道畅涂,LitePal是由我編寫并長期維護的一個Android數(shù)據(jù)庫框架。這個框架可以幫助大家自動管理表的創(chuàng)建與升級道川,并提供方便的數(shù)據(jù)庫操作API午衰。

而用過LitePal的朋友一定知道,LitePal有提供一個initialize()接口冒萄,在進行所有的數(shù)據(jù)庫操作之前臊岸,我們需要在自己的Application當中去調(diào)用這個接口進行初始化:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        LitePal.initialize(this)
    }
    ...
}

為什么LitePal要求先進行初始化呢?因為Android的數(shù)據(jù)庫中有需要操作都是需要依賴于Context的尊流,在初始化的時候傳入一次Context帅戒,LitePal會在內(nèi)部將其保存下來,這樣所以有其他數(shù)據(jù)庫接口就不需要再傳入Context參數(shù)了崖技,從而讓API變得更加精簡蜘澜。

這確實是個不錯的主意,但是并不是只有LitePal想到了這一點响疚,許多庫也提供了類似的初始化接口鄙信,因此如果你在項目當中引入了非常多的第三方庫,那么Application中的代碼就可能會變成這個樣子:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        LitePal.initialize(this)
        AAA.initialize(this)
        BBB.initialize(this)
        CCC.initialize(this)
        DDD.initialize(this)
        EEE.initialize(this)
    }
    ...
}

這樣的代碼就會顯得有些凌亂了對不對忿晕?隨著你引用的第三方庫越來越多装诡,這種情況真的是有可能發(fā)生的。

于是,有些更加聰明的庫設計者鸦采,他們想到了一種非常巧妙的辦法來避免顯示地調(diào)用初始化接口宾巍,而是可以自動調(diào)用初始化接口,這種辦法就是借助ContentProvider渔伯。

ContentProvider我們都知道是Android四大組件之一顶霞,它的主要作用是跨應用程序共享數(shù)據(jù)。比如為什么我們可以讀取到電話簿中的聯(lián)系人锣吼、相冊中的照片等數(shù)據(jù)选浑,借助的都是ContentProvider掺出。

然而這些聰明的庫設計者們并沒有打算使用ContentProvider來跨應用程序共享數(shù)據(jù)匿级,只是準備使用它進行初始化而已溢豆。我們來看如下代碼:

class MyProvider : ContentProvider() {

    override fun onCreate(): Boolean {
        context?.let {
            LitePal.initialize(it)
        }
        return true
    }
    ...
}

這里我定義了一個MyProvider棍厂,并讓它繼承自ContentProvider,然后我們在onCreate()方法中調(diào)用了LitePal的初始化接口爸吮。注意在ContentProvider中也是可以獲取到Context的钙态。

當然述呐,繼承了ContentProvider之后寺惫,我們是要重寫很多個方法的疹吃,只不過其他方法在我們這個場景下完全使用不到,所以你可以在那些方法中直接拋出一個異常西雀,或者進行空實現(xiàn)都是可以的互墓。

另外不要忘記,四大組件是需要在AndroidManifest.xml文件中進行注冊才可以使用的蒋搜,因此記得添加如下內(nèi)容:

<application ...>

    <provider
        android:name=".MyProvider"
        android:authorities="${applicationId}.myProvider"
        android:exported="false" />

</application>

authorities在這里并沒有固定的要求,填寫什么值都是可以的判莉,但必須保證這個值在整個手機上是唯一的豆挽,所以通常會使用${applicationId}作為前綴,以防止和其他應用程序沖突券盅。

那么帮哈,自定義的這個MyProvider它會在什么時候執(zhí)行呢?我們來看一下這張流程圖:

可以看到锰镀,一個應用程序的執(zhí)行順序是這個樣子的娘侍。首先調(diào)用Application的attachBaseContext()方法,然后調(diào)用ContentProvider的onCreate()方法泳炉,接下來調(diào)用Application的onCreate()方法憾筏。

那么,假如LitePal在自己的庫當中實現(xiàn)了上述的MyProvider花鹅,會發(fā)生什么情況呢氧腰?

你會發(fā)現(xiàn)LitePal.initialize()這個接口可以省略了,因為在MyProvider當中這個接口會被自動調(diào)用,這樣在進入Application的onCreate()方法時古拴,LitePal其實已經(jīng)初始化過了箩帚。

有沒有覺得這種設計方式很巧妙?它可以將庫的用法進一步簡化黄痪,不需要你主動去調(diào)用初始化接口紧帕,而是將這個工作在背后悄悄自動完成了。

那么有哪些庫使用了這種設計方式呢桅打?這個真的有很多了是嗜,比如說Facebook的庫,F(xiàn)irebase的庫油额,還有我們所熟知的WorkManager叠纷,Lifecycles等等。這些庫都沒有提供一個像LitePal那樣的初始化接口潦嘶,其實就是使用了上述的技巧涩嚣。

看上去如此巧妙的技術(shù)方案,那么它有沒有什么缺點呢掂僵?

有航厚,缺點就是,ContentProvider會增加許多額外的耗時锰蓬。

畢竟ContentProvider是Android四大組件之一幔睬,這個組件相對來說是比較重量級的。也就是說芹扭,本來我的初始化操作可能是一個非常輕量級的操作麻顶,依賴于ContentProvider之后就變成了一個重量級的操作了。

關(guān)于ContentProvider的耗時舱卡,Google官方也有給出一個測試結(jié)果:

這是在一臺搭載Android 10系統(tǒng)的Pixel2手機上測試的情況辅肾。可以看到轮锥,一個空的ContentProvider大約會占用2ms的耗時矫钓,隨著ContentProvider的增加,耗時也會跟著一起增加舍杜。如果你的應用程序中使用了50個ContentProvider新娜,那么將會占用接近20ms的耗時。

注意這還只是空ContentProvider的耗時既绩,并沒有算上你在ContentProvider中執(zhí)行邏輯的耗時概龄。

這個測試結(jié)果告訴我們,雖然剛才所介紹的使用ContentProvider來進行初始化的設計方式很巧妙饲握,但是如果每個第三方庫都自己創(chuàng)建了一個ContentProvider旁钧,那么最終我們App的啟動速度就會受到比較大的影響吸重。

有沒有辦法解決這個問題呢?

有歪今,就是使用我們今天要介紹的主題:App Startup嚎幸。

我上面花了很長的篇幅來介紹App Startup具體是用來解決什么問題的,因為這部分內(nèi)容才是App Startup庫的核心寄猩,只有了解了它是用來解決什么問題的嫉晶,才能快速掌握它的用法。不然就會像剛開始說的那樣田篇,學著學著怎么學到ContentProvider上面去了替废,一頭霧水。

那么App Startup是如何解決這個問題的呢泊柬?它可以將所有用于初始化的ContentProvider合并成一個椎镣,從而使App的啟動速度變得更快。

具體來講兽赁,App Startup內(nèi)部也創(chuàng)建了一個ContentProvider状答,并提供了一套用于初始化的標準。然后對于其他第三方庫來說刀崖,你們就不需要再自己創(chuàng)建ContentProvider了惊科,都按我的這套標準進行實現(xiàn)就行了,我可以保證你們的庫在App啟動之前都成功進行初始化亮钦。

了解了App Startup具體是用來解決什么問題的馆截,以及它的實現(xiàn)原理,接下來我們開始學習它的用法蜂莉,這部分就非常簡單了蜡娶。

首先要使用App Startup,我們要將這個庫引入進來:

dependencies {
    implementation "androidx.startup:startup-runtime:1.0.0-alpha01"
}

接下來我們要定義一個用于執(zhí)行初始化的Initializer映穗,并實現(xiàn)App Startup庫的Initializer接口窖张,如下所示:

class LitePalInitializer : Initializer<Unit> {

    override fun create(context: Context) {
        LitePal.initialize(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return listOf(OtherInitializer::class.java)
    }

}

實現(xiàn)Initializer接口要求重現(xiàn)兩個方法,在create()方法中男公,我們?nèi)ミM行之前要進行的初始化操作就可以了,create()方法會把我們需要的Context參數(shù)傳遞進來合陵。

dependencies()方法表示枢赔,當前的LitePalInitializer是否還依賴于其他的Initializer,如果有的話拥知,就在這里進行配置踏拜,App Startup會保證先初始化依賴的Initializer,然后才會初始化當前的LitePalInitializer低剔。

當然速梗,絕大多數(shù)的情況下肮塞,我們的初始化操作都是不會依賴于其他Initializer的,所以通常直接返回一個emptyList()就可以了姻锁,如下所示:

class LitePalInitializer : Initializer<Unit> {

    override fun create(context: Context) {
        LitePal.initialize(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }

}

定義好了Initializer之后枕赵,接下來還剩最后一步,將它配置到AndroidManifest.xml當中位隶。但是注意拷窜,這里的配置是有比較嚴格的格式要求的,如下所示:

<application ...>

    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <meta-data
            android:name="com.example.LitePalInitializer"
            android:value="androidx.startup" />
    </provider>
    
</application>

上述配置涧黄,我們能修改的地方并不多篮昧,只有meta-data中的android:name部分我們需要指定成我們自定義的Initializer的全路徑類名,其他部分都是不能修改的笋妥,否則App Startup庫可能會無法正常工作懊昨。

沒錯,App Startup庫的用法就是這么簡單春宣,基本我將它總結(jié)成了三步走的操作酵颁。

引入App Startup的庫。
自定義一個用于初始化的Initializer信认。
將自定義Initializer配置到AndroidManifest.xml當中材义。

這樣,當App啟動的時候會自動執(zhí)行App Startup庫中內(nèi)置的ContentProvider嫁赏,并在它的ContentProvider中會搜尋所有注冊的Initializer其掂,然后逐個調(diào)用它們的create()方法來進行初始化操作。

只用一個ContentProvider就可以讓所有庫都正常初始化潦蝇,Everyone is happy款熬。

其實到這里為止,App Startup庫的知識就已經(jīng)講完了攘乒,最后再介紹一個不太常用的知識點吧:延遲初始化贤牛。

現(xiàn)在我們已經(jīng)知道,所有的Initializer都會在App啟動的時候自動執(zhí)行初始化操作则酝。但是如果我作為LitePal庫的用戶殉簸,就是不希望它在啟動的時候自動初始化,而是想要在特定的時機手動初始化沽讹,這要怎么辦呢般卑?

首先,你得通過分析LitePal源碼的方式爽雄,找到LitePal用于初始化的Initializer的全路徑類名是什么蝠检,比如上述例子當中的com.example.LitePalInitializer(注意這里我只是為了講解這個知識點而舉的例子,實際上LitePal還并沒有接入App Startup)挚瘟。

然后叹谁,在你的項目的AndroidManifest.xml當中加入如下配置:

<application ...>

    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <meta-data
            android:name="com.example.LitePalInitializer"
            tools:node="remove" />
    </provider>
    
</application>

區(qū)別就在于饲梭,這里在LitePalInitializer的meta-data當中加入了一個tools:node="remove"的標記。

這個標記用于告訴manifest merger tool焰檩,在最后打包成APK時憔涉,將所有android:name是com.example.LitePalInitializer的meta-data節(jié)點全部刪除。

這樣锅尘,LitePal庫在自己的AndroidManifest.xml中配置的Initializer也會被刪除监氢,既然刪除了,App Startup在啟動的時候肯定就無法初始化它了藤违。

而在之后手動去初始化LitePal的代碼也極其簡單浪腐,如下所示:

AppInitializer.getInstance(this)
    .initializeComponent(LitePalInitializer::class.java)

將LitePalInitializer傳入到initializeComponent()方法當中即可,App Startup庫會按照同樣的標準去調(diào)用其create()方法來執(zhí)行初始化操作顿乒。

到這里為止议街,App Startup的功能基本就全部講解完了。

最后如果讓我總結(jié)一下的話璧榄,這個庫的整體用法非常簡單特漩,但是可能并不適合所有人去使用。如果你是一個庫開發(fā)者骨杂,并且使用了ContentProvider的方式來進行初始化操作涂身,那么你應該接入App Startup,這樣可以讓接入你的庫的App降低啟動耗時搓蚪。而如果你是一個App開發(fā)者蛤售,我認為使用ContentProvider來進行初始化操作的概率很低,所以可能App Startup對你來說用處并不大妒潭。

當然悴能,考慮到業(yè)務邏輯分離的代碼結(jié)構(gòu),App的開發(fā)者也可以考慮將一些原來放在Application中的初始化代碼雳灾,移動到一個Initializer中去單獨執(zhí)行漠酿,或許可以讓你的代碼結(jié)構(gòu)變得更加合理與清晰。

當前保存作為筆記原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谎亩,一起剝皮案震驚了整個濱河市炒嘲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匈庭,老刑警劉巖夫凸,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嚎花,居然都是意外死亡寸痢,警方通過查閱死者的電腦和手機呀洲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門紊选,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啼止,“玉大人,你說我怎么就攤上這事兵罢∠追常” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵卖词,是天一觀的道長巩那。 經(jīng)常有香客問我,道長此蜈,這世上最難降的妖魔是什么即横? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮裆赵,結(jié)果婚禮上东囚,老公的妹妹穿的比我還像新娘。我一直安慰自己战授,他們只是感情好页藻,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著植兰,像睡著了一般份帐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上楣导,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天废境,我揣著相機與錄音,去河邊找鬼爷辙。 笑死彬坏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的膝晾。 我是一名探鬼主播栓始,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼血当!你這毒婦竟也來了幻赚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤臊旭,失蹤者是張志新(化名)和其女友劉穎落恼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體离熏,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡佳谦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了滋戳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钻蔑。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡啥刻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咪笑,到底是詐尸還是另有隱情可帽,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布窗怒,位于F島的核電站映跟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扬虚。R本人自食惡果不足惜努隙,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辜昵。 院中可真熱鬧剃法,春花似錦、人聲如沸路鹰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晋柱。三九已至优构,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雁竞,已是汗流浹背钦椭。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碑诉,地道東北人彪腔。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像进栽,于是被迫代替她去往敵國和親德挣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361