我爲什麼棄用Jetpack的App Startup?

2020_08_05_00_07.jpeg

前言

最近Jetpack又添加了新成員App Startup,官方聲明這是一個在Android應用啓動時,針對初始化組件進行優化的依賴庫。本人第一次聽到後很是高興,由於本身負責的項目在啓動時須要初始化的東西實在是太多,並且有點雜亂無章,都耦合在一塊兒了。對於能夠異步初始化的組件也沒有進行異步處理,而對於已經處理過的異步組件它們之間的依賴關係或者多個異步以後的統一邏輯處理也沒有一個很好的統一規範。因此針對這種狀況早就想找個方案來優化了,此次終於等到了App Startupjava

可是,當我元氣滿滿的去查看官方文檔時,並無找到預想中的結果。官方文檔中只提到了能夠經過一個ContentProvider來統一管理須要初始化的組件,同時經過dependencies()方法解決組件間初始化的依賴順序,而後呢?沒了?等等官方你是否是漏了什麼?android

異步處理呢?雖然咱們能夠在create()方法中手動建立子線程進行異步任務,但一個異步任務依賴另外一個異步任務又該如何處理呢?多個異步任務完成以後,統一邏輯處理又在哪裏呢?依賴任務完成後的回調又在哪裏?亦或者是依賴任務完成後的通知?git

我有點不相信,因此又去查看了App Startup的源碼,源碼很簡單,也就幾個文件,最後發現確實只支持上面的那幾個功能。github

若是你的項目都是同步初始化的話,而且使用到了多個ContentProviderApp Startup可能有必定的優化空間,畢竟統一到了一個ContentProvider中,同時支持了簡單的順序依賴。segmentfault

值得一提的是,App Startup中只提供了使用反射來獲取初始化的組件實例,這對於一些沒有過多依賴的初始化項目來講,盲目使用App Startup來優化是否會對啓動速度進一步形成影響呢?微信

因此細想了一下,不由讓我想起了三國時的一個名詞:雞肋。食之無味,棄之惋惜。架構

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

放棄以後有點不甘心,可能更多的是它沒有解決我當前的項目場景。都分析了這麼多,源碼都看了,總不能半途而廢吧,因此本身咬咬牙再補充一點唄。異步

因此堅持一下,就有了下面這個庫,App Startup的進階版Android Startupide

Android Startup

Android Startup提供一種在應用啓動時可以更加簡單、高效的方式來初始化組件。開發人員可使用Android Startup來簡化啓動序列,並顯式地設置初始化順序與組件之間的依賴關係。
與此同時,Android Startup支持同步與異步等待,並經過有向無環圖拓撲排序的方式來保證內部依賴組件的初始化順序。

因爲Android Startup是基於App Startup進行的擴展,因此它的使用方式與App Startup有點相似,該有的功能基本上都有,同時額外還附加其它功能。

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

指標 App Startup Android Startup
手動配置
自動配置
依賴支持
閉環處理
線程控制
異步等待
依賴回調
拓撲優化

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

添加依賴

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

dependencies {
    implementation 'com.rousetime.android:android-startup:1.0.1'
}
依賴版本的更新信息: Release

快速使用

android-startup提供了兩種使用方式,在使用以前須要先定義初始化的組件。

定義初始化的組件

每個初始化的組件都須要實現AndroidStartup<T>抽象類,它實現了Startup<T>接口,它主要有如下四個抽象方法:

  • callCreateOnMainThread(): Boolean用來控制create()方法調時所在的線程,返回true表明在主線程執行。
  • waitOnMainThread(): Boolean用來控制當前初始化的組件是否須要在主線程進行等待其完成。若是返回true,將在主線程等待,而且阻塞主線程。
  • create(): T?組件初始化方法,執行須要處理的初始化邏輯,支持返回一個T類型的實例。
  • dependencies(): List<Class<out Startup<*>>>?返回Startup<*>類型的list集合。用來表示當前組件在執行以前須要依賴的組件。

