Android 應用啓動性能 | 延遲初始化

上一篇文章 中,我展現了 content provider (它出如今應用合併後的 manifest 文件) 是如何在應用啓動的時候自動加載第三方庫以及模塊的。java

在這篇文章中,我會介紹如何使用 AndroidX 的 應用啓動 (App Startup) 庫來進一步控制那些庫該在什麼時候以及以何種方式被加載。也許,我是說也許,咱們也會順便發現該如何縮短應用的啓動時間。node

使用應用啓動庫自動初始化

使用應用啓動庫 (App Startup) 最簡單的方式是利用它的 content provider 在後臺初始化其餘庫。您既能夠指定應用啓動庫該如何初始化其餘的庫,也能夠從合併後的 manifest 文件中移除其餘庫的 content provider。避免使用多個 content provider 執行啓動任務,而是將資源用於加載應用啓動庫,而後再加載其餘內容。android

您能夠經過以下三步實現上述操做,首先在您工程的 build.gradle 文件中添加應用啓動庫做爲依賴,其次爲每個須要初始化的庫建立一個 Initializer,最後在您工程的 Manifest.xml 文件中添加相關信息。性能優化

讓咱們再看一遍我在 第一篇文章 中使用的 WorkManager 示例。爲了經過應用啓動庫加載 WorkManager,我先在應用的 build.gradle 文件中添加了應用啓動庫:app

// 查看最新的版本號 https://developer.android.google.cn/jetpack/androidx/releases/startup
def startup_version = "1.0.0"
implementation 「androidx.startup:startup-runtime:$startup_version」

而後,基於應用啓動庫提供的 Initializer 接口,我建立了一個 Initializer:ide

class MyWorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder().build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 沒有其餘依賴庫
        return emptyList()
    }
}

每個 Initializer 有兩個方法須要複寫: create()dependencies()dependencies() 被用來指定多個依賴庫的初始化順序。在這個示例中我並不須要這個功能,由於我只須要處理 WorkManager。若是您須要在應用中使用多個庫,請查看 應用啓動使用手冊 中關於使用 dependencies() 的詳情。工具

對於 create() 方法,我模仿了 WorkManager’s content provider 中的實現。性能

順便說一下,其實這個方法在使用應用啓動庫的時候很經常使用。一個庫的 content provider 負責了其初始化的實現,因此您一般均可以參考那個類中的代碼來手動實現它。有些庫可能比較麻煩,由於它們使用了隱藏的或者內部的 API,可是好在 WorkManager 並非,因此我能夠這麼作,但願該方法也適用於您的狀況。測試

最後,我在 Manifest.xml 文件的 <application> 代碼塊中添加了兩個 provider 的標籤。第一個以下所示:gradle

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    android:exported="false"
    tools:node="remove" />

WorkManagerInitializer 標籤很重要,由於它表示須要 Android Studio 刪除自動生成的 provider,而該 provider 是在 build.gradle 文件中添加 WorkManager 後生成的。若是沒有這個特殊的標籤,這個庫仍然會在應用啓動的時候自動初始化,繼而在應用啓動庫嘗試初始化它的時候報錯,由於它已經被初始化了。

下面是我添加的第二個 provider 標籤:

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

InitializationProvider 標籤和經過添加應用啓動庫到 build.gradle 文件中自動生成的標籤基本相同 (您能夠經過查看合併後的 manifest 文件來驗證 -- 詳情請查看 第一篇文章),可是它們有兩個很重要的不一樣點:

tools:node="merge"

這個參數主要用於 Android Studio 所負責的 manifest 合併操做。它告訴工具在最終合併的 manifest 文件中合併這個標籤的多個實例。在這個例子中,它會合並由庫依賴自動生成的 <provider> 到這個版本的 provider,這樣在最終合併的 manifest 文件中只會有這一個標籤實例。

另外一行包含了這個 meta-data:

<meta-data  android:name="com.example.startuplibtest.MyWorkManagerInitializer"
    android:value="androidx.startup" />

這個 provider 中的 metadata 標籤告訴應用啓動庫如何找到您的 Initializer 代碼,這些代碼會在應用啓動的時候執行來初始化這個庫。請注意這致使的區別: 若是您沒有使用應用啓動庫,就會自動執行相關初始化,由於 Android 會在那個庫中建立並執行 content provider,以後會自動初始化這個庫自己。可是經過應用啓動庫指定您的 Initializer,以及在合併 manifest 文件中去除 WorkManager 的 provider,至關於告訴 Android 轉而使用應用啓動庫的 content provider 來加載 WorkManager 庫。若是經過這個方式初始化多個庫,您能夠利用應用啓動庫的這個單獨的 content provider 有效地管理這些請求,而不是致使每一個庫都建立本身的 content provider。

偷個懶...若是您想的話

當優化應用啓動性能的時候,咱們不能改變那些沒法控制的代碼實現。因此這裏的思路並非加速咱們使用庫的初始化,而是控制這些庫何時以及如何被初始化。尤爲是咱們能夠決定任一個庫是否須要在應用啓動的時候被初始化 (要麼使用庫的默認機制添加 content provider 到合併的 manifest 文件,或者也能夠利用應用啓動庫的 content provider 來集中管理初始化請求),仍是須要稍候再加載它們。

舉個例子,或許在您應用的一個特殊的流程中須要某一個包含 content provider 初始化的庫,可是這個庫並不須要在應用啓動的時候當即被加載,又或者在某些狀況下它根本不須要被加載。若是是這樣的話,爲何要由於只在某個特殊代碼路徑中須要而在應用啓動時花時間初始化一個很大的庫呢?爲何不等到這個庫真正被須要的時候再引入相關的初始化開銷呢?

