[譯] 在 Android Instant App(安卓即時應用程式)中啟用 ProGuard (混淆)

Android_開發者發表於2018-03-19

Instant Apps(即時應用)和 4 MB 位元組的限制

把一個已經存在的應用程式轉換成 Android Instant App(安卓即時應用程式)是很有挑戰性的,但對於模組及結構化你的專案而言卻是一個很好的練習,更新 SDKs(開發工具包)並遵守所有的 Instant Apps(即時應用程式)沙箱限制以確保即時應用程式的安全和更快的載入速度。

其中一項限制規定,對於即時應用處理的每個 URL,傳送到客戶端裝置上的功能模組和基本模組的總大小不得超過 4 MB 位元組。

想一下你的專案中可能存在的典型的 common(公共) 模組(在 Instant Apps(即時應用程式)術語中,我們將稱這個模組為 base feature(基礎功能) 模組):它可能依賴於支援庫的許多部分,包含 SDK,影象載入庫,公共網路程式碼等等。這些大量的程式碼通常只是為了啟動,因此不能為實際功能模組程式碼和資源留出足夠的空間來解決 4 MB 位元組的限制。

這裡有許多通用安卓即時程式專用(AIA 意為 Android Instant Apps)的技術可以減少 APK 大小,你應該都去了解一下,但使用 ProGuard(混淆)來移除未使用的程式碼對 nstant Apps(即使應用程式)而言卻是必不可少的,通過丟棄那些你從來不會使用的匯入庫和程式碼將有助於縮減所有的這些依賴。

即使對於常規專案配置 ProGuard(混淆)也是很有挑戰性的,更何況是 Instant App(即時應用),當你啟動的時候,你幾乎肯定會遇到構建失敗或者程式崩潰的情況。當 ProGuard(混淆)整合到 Android 構建中時,新的 com.android.feature Gradle 外掛(用於構建 AIA (安卓即時應用程式)模組)根本不存在,並且 ProGuard(混淆)沒有考慮模組在執行時如何載入在一起。

幸運的是,你可以一步一步按照下面的流程進行操作,這樣可以更輕鬆地為你的 Instant App(即時應用程式)配置 ProGuard(混淆),本文將對此進行概述。

問題剖析 - 兩種不同的構建方式

在一個典型的場景中,在模組化應用程式並使用新的 Gradle 外掛後,您的專案結構將如下所示:

[譯] 在 Android Instant App(安卓即時應用程式)中啟用 ProGuard (混淆)

一個典型的多功能安裝 + 即時應用程式專案。

在共享的即時應用程式/可安裝應用程式專案中,功能模組替換舊的 com.android.library 模組。

當構建一個可安裝的應用程式時,ProGuard(混淆)會在構建過程結束時執行。功能模組的行為與庫相似,它們都將程式碼和資源提供給編譯的最後階段,在應用程式模組中這些都發生在將所有東西打包成一個 APK 之前。在這種情況下,ProGuard(混淆)能夠分析你的整個程式碼庫,找出哪些類被使用,哪些可以被安全地刪除。

**在即時應用程式構建中,每個功能模組都會生成自己的 APK。**因此,與可安裝的應用程式構建相反,ProGuard(混淆)可以獨立執行在每個功能模組的程式碼中。例如:base feature 編譯,程式碼縮減和打包發生時無需檢視 feature 1 和 2 中包含的任何程式碼。

簡單地說:如果你的 base feature 包含的公共元素(例如 AppCompat 小部件)僅在功能 1 和/或功能 2 中使用但並未在基本功能本身中,則這些元素將被 ProGuard(混淆)刪除,導致執行時崩潰。

現在我們明白了為什麼 ProGuard(混淆)會失敗了,是時候解決這個問題了:確保我們為專案配置新增必要的保留規則,以防止在不同模組(在一個模組中定義,在另一箇中使用)之間的類被移除或混淆。

層層深入的解決方案

1. 在你構建你的可安裝程式中啟用 ProGuard(混淆)並修復所有的執行時異常

這是最困難的部分,也是唯一不容易復現的部分,因為每個專案所需的 ProGuard(混淆)配置規則會有所不同。我建議在處理 ProGuard(混淆)錯誤前熟讀 Android Studio 文件ProGuard (混淆)手冊 以及我的上一篇文章

接下來我們將在即時應用程式 ProGuard(混淆)配置來自可安裝應用中的規則。

2. 為你所有的即時應用功能啟用 ProGuard(混淆)

