[譯] 谷歌尋蹤聖誕老人應用(Santa Tracker)遷移到 Android App Bundle 記錄

PhxNirvana發表於2019-05-11

[譯] 谷歌尋蹤聖誕老人應用(Santa Tracker)遷移到 Android App Bundle 記錄

本文是 2018 谷歌尋蹤聖誕老人應用改進探索系列文章的第一篇。

尋蹤聖誕老人是谷歌每年都會發布的一款應用,這款應用讓人們可以在全球追尋聖誕老人的足跡。不幸的是,這款應用在經過幾年的迭代後,體積劇增,2017 年甚至達到了 60MB。我們在剛剛過去的聖誕季的目標是幫它大量減肥,本文講述了我們實現該目標的過程。


如果讀者體驗過 尋蹤聖誕老人應用 的話,就會發現該應用有兩大特色,「追蹤器」讓使用者得以在全球範圍內尋覓聖誕老人,另外一系列在十二月提供的小遊戲來幫助使用者享受聖誕季?。

「追蹤器」是該應用的主要功能,也是最多被使用者使用的功能。該功能事實上只在聖誕節前 26 小時(12 月 24 日)可用,在此期間,追蹤器是最多被使用的功能。更準確地說,在 12 月的所有介面使用統計中,37% 是在 12 月 24 日 使用的,而那一天,追蹤器的使用率超過了 65%

那麼,為什麼這項功能如此重要呢?只有瞭解我們的主要特色是什麼,才能讓我們想明白,哪些是應用首次安裝時最關鍵的功能,哪些是次要的、可以移到另外 module 中動態下發的功能,這樣就使得我們的首次安裝體積變小。2017 年釋出的 app 包含全部功能,其中包括全部的遊戲,即使使用者根本不玩這些遊戲。

是時候對尋蹤聖誕老人動刀子了,我們設立了將首次下載體積減少到僅僅 10MB 的目標?。

什麼,為什麼是這個數字?因為資料顯示,相比 100MB 的應用,10MB 的應用提高了 30% 的轉化率。當然,儘管許多應用都在追蹤轉化率,尋蹤聖誕老人卻並不是我們追蹤轉化率的 app。10MB 也是一個嘗試起來很難達到的目標,我們想看看這究竟是不是可行的。關於更多統計背後的資訊,可以閱讀 Google Play 團隊 的這篇文章:

動態分發

讀者可能聽說過 Android App Bundle 這項新技術,該技術使得 Google Play 商店可以動態下發僅僅和使用者裝置相關的定製應用。這項技術也幫助我們開了個好頭。只需上傳 AAB(Android App Bundle)來代替 APK,我們就馬上讓下載體積減少了將近 20% ,達到了 48.5MB(從 60MB)。們只不過是花了一小步的功夫,就在縮減體積方面邁進了一大步

如果只打算從本文中學一項技術,一定得是上傳 AAB 來取代 APK。這一小改動有很大機會來節省使用者的時間和金錢。

Google Play 是怎麼實現這種瘦身的呢?這一做法能夠分發針對個別裝置的優化包,這麼一來,相應工具就能從安裝包中移除所有不適用於裝置的語言資源、解析度資源以及本地庫。比如,如果你的裝置設定是 fr-FR(法語),解析度是 xxhdpi ,CPU是 arm64-v8a 架構的,下發的 APK 便只會包含必要的資源,而不會包含諸如針對西班牙語本地化的字串之類的東西。當發現本地化字串佔用的空間有多大時,你一定會大吃一驚。

不要忘了觀看 Android 開發大會 ’18 上的 ‘優化應用的體積’ 演講來獲取更多資訊:

功能模組

儘管我們有著良好的開頭,卻仍距離 10MB 的目標十萬八千里!所以我們開始考慮哪些功能可以被拆到動態功能模組中,使用者可以通過 Play Core library 來獲取所需的模組。好訊息是我們已經按邏輯分離了一大模組:遊戲?。

於是便有了如下的計劃:將每個遊戲拆分到單獨的功能模組中,並只當使用者第一次開啟特定遊戲的時候才安裝。聽起來很棒,不是麼?儘管邏輯上游戲都分離了,但基礎程式碼卻並沒有分離。經過數年的功能變遷,它們已經纏纏綿綿難以分離了。應用中的庫模組層層疊疊,而且到處是重複的資源。

我們的首要工作是將其解耦和,並在遊戲模組之間建立足夠清晰的邊界。我們小心翼翼地分離了全部的遊戲模組,通過使用新的 com.android.dynamic-feature Gradle 外掛,現在每個遊戲都是完全獨立的模組了。對於那些有著相同依賴的遊戲(比如 ‘Penguin Swim’ 和 ‘Elf Jetpack’ 共享了許多程式碼),依賴被新增到 ‘base’ 模組中,這樣一來,就可以只安裝一次(同時玩兩個遊戲)了。