這正是應用啓動庫高明的地方,它能幫您從合併的 manifest 文件中和應用啓動的過程當中移除隱藏的 content provider,也能幫您延遲或者更有目的地加載這些庫。

使用應用啓動庫實現延遲初始化

如今咱們已經知道該如何使用應用啓動庫實現自動加載以及初始化庫。接下來讓咱們更進一步地來看看,若是您不想在啓動的時候初始化,該如何實現延遲初始化。

其實上面的代碼已經很接近了,在 build.gradle 文件中您須要一樣的啓動依賴和其餘您想使用的庫,也仍是須要特殊的 "移除" provider 標籤來去除每一個庫自動生成的 content provider。咱們只須要向 manifest 文件添加多一點信息來告訴它一樣移除應用啓動庫的 provider。這樣在應用啓動的時候就不會有任何 content provider 初始化發生,而徹底由您來決定何時應該觸發相關初始化。

爲了達到這個目的,我用下面的代碼替換了前面使用的 InitializationProvider。上面所展現的代碼告訴了系統該如何定位 content provider 中自動初始化您庫的代碼。由於稍後要手動觸發初始化,這一次我要跳過那個部分,而只留下在應用啓動的時候去除自動生成的 content provider 的部分。

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />

在我作了這個改動後,在合併的 manifest 文件中再也不有任何 content provider 了,因此應用啓動庫和 WorkManager 都不會在應用啓動的時候被自動初始化了。

爲了手動初始化這些庫,我在應用的其餘地方添加了以下代碼來實現它:

val initializer = AppInitializer.getInstance(context)
initializer.initializeComponent(MyWorkManagerInitializer::class.java)

AppInitializer 是應用啓動庫提供的鏈接全部這些部分的類。您須要使用一個 context 對象來建立 AppInitializer 對象,而後能夠向其傳遞一個您爲初始化各類不一樣庫建立的 Initializer 引用。在這個示例中,我使用的是 MyWorkManagerInitializer,而後就搞定了。

時間就是一切

我作了幾回測試 (使用的是我在 測試應用啓動性能 文章中提到的計時方法) 來比較幾種不一樣的啓動應用和初始化庫的方法。我統計了不帶任何庫、帶 WorkManager (使用默認自動生成的 content provider)、在啓動時使用應用啓動庫自動初始化 WorkManager 以及使用 AppInitializer 延遲初始化 WorkManager 和應用啓動庫。

須要注意的是,就像咱們在 以前的文章 中討論的,全部的這些時間計算都是基於鎖定的 CPU 主頻,因此這些時長都要比在沒有鎖定 CPU 主頻的機器上大不少。它們只在相互之間比較的時候有意義,而並不能表明真實的狀況。下面是我發現的:

  • 不帶 WorkManager: 1244 ms
  • 帶 WorkManager 而且經過 content provider 加載: 1311 ms
  • 帶 WorkManager 而且經過 App Startup 加載: 1315 ms
  • 帶 WorkManager (延遲加載): 1268 ms

最後,我統計了利用 AppInitalizer 手動初始化 WorkManager 的耗時:

  • 利用 AppInitializer 初始化 WorkManager: 51 ms

這個數據給咱們帶來一些啓示。首先,在應用啓動的時候加載 WorkManager 會給個人應用平均增長 67 毫秒 (1311–1244) 的啓動時間。須要注意的是: 加載這個庫的常規方式 (使用 content provider) 使用的時間和使用應用啓動庫的 (1315 – 1244 = 71 ms) 差很少。這是由於應用啓動庫在單個庫的例子中並不會幫咱們節省時間,咱們只不過是轉移邏輯到另外一個代碼路徑中運行。若是使用應用啓動庫加載多個庫,咱們會獲得相應的優化效果,可是針對這裏的單個庫的例子,使用這個方法不會有任何節省時間的優點。

同時延遲初始化 WorkManager 讓我能夠 "節省" 大約 51 毫秒的時間。

這個差異是否足夠明顯到您須要擔憂呢?答案永遠是 "看狀況而定"。

51 毫秒佔了 1.3 秒總時長的不到 4%,而對於一個真實應用來講,一般都會比我這個簡單的應用更復雜,這個耗時佔總啓動時間的百分比會更低。這種狀況下這個時長可能不值得擔憂。可是有時候您可能發現有些庫須要太長時間來初始化,更有可能的是,您可能使用了幾個自帶 content provider 的庫,而它們每個都會增長一點您應用的啓動時間。若是您能夠將上述大部分或者所有工做推遲到一個更爲合適的時間點,而且從啓動過程當中剝離,或許您會發現應用的啓動速度會有顯著的提升。

像全部的性能優化項目,您能夠作的最重要的事情是分析細節、測量以及決定:

  • 檢查您項目合併後的 manifest 文件。您能夠看到多少 <provider> 標籤?
  • 您可否利用應用啓動庫從合併的 manifest 文件中移除一些甚至全部這些 content provider,並觀察它如何影響啓動時間?您可否在實現這個的同時不影響運行時行爲呢?(值得注意的是: 您須要保證在應用開始依賴相關庫的功能以前,確保初始化它們。)

最後,盡情享受性能測試和優化。我會繼續找尋更多分析和優化應用的性能辦法,若是發現什麼有價值的東西我會發布相關的內容。

相關文章
相關標籤/搜索