例如,下面定義一個SampleFirstStartup類來實現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在執行以前不須要依賴其它組件,因此它的dependencies()方法能夠返回空,同時它會在主線程中執行。

注意:️雖然 waitOnMainThread()返回了 false,但因爲它是在主線程中執行,而主線程默認是阻塞的,因此 callCreateOnMainThread()返回 true時,該方法設置將失效。

假設你還須要定義SampleSecondStartup,它依賴於SampleFirstStartup。這意味着在執行SampleSecondStartup以前SampleFirstStartup必須先執行完畢。

class SampleSecondStartup : AndroidStartup<Boolean>() {
 
    override fun callCreateOnMainThread(): Boolean = false
 
    override fun waitOnMainThread(): Boolean = true
 
    override fun create(context: Context): Boolean {
        // 模仿執行耗時
        Thread.sleep(5000)
        return true
    }
 
    override fun dependencies(): List<Class<out Startup<*>>>? {
        return listOf(SampleFirstStartup::class.java)
    }
 
}

dependencies()方法中返回了SampleFirstStartup,因此它能保證SampleFirstStartup優先執行完畢。
它會在子線程中執行,但因爲waitOnMainThread()返回了true,因此主線程會阻塞等待直到它執行完畢。

例如,你還定義了SampleThirdStartupSampleFourthStartup

Manifest中自動配置

第一種初始化方法是在Manifest中進行自動配置。

在Android Startup中提供了StartupProvider類,它是一個特殊的content provider,提供自動識別在manifest中配置的初始化組件。
爲了讓其可以自動識別,須要在StartupProvider中定義<meta-data>標籤。其中的name爲定義的組件類,value的值對應爲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>標籤中。這是由於在SampleFourthStartup中,它的dependencies()中依賴了這些組件。StartupProvider會自動識別已經聲明的組件中依賴的其它組件。

Application中手動配置

第二種初始化方法是在Application進行手動配置。

手動初始化須要使用到StartupManager.Builder()

例如,以下代碼使用StartupManager.Builder()進行初始化配置。

class SampleApplication : Application() {
 
    override fun onCreate() {
        super.onCreate()
        StartupManager.Builder()
            .addStartup(SampleFirstStartup())
            .addStartup(SampleSecondStartup())
            .addStartup(SampleThirdStartup())
            .addStartup(SampleFourthStartup())
            .build(this)
            .start()
            .await()
    }
}

若是你開啓了日誌輸出,而後運行項目以後,將會在控制檯中輸出通過拓撲排序優化以後的初始化組件的執行順序。

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中配置

使用這些配置,你須要定義一個類去實現StartupProviderConfig接口,而且實現它的對應方法。

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

與此同時,你還須要在manifest中進行配置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>

通過上面的配置,StartupProvider會自動解析SampleStartupProviderConfig

Application中配置

在Application須要藉助StartupManager.Builder()進行配置。

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: 若是定義的組件沒有運行在主線程,那麼能夠經過該方法進行控制運行的子線程。
  • onDependenciesCompleted(startup: Startup<*>, result: Any?): 該方法會在每個依賴執行完畢以後進行回調。

實戰測試

AwesomeGithub中使用了Android Startup,優化配置的初始化時間與組件化開發的配置注入時機,使用前與使用後時間對比:

狀態 啓動頁面 消耗時間
使用前 WelcomeActivity 420ms
使用後 WelcomeActivity 333ms

AwesomeGithub

AwesomeGithub是基於Github的客戶端,純練習項目,支持組件化開發,支持帳戶密碼與認證登錄。使用Kotlin語言進行開發,項目架構是基於JetPack&DataBinding的MVVM;項目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger與Hilt等流行開源技術。

項目圖

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

若是你喜歡個人文章,你能夠關注個人微信公衆號:【Android補給站】或者掃描下方二維碼進行關注,固然你也能夠直接關注當前網站的賬號。主要區別就是微信可以更方法互動。

公衆號更新不會很頻繁,但一旦更新一定是純乾貨。

Android補給站.jpg

相關文章
相關標籤/搜索