ProGuard 在 Android 上的使用姿式

爲何使用 ProGuard

ProGuard 是一個壓縮、優化、混淆代碼的工具。儘管有不少其餘工具供開發者們使用,可是 ProGuard 做爲 Android Gradle 構建過程的一部分,已經打包在 SDK 中。html

當咱們構建應用時,使用 ProGuard 有不少好處。有的開發者更關心混淆這塊功能,對我而言最大的用處是打包時移除 dex 中的無用代碼。前端

一個 Android 示例應用的空間分佈圖,源碼地址 Topeka sample appjava

減小包體積的好處有不少,好比增長用戶黏性和滿意度,提高下載速度,減小安裝時間,以便在終端設備上鍊接用戶,尤爲是在新興市場。固然,有時候您不得不限制您的應用的大小,好比 Instant App 限制大小 4 MB,此時 ProGuard 顯得必不可少了。node

若是以上還不足以說服您使用 ProGuard,其實移除無用代碼和混淆全部名稱還有其餘更多的優化效果:react

  • 在一些版本的 Android 設備上,DEX 代碼會在安裝或者運行時被編譯成機器碼。原始的 DEX 和優化後的機器碼都會保留在設備中,因此算一下就知道:代碼越少,意味着編譯時間越短,存儲佔用越少
  • ProGuard 除了能夠大幅減小代碼的空間以外,還能夠讓全部的標識符(包、類和成員)都使用更短的名字,如 a.Aa.a.B。這個過程就是混淆。混淆經過兩種方式來減小代碼:讓表示名稱的字符串更短;在這些方法或者屬性有相同的簽名狀況,下這些字符串更容易被複用,最終減小了字符串池的數目。
  • 使用 ProGuard 是開啓資源壓縮的前提條件. 資源壓縮功能會移除您項目中代碼沒有引用到的資源文件(如圖片資源,這通常是 APK 中佔比最大的部分了).
  • 經過僅將您代碼中實際使用的方法打包到 APK 中,移除代碼會幫您避免 64K dex 方法引用問題。尤爲是您引用了不少第三方庫的時候,這樣能夠大大下降在您應用中使用 Multidex 的需求。

每一個 Android 應用都應該使用代碼壓縮嗎?我認爲是的!android

可是在您激動的跳起來以前,請先繼續閱讀下去。當您開啓 ProGuard 時,在某些很是微妙的狀況下會讓您的應用崩潰。雖然有些錯誤會在構建應用時發生,您能及時發現,可是也有些錯誤您只能在運行時發現,因此請確保您的應用通過完全的測試。ios

如何使用 ProGuard?

在您的項目中開啓 ProGuard 只需簡單到添加以下幾行代碼在您的主應用模塊的 build.gradle 文件中:git

buildTypes {
/* you will normally want to enable ProGuard only for your release
builds, as it’s an additional step that makes the build slower and can make debugging more difficult */
  
  release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
  }
}
複製代碼

ProGuard 自身的配置已經在另一個單獨的配置文件中完成了。上面的代碼中,我給出了 Android Gradle 打包插件中的默認配置¹,接下去我會在 proguard-rules.pro 中加入其餘的配置。github

在 ProGuard 官網您能夠找到一個 使用手冊。 在您深刻研究這些配置以前,最好先大概理解 ProGuard 是如何工做的和咱們爲何要指定一些額外的選項。後端

您也能夠去觀看 part of this Google I/O session Shai Barack 的教學視頻。

簡單來講,ProGuard 將您項目中的 .class 文件作爲輸入,而後尋找代碼中全部的調用點,計算出代碼中全部可達的調用關係圖,而後移除剩餘的部分(即不可達的代碼和那些不會被調用的代碼)。

在您讀 ProGuard 手冊時,您不必看那些 輸入 / 輸出的部分,由於這些 Android Gradle 打包插件會替您指定輸入源(您和第三方庫的代碼) 和 Android jar 庫(您構建應用時用到的 Android 框架類)。

想要正確配置 ProGuard,最重要的就是讓它知道運行時您的哪些代碼不該該被移除(若是開啓混淆的話,固然也要保持他們的名稱不變)。當一些類和方法會被動態訪問到時(如使用反射),在某些狀況下,ProGuard 在構建調用圖時不能正確的決定他們的「生死」,致使這些代碼被錯誤的移除掉。當您只從 XML 資源引用您的代碼會時(一般使用底層的反射),這個狀況也會發生。

在一次 Android 典型的構建過程當中,AAPT(處理資源的工具)會生成一個額外的 ProGuard 規則文件。它會爲 Android 應用添加一些特別的 keep 規則,因此您在 Android Manifest.xml 中記錄的 Activities、Services、BroadcastReceivers 和 ContentProviders 會保持不動. 這就是爲何在上面動圖中 MyActivity 類沒有被被移除或者重命名.