功能模組的實現

正如之前說過的那樣,模組遷移中佔大頭的工作是已有程式碼的重新組織,另外也有一些小的整合工作需要通過 Play Core library 來將其穿插起來。

首先是使用者啟動遊戲時的 UX。我們首先開啟顯示 logo 和遊戲標題的 ‘啟動頁(splash screen)’ activity,過一小段時間再執行遊戲。執行遊戲需要的全部資訊都作為 intent extras 傳送到啟動頁了。數年來該行為都沒有變化,我們也並不打算修改這一行為。相反,我們從中找到了動態分發功能模組的切入點。

2018 年我們更新了啟動行為,傳送了四點資訊:遊戲標題、遊戲圖示、要執行的 Activity 類以及該功能模組的 ID。一旦啟動頁展示出來,就檢查是否安裝了相關模組。如果安裝了,就直接執行,反之則通過 Play Core library 請求安裝,並展示下載進度條:

[譯] 谷歌尋蹤聖誕老人應用(Santa Tracker)遷移到 Android App Bundle 記錄

我們在早期測試中發現需要小心處理下載安裝時的場景。我們並不想因為在使用者處於行動網路時安裝功能模組,而無意中讓他們花錢。為了應對這種情形,我們在檢測到當前網路是流量網路(如行動網路)時增加了確認對話方塊:

當連線到流量網路時的確認對話方塊

整體邏輯如下:

/* Copyright 2018 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

override fun onCreate(savedInstanceState: Bundle?) {
    // ... 安裝

    // 遊戲功能模組的 Id 
    val featureModuleId = intent.getStringExtra(...)

    if (featureModuleName in splitInstallManager.installedModules) {
        // 功能模組已經安裝,直接執行
        launchTargetActivity()
    } else {
        // 功能模組沒有安裝,請求安裝
        val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        if (mgr.activeNetworkInfo?.isConnected == true) {
            // 有網路...
            if (mgr.isActiveNetworkMetered) {
                // TODO ...流量網路,請求使用者確認
                showMeteredNetworkConfirmDialog()
            } else {
                // ...否則,直接下載
                startModuleInstall(featureModuleId)
            }
        } else {
            // 沒有網路,顯示錯誤框並退出
            onFeatureModuleLaunchFailure()
        }
    }
}
複製程式碼

由於 Play Core API 的緣故,startModuleInstall() 的方法看起來有些複雜。需要在安裝時新增一個用於回撥的 listener,然後再請求安裝,如下所示:

/* Copyright 2018 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */

private lateinit var splitInstallManager: SplitInstallManager
private lateinit var installListener: SplitInstallStateUpdatedListener

private fun startModuleInstall(featureModuleId: String) {
    // 顯示進度條
    progressbar.isVisible = true
    progressbar.isIndeterminate = true

    // 新增 listener
    splitInstallManager.registerListener(installListener)
    
    // 傳送請求,開始安裝
    val request = SplitInstallRequest.newBuilder()
            .addModule(featureModuleId)
            .build()
    splitInstallManager.startInstall(request)
}
複製程式碼

listener 會監聽到安裝完成的訊號,然後執行遊戲。可以在 這裡 找到完整程式碼。

成果

如果你讀到這裡了,一定會想知道我們的成果如何……

Android Studio 分析 App Bundle(以及 APK)的工具相當好用,可以深入觀察每個功能模組的下載體積。我們可以在其中看到我們應用的初始下載體積是 11.6MB (並沒有達到 10MB 的目標),總下載體積是 25.5MB。

**使用 Android Studio 中 Analyze Bundle 功能計算的下載體積**

展示模組體積對比的圖表

但……這些值只展示了生成的 Android App Bundle 檔案,並沒有計算 Google Play 動態下發(上文討論過)節省的體積。觀察特定裝置下載體積最準確的方式是在 Google Play 開發者控制檯 中。上傳 App Bundle 後,就可以在 ‘Release Management’ -> ‘Artifact Library’ 看到特定裝置的下發包體積:

計算結果是……

可以看到我們達到了 10MB 的目標,下載體積只有 9.21MB!相比 2017 年 60MB 的應用,我們減少了 85% 的體積! ??

高畫質的實際截圖

普惠眾生

希望本文展示了遷移到 App Bundle 可以帶給使用者的巨大收益。儘管分離模組並不是什麼舉手之勞,但好的程式碼實踐諸如高內聚低耦合也會收益良多。

關於上面的數字還有一小點要注意的是,其中也有我們使用的其他體積壓縮技術的功勞,包括 asset 壓縮和遷移到 R8。我們會在下篇文章中討論這些。

  • 讀者可能會好奇為什麼是 26 個小時而不是 24?這是因為國際日期變更線 並不是一條直線。吉里巴斯的時區是 UTC+14,這意味著它和豪蘭島和貝克島(UTC-12 時區)間有 26 小時的時差。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章