- 原文地址:Enabling ProGuard in an Android Instant App
- 原文做者:Wojtek Kaliciński
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:JayZhaoBoy
- 校對者:hanliuxin5
更新於 2018–01–18: 指南第五步中的重要更新,是對非基礎模塊的必要補充html
把一個已經存在的應用程序轉換成 Android Instant App(安卓即時應用程序)是頗有挑戰性的,但對於模塊及結構化你的項目而言倒是一個很好的練習,更新 SDKs(開發工具包)並遵照全部的 Instant Apps(即時應用程序)沙箱限制以確保即時應用程序的安全和更快的加載速度。前端
其中一項限制規定,對於即時應用處理的每一個 URL,傳送到客戶端設備上的功能模塊和基本模塊的總大小不得超過 4 MB 字節。java
想一下你的項目中可能存在的典型的 common(公共) 模塊(在 Instant Apps(即時應用程序)術語中,咱們將稱這個模塊爲 base feature(基礎功能) 模塊):它可能依賴於支持庫的許多部分,包含 SDK,圖像加載庫,公共網絡代碼等等。這些大量的代碼一般只是爲了啓動,所以不能爲實際功能模塊代碼和資源留出足夠的空間來解決 4 MB 字節的限制。linux
這裏有許多通用和 安卓即時程序專用(AIA 意爲 Android Instant Apps)的技術能夠減小 APK 大小,你應該都去了解一下,但使用 ProGuard(混淆)來移除未使用的代碼對 nstant Apps(即便應用程序)而言倒是必不可少的,經過丟棄那些你歷來不會使用的導入庫和代碼將有助於縮減全部的這些依賴。android
即便對於常規項目配置 ProGuard(混淆)也是頗有挑戰性的,更況且是 Instant App(即時應用),當你啓動的時候,你幾乎確定會遇到構建失敗或者程序崩潰的狀況。當 ProGuard(混淆)集成到 Android 構建中時,新的 com.android.feature
Gradle 插件(用於構建 AIA (安卓即時應用程序)模塊)根本不存在,而且 ProGuard(混淆)沒有考慮模塊在運行時如何加載在一塊兒。ios
幸運的是,你能夠一步一步按照下面的流程進行操做,這樣能夠更輕鬆地爲你的 Instant App(即時應用程序)配置 ProGuard(混淆),本文將對此進行概述。git
在一個典型的場景中,在模塊化應用程序並使用新的 Gradle 插件後,您的項目結構將以下所示:github
一個典型的多功能安裝 + 即時應用程序項目。shell
在共享的即時應用程序/可安裝應用程序項目中,功能模塊替換舊的 com.android.library
模塊。後端
當構建一個可安裝的應用程序時,ProGuard(混淆)會在構建過程結束時運行。功能模塊的行爲與庫類似,它們都將代碼和資源提供給編譯的最後階段,在應用程序模塊中這些都發生在將全部東西打包成一個 APK 以前。在這種狀況下,ProGuard(混淆)可以分析你的整個代碼庫,找出哪些類被使用,哪些能夠被安全地刪除。
**在即時應用程序構建中,每一個功能模塊都會生成本身的 APK。**所以,與可安裝的應用程序構建相反,ProGuard(混淆)能夠獨立運行在每一個功能模塊的代碼中。例如:base feature 編譯,代碼縮減和打包發生時無需查看 feature 1 和 2 中包含的任何代碼。
簡單地說:若是你的 base feature 包含的公共元素(例如 AppCompat 小部件)僅在功能 1 和/或功能 2 中使用但並未在基本功能自己中,則這些元素將被 ProGuard(混淆)刪除,致使運行時崩潰。
如今咱們明白了爲何 ProGuard(混淆)會失敗了,是時候解決這個問題了:確保咱們爲項目配置添加必要的保留規則,以防止在不一樣模塊(在一個模塊中定義,在另外一箇中使用)之間的類被移除或混淆。
這是最困難的部分,也是惟一不容易復現的部分,由於每一個項目所需的 ProGuard(混淆)配置規則會有所不一樣。我建議在處理 ProGuard(混淆)錯誤前熟讀 Android Studio 文檔,ProGuard (混淆)手冊 以及個人上一篇文章 。
接下來咱們將在即時應用程序 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」)將全部功能模塊指向單個文件。
咱們須要從功能 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…
複製代碼
咱們差很少完成了,但還有一個細節須要咱們處理。有時咱們偶爾會使用 Android 資源中的類,例如從 XML 佈局文件中實例化一個小部件,但實際上從未實際從代碼中引用該類。
在已安裝的應用程序構建中,AAPT(處理資源構建的一部分)會自動爲你處理。它爲資源文件和 Android Manifest 中使用的類生成所需的 ProGuard(混淆)規則,但在構建即時應用程序的狀況下,它們最終可能會出如今錯誤的模塊中。
要解決這個問題,首先要啓用 ProGuard(混淆)來開發即時應用程序(例如使用剛剛在前面步驟中設置的構建方式)。而後進入每一個模塊的構建文件夾,找到 aapt_rules.txt
文件(查看與此相似的路徑:build / intermediates / proguard-rules / feature / release / aapt_rules.txt
)並將其內容複製並粘貼到你的aia-proguard-rules.pro
配置中。
如今看來,我在個人指南中遺漏了一個重要的(如今很明顯就發現了)的點。因爲非基本模塊會被獨立地 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"
}
複製代碼
若是你按照步驟 1 中進行了最初的 ProGuard(混淆)設置,而且正確執行了步驟 2-4,那麼到目前爲止,你應該擁有一個較小的,通過優化的即時應用,該應用不會因 ProGuard(混淆)問題而崩潰。請記住經過運行應用程序並檢查全部可能的狀況來完全進行測試,由於某些錯誤只能在運行時發生。
但願本指南可以讓你更好地理解爲何 ProGuard(混淆)可使你的即時應用程序崩潰。遵循這些步驟應該能帶你完成構建,並防止你的即時應用程序崩潰。
你能夠在 GitHub 上看看最新的一些使用 ProGuard(混淆)配置的即時應用示例 來和你的相比較,或者練習本文中介紹的相關示例項目的方法。
我認可能夠經過設置每一個方法的保留規則而不是每一個類來改進上面的解決方案(引用方法列表的命令是:apkanalyzer dex packages detail-debug.apk | grep"^ M r"| cut - f4
),這可能節省出更大的空間。但這會讓本教程的其他部分(例如篩選框架類)變得更加複雜,因此我將它做爲練習給讀者你。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。