AAPT 也會 keep 住全部在 XML 佈局文件使用到的 View 類(和它們的構造函數)和其餘一些類,如在過渡動畫資源中引用到的過渡類。 您能夠在構建後直接看這個 AAPT 生成的配置文件,位置是:<your_project>/<app_module>/build/intermediates/proguard-rules/<variant>/aapt_rules.txt

在構建時 AAPT 生成的一個示例 ProGuard 配置文件

我會在本文後面章節中討論更多關於 keep 規則,可是在那以前咱們最好先學一下在如下狀況時應該怎麼作:

當 ProGuard 打斷了您的構建

在您能夠測試是否開啓 ProGuard 後全部代碼在運行時都能正常工做前,您須要先構建您的應用。不幸的是,ProGuard 可能會發現一些引用的類缺失,並給予告警,致使您的構建失敗。

修復這個問題的關鍵是仔細觀察構建時輸出的消息,理解這些警告的內容並定位他們。一般的途徑是修正您的依賴或者在您的 ProGuard 配置中添加 -dontwarn 規則。

這些警告的一個緣由就是,您的構建路徑中沒有加入須要依賴的 JARs,如使用了 provided (僅編譯時)依賴。而有時候,在 Android 上這些代碼的依賴在運行時並不會被真正的調用。讓咱們看一個真實的例子。

一個項目依賴 OkHttp 3.8.0 構建時的消息。

OkHttp 庫在 3.8.0 版本的類中添加了新的註解(javax.annotation.Nullable)。可是由於它們使用了編譯時的依賴,因此這些註解在最終構建時不會被打包進去(哪怕應用顯式的依賴了 com.google.code.findbugs:jsr305),所以 ProGuard 會抱怨 缺失了這些類.

由於咱們知道這些註解類在運行時不會被使用,咱們能夠經過在 ProGuard 配置中添加 -dontwarn 規則來安全地忽略掉這些警告,如 在 OkHttp 文檔中加入這些規則

-dontwarn javax.annotation.Nullable  
-dontwarn javax.annotation.ParametersAreNonnullByDefault
複製代碼

您應該經歷過相似的過程,在輸出消息中看到這些警告,而後從新構建直到構建經過。重要的是去理解爲何您會收到這些警告以及您在構建時是否真的缺乏這些類。

如今您可能會嘗試使用 -ignorewarnings 選項直接忽略全部的警告,但這一般不是個好注意。在某些狀況下,ProGuard 的警告確實有助於您發現閃退的罪魁禍首和關於您配置上的其餘問題

您可能須要瞭解一下 Progard的 notes (優先級低於警告的消息),它能夠幫您發現一些反射相關的問題。雖然它不會打斷您的構建,可是在運行時可能會閃退。這會在下面的場景中發生:

當 ProGuard 移除過多的類

在某些狀況下,ProGuard 並不知道一個類或者方法被使用了,例如這個類僅在反射時被使用或者僅在 XML 中被引用。爲了阻止這樣的代碼被移除或混淆,您應當在 ProGuard 配置中指定額外 keep 規則。這取決於做爲應用開發者的你,須要去發現哪些部分代碼有問題並提供必要的規則。

當運行時發生了 ClassNotFoundExceptionMethodNotFoundException 異常意味着您確定缺失了某些類或者方法,也許是 ProGuard 移除了他們,又或者是由於錯誤配置依賴而致使沒法找到他們。因此生產環境的構建(開啓 ProGuard 時)必定要注重完全的測試並正視這些錯誤。

您有不少選項來配置您的 ProGuard:

  • **keep **— 保留全部匹配的類和方法
  • **keepclassmembers **— 當且僅當它們的類由於其餘的緣由被保留時(被其餘調用點引用到或者被其餘的規則 keep 住),keep 住指定的一些成員
  • **keepclasseswithmembers **— 當且僅當全部的成員在匹配的類中存在時,會 keep 住 這些類和它的成員

我建議您從 ProGuard 的這篇 class specification syntax 開始熟悉,此文討論了上述全部的 keep 規則和前一段討論到的 -dontwarn 選項。另外這三個 keep 規則也各有一個不一樣的版本支持僅保留混淆(重命名),不保留壓縮。您能夠在 ProGuard 官網的表格看一下概覽。

做爲一個可選的方案來寫 ProGuard 規則,您能夠直接在某個不想被混淆和移除的類、方法、屬性上添加 @Keep 註解。注意,若是這樣作的話,您須要把 Android 默認的 ProGuard 配置加入到您的構建中。

APK Analyzer 和 ProGuard

Android Studio 集成的 APK Analyzer 能夠幫您看到哪些類被 ProGuard 移除了並支持爲它們生成 keep 規則。當您構建 APK 時開啓了 ProGuard,那麼會額外輸出一些文件在 <app_module>/build/outputs/mapping/ 目錄下。這些文件包含了移除代碼的信息、混淆的映射關係。

加載 ProGuard 映射文件到 APK Analyzer 能夠看到 DEX 視圖中更多的信息