在可安裝的應用程式版本構建過程中,ProGuard(混淆)只執行一次:在使用 com.android.application 外掛的模組中。在即時應用程式構建過程中,我們需要將 ProGuard(混淆)配置新增到所有功能模組,因為它們都會生成 APK。

開啟每個 com.android.feature 模組中的 build.gradle 檔案,併為它們新增以下配置:

android {
  buildTypes {
    release {
      minifyEnabled true
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'aia-proguard-rules.pro'
    }
  }
  ...
}
複製程式碼

在上面的程式碼片段中,我選擇了一個名為 aia-proguard-rules.pro 的檔案用於我的 Android Instant App(安卓即時應用程式)專用 ProGuard(混淆)配置。對於該檔案的初始內容,您應該複製並貼上可安裝應用程式中的規則(從本指南的第 1 步中)。

如果你願意,不必為每個功能建立單獨的規則檔案,您可以使用相對路徑(例如「../ aia-proguard-rules.pro」)將所有功能模組指向單個檔案。

3. 為從程式碼中使用了跨模組的類新增保留規則

我們需要從功能 APKs 中找出使用基本模組中的哪些類。你可以通過檢查來源手動追蹤,但對於大型專案這種方法是不可行的。竅門是使用 Android SDK 中提供的工具來近乎自動化的執行這個操作。

首先,準備好一個除錯版本(或者沒有啟用 ProGuard(混淆)的除錯版本)。解壓 ZIP 檔案(通常在 <instant-module-name> / build / outputs / apks / debug 中找到),以便你可以輕鬆訪問這些 feature 和 base APK。。

$ unzip instant-debug.zip
Archive: instant-debug.zip
  inflating: base-debug.apk
  inflating: main-debug.apk
  inflating: detail-debug.apk
複製程式碼

每個 APK 都包含一個(或多個)classes.dex 檔案,該檔案包含從其構建的模組的所有程式碼。有了關於 DEX 格式和命令列 APK 分析器(一個分析 APK 中 DEX 檔案的工具)的一些知識,我們可以很容易地找到所選模組中哪些被使用了但沒有定義的類。我們來看看 detail 模組的 DEX 內容:

$ ~/Android/Sdk/tools/bin/apkanalyzer dex packages detail-debug.apk
P d 23 37 3216 com.example.android.unsplash
C d 10 20 1513 com.example.android.unsplash.DetailActivity
M d 1  1  70   com.example.android.unsplash.DetailActivity <init>()
...
P r 0 8 196 android.support.v4.view
C r 0 8 196 android.support.v4.view.ViewPager
複製程式碼

輸出結果顯示了 (P)ackages,(C)lasses 以及 (M)ethods(上文第 1 列中的 P / C / M )是被這個檔案所 (d)efined(定義)又或者僅僅被 (r)eferenced(引用)(上文第 2 列中的 s / r )。

referenced 類只能來自兩個地方:Android 框架或其他模組,這取決於...答對了!使用一點 shell 魔法(我在後面的所有命令都是基於 Linux 系統的 bash命令),我們可以得到 ProGuard(混淆)規則中需要保留的類的列表:

$ apkanalyzer dex packages detail-debug.apk | grep "^C r" | cut -f4
com.example.android.unsplash.ui.pager.DetailViewPagerAdapter
com.example.android.unsplash.ui.DetailSharedElementEnterCallback
com.example.android.unsplash.data.PhotoService
android.support.v4.view.ViewPager
android.transition.Slide
android.transition.TransitionSet
android.transition.Fade
android.app.Activity
...
複製程式碼

我們可以通過任何手段擺脫哪些來自框架的類(我們不需要包含這些規則,因為它們不是應用程式 APK 的一部分),比如 android.app.Activity?因此我們可以先通過 SDK 中的 android.jar 獲取框架類的列表來進行過濾:

$ jar tf ~/Android/Sdk/platforms/android-27/android.jar | sed s/.class$// | sed -e s-/-.-g
java.io.InterruptedIOException
java.io.FileNotFoundException
...
android.app.Activity
android.app.MediaRouteButton
android.app.AlertDialog$Builder
android.app.Notification$InboxStyle
複製程式碼