當您加載了映射文件到 APK Analyzer時(點擊 「Load Proguard mappings… 「 按鈕), 您能夠在 DEX 視圖樹中看到一些額外功能:

  • 全部的名字都是混淆前的(即您能夠看到原始的名字)
  • 被 ProGuard 配置規則 kept 的包,類,方法和屬性會顯示成粗體
  • 您能夠開啓 「Show removed nodes」 選項來看任何被 ProGuard 移除的內容(字體上會有刪除線)。右擊樹上的一個節點可讓您生成一個 keep 規則以便您粘貼到您的配置文件中。

當 ProGuard 移除過少的類

全部應用均可以使用 Android 內置的 ProGuard 的一些安全的默認規則,如保留 View 的 getter 和 setter 方法,由於他們一般會被反射來訪問,以及其餘一些普通的方法和類都不會被移除。 這在許多狀況下能夠時您的應用避免崩潰的發生,可是這些配置並非 100% 適合您的應用。您能夠移除掉默認的 ProGuard 文件而使用您本身的。

若是您但願 ProGuard 移除全部未使用的代碼,您應當避免 keep 規則寫的太寬泛,如加入通配符匹配整個包,而是使用類相關的匹配規則或者使用上面說起的 @Keep 註解。

使用 -whyareyoukeeping <class-specification> 選項來觀察爲何這些類沒有被移除。

若是您實在不肯定爲何 ProGuard 沒有移除您指望它移除的代碼,,您能夠添加 -whyareyoukeeping 選項至 ProGuard 配置文件中,而後從新構建您的應用。在構建輸出中,您會看到是什麼調用鏈決定了 ProGuard 保留這些代碼。

在 APK Analyzer 中追蹤是什麼在 DEX 中 keep 住了這些類和方法

另外一種方法不那麼精準,但在任何應用都不須要從新構建和額外的工做量。那就是在 APK Analyzer 中打開 DEX 文件,而後右擊您關注的類、方法。選擇 「Find usages」 您將看到引用鏈,這也許會引導您瞭解哪部分代碼使用指定的類、方法從而阻止了它被移除。

ProGuard 和 混淆後的堆棧

我以前說起到,在構建過程當中 ProGuard 會在處理類文件時輸出映射關係和日誌文件。當您須要保留構建產物時,您應當保存好這些文件和 APK 在一塊兒。這些映射文件不能被其餘的構建所使用,而只會在與它們一塊兒生成的 APK 配合使用時才能確保正確。有了這些映射關係,您纔能有效地 debug 用戶設備的發生的崩潰。不然太難去定位問題了,由於名字都混淆過了。

上傳 APK 對應的 ProGuard 映射文件至 Google Play 控制檯,從而得到混淆前的堆棧信息。

您在 Google Play 控制檯發佈混淆後的生產 APK時,記得爲每一個版本上傳對應的映射文件。這樣的話當您看 ANRs & crashes 頁面時,上報的堆棧都會現實真實的類名、方法名和行號而不是縮短的混淆後的那些。

關於 ProGuard 和 第三方庫

就像您有責任爲您本身的代碼提供 keep 規則同樣,那些第三方庫的做者們也有義務向您提供必要的混淆規則配置來避免開啓 Proguard 致使的構建失敗或者應用崩潰。

有些項目簡單地在他們的文檔或者 README 上說起了必要的混淆規則,因此您須要複製粘貼這些規則到您的主 ProGuard 配置文件中。不過有個更好的方法,第三方庫的維護者們若是發佈的庫是 AAR ,那麼能夠指定規則打包在 AAR 中並會在應用構建時自動暴露給構建系統,經過添加下面幾行代碼到庫模塊的 build.gradle 文件中:

release { //or your own build type  
  consumerProguardFiles ‘consumer-proguard.txt’  
}
複製代碼

您寫入在 consumer-proguard.txt 文件中的規則將會在應用構建時附加到應用主 ProGuard 配置並被使用。


若是想了解更多關於代碼和資源壓縮的信息,請參考咱們的文檔頁面


開啓 ProGuard 可能一開始會比較困難,可是我我的認爲這些代價是值得的。只要投入一點點時間,您將會得到一個輕量、優化後的應用。此外,如今花費時間去配置您的應用意味着當實驗性的 ProGuard 替代者 R8 就緒時,您已經準備好了。由於 R8 也是用現有的 ProGuard 規則文件來工做的。

除了讓您的代碼更小巧以外, ProGuard 和 R8 能夠選擇優化您的代碼讓它運行得更快,固然這又是另外一篇文章的話題了……


¹ proguard-android.txt 文件以前是在 SDK tools 目錄下(SDK/tools/proguard/proguard-android.txt),但在新版的 SDK Tools 和 Android Gradle 插件版本2.2.0+上,能夠在構建時從 Android 插件的 jar 中解壓出來。在構建您的項目後,您能夠在 <your_project>/build/intermediates/proguard-files/ 目錄下找到這個配置文件。

感謝 Daniel Galpin


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索