最後使用[comm](https://linux.die.net/man/1/comm) 命令(逐行比較兩個已排序的檔案)列出僅存在於第一個列表中的類,通過管道按照前兩個命令輸出的排序進行輸入:

$ comm -23 <(apkanalyzer dex packages detail-debug.apk | grep "^C r" | cut -f4 | sort) <(jar tf ~/Android/Sdk/platforms/android-27/android.jar | sed s/.class$// | sed -e s-/-.-g | sort)
android.support.v4.view.ViewPager
com.example.android.unsplash.data.PhotoService
com.example.android.unsplash.ui.DetailSharedElementEnterCallback
com.example.android.unsplash.ui.pager.DetailViewPagerAdapter
複製程式碼

唷!誰會不喜歡 shell 中的一些文字處理呢?剩下的就是取出輸出的每一行,並將其轉換為 aia-proguard-rules.pro 檔案中的 ProGuard(混淆)保留規則。 它看起來應該像這樣:

-keep, includedescriptorclasses class android.support.v4.view.ViewPager {
  public protected *;
}
-keep, includedescriptorclasses class com.example.android.unsplash.data.PhotoService {
  public protected *;
}
#and so on for every class in the output…
複製程式碼

4. 為從資原始檔中出現的跨模組類新增保留規則

我們差不多完成了,但還有一個細節需要我們處理。有時我們偶爾會使用 Android 資源中的類,例如從 XML 佈局檔案中例項化一個小部件,但實際上從未實際從程式碼中引用該類。

在已安裝的應用程式構建中,AAPT(處理資源構建的一部分)會自動為你處理。它為資原始檔和 Android Manifest 中使用的類生成所需的 ProGuard(混淆)規則,但在構建即時應用程式的情況下,它們最終可能會出現在錯誤的模組中。

要解決這個問題,首先要啟用 ProGuard(混淆)來開發即時應用程式(例如使用剛剛在前面步驟中設定的構建方式)。然後進入每個模組的構建資料夾,找到 aapt_rules.txt 檔案(檢視與此類似的路徑:build / intermediates / proguard-rules / feature / release / aapt_rules.txt)並將其內容複製並貼上到你的aia-proguard-rules.pro配置中。

5. 新功能:禁用非基本模組中的混淆

現在看來,我在我的指南中遺漏了一個重要的(現在很明顯就發現了)的點。由於非基本模組會被獨立地 ProGuard(混淆),因此這些模組中的類可以在混淆期間輕鬆地分配相同的名稱。

例如,在模組 detail 中,名為 com.sample.DetailActivity 的類變為com.sample.a,而在模組 main 中,類 com.sample.MainActivity 也變為 com.sample.a。這可能會在執行時導致 ClassCastException 或其他奇怪的行為,因為只能有一個結果類將會被載入和使用。

有兩種方法可以做到這一點。更好的方法是在完整的,可安裝的應用程式中重新使用 ProGuard(混淆)對映檔案,但設定和維護起來很困難。更簡單的方法是簡單地禁用非基本特徵中的混淆。因此,由於類和方法名較長,你的 APK 會稍微大一點,但你仍然享受這刪除程式碼帶來的好處,這是最重要的部分。

要為非基本模組禁用混淆處理,請將此規則新增到其ProGuard(混淆)配置中:

-dontobfuscate
複製程式碼

如果你在基本模組和非基本模組之間有共享配置檔案,我建議你建立一個單獨的配置檔案。基礎模組仍然可以使用混淆。你可以在 build.gradle 中指定其他檔案:

release {
  minifyEnabled true
  signingConfig signingConfigs.debug
  proguardFiles getDefaultProguardFile("proguard-android.txt"), "../instant/proguard.pro", "non-base.pro"
}
複製程式碼

6. 構建並測試你的即時應用程式

如果你按照步驟 1 中進行了最初的 ProGuard(混淆)設定,並且正確執行了步驟 2-4,那麼到目前為止,你應該擁有一個較小的,經過優化的即時應用,該應用不會因 ProGuard(混淆)問題而崩潰。請記住通過執行應用程式並檢查所有可能的情況來徹底進行測試,因為某些錯誤只能在執行時發生。


希望本指南能夠讓你更好地理解為什麼 ProGuard(混淆)可以使你的即時應用程式崩潰。遵循這些步驟應該能帶你完成構建,並防止你的即時應用程式崩潰。

你可以在 GitHub 上看看最新的一些使用 ProGuard(混淆)配置的即時應用示例 來和你的相比較,或者練習本文中介紹的相關示例專案的方法。

我承認可以通過設定每個方法的保留規則而不是每個類來改進上面的解決方案(引用方法列表的命令是:apkanalyzer dex packages detail-debug.apk | grep"^ M r"| cut - f4),這可能節省出更大的空間。但這會讓本教程的其餘部分(例如篩選框架類)變得更加複雜,所以我將它作為練習給讀者你。


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

相關文章