在現代 Android 開發中,提供小巧、快速且安全的應用程式是基本使用者期望。Android 建構系統的主要工具是 R8 最佳化工具,這個編譯器會處理無效程式碼和資源移除作業,以縮減程式碼、重新命名或壓縮程式碼,以及最佳化應用程式。
啟用 R8 是準備發布應用程式的重要步驟,但開發人員必須以「保留規則」的形式提供指引。
閱讀本文後,請觀看 YouTube 上的「成效焦點週」影片,瞭解如何啟用、偵錯及排解 R8 最佳化工具的問題。
為何需要保留規則
之所以需要編寫 Keep 規則,是因為存在核心衝突:R8 是靜態分析工具,但 Android 應用程式通常依賴動態執行模式,例如使用 JNI (Java Native Interface) 反射或呼叫原生程式碼。
R8 會分析直接呼叫,建構「已使用」程式碼的圖表。以動態方式存取程式碼時,R8 的靜態分析無法預測,因此會將該程式碼識別為「未使用」並移除,導致執行階段當機。
「保留規則」是針對 R8 編譯器的明確指令,指出「這個特定類別、方法或欄位是進入點,會在執行階段動態存取。即使找不到直接參照,也必須保留。」
撰寫保留規則的位置
應用程式的自訂保留規則會寫入文字檔。按照慣例,這個檔案的名稱為 proguard-rules.pro,位於應用程式或程式庫模組的根目錄中。然後在模組的 build.gradle.kts 檔案的 release 建構類型中指定這個檔案。
release {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
使用正確的預設檔案
getDefaultProguardFile 方法會匯入 Android SDK 提供的預設規則集。如果使用錯誤的檔案,應用程式可能無法發揮最佳效能。請務必使用 proguard-android-optimize.txt。這個檔案提供標準 Android 元件的預設保留規則,並啟用 R8 的程式碼最佳化功能。舊版只提供保留規則,但不會啟用 R8 的最佳化功能。proguard-android.txt
由於這是嚴重的效能問題,我們將從 Android Studio Narwhal 3 Feature Drop 開始,警告開發人員使用錯誤的檔案。從 Android Gradle 外掛程式 9.0 版開始,我們不再支援舊版 proguard-android.txt 檔案。因此請務必升級至最佳化版本。
如何撰寫 Keep 規則
保留規則包含三個主要部分:
- 選項 ,例如
-keep或-keepclassmembers - 選用修飾符,例如
allowshrinking - 類別規格,定義要比對的程式碼
如需完整語法和範例,請參閱「新增保留規則」指南。
保留規則反模式
瞭解最佳做法很重要,但也要知道反模式。這些反模式通常源自誤解或疑難排解捷徑,可能會對正式版建構的效能造成災難性影響。
全域選項
這些旗標是全域切換開關,絕不應在發布子版本中使用。這些記錄僅供暫時偵錯,以找出問題。
有效使用 -dontotptimize 會停用 R8 的效能最佳化功能,導致應用程式速度變慢。
使用 -dontobfuscate 時,您會停用所有重新命名作業,而使用 -dontshrink 則會關閉無效程式碼移除作業。這兩項全域規則都會增加應用程式大小。
為提升應用程式使用者體驗,請盡可能避免在正式環境中使用這些全域標記。
過於寬鬆的保留規則
如要讓 R8 的優勢失效,最簡單的方法就是編寫過於寬鬆的保留規則。如下所示的規則會指示 R8 最佳化工具,不要縮減、模糊化或最佳化這個套件或其任何子套件中的任何類別。這會完全移除該套件的 R8 優勢。建議改為撰寫範圍較窄且具體的保留規則。
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS
反轉運算子 (!)
反轉運算子 (!) 似乎是從規則排除套件的強大方式。但事情並非如此簡單。舉例來說:
-keep class !com.example.my_package.** { *; } // USE WITH CAUTION
您可能會認為這項規則表示「不要保留 com.example.package 中的類別」,但實際上是指「保留整個應用程式中不在 com.example.package 中的所有類別、方法和屬性」。如果您感到意外,建議檢查 R8 設定中是否有任何否定。
Android 元件的多餘規則
另一個常見的錯誤,是手動為應用程式的 Activities、Services 或 BroadcastReceivers 新增 Keep 規則。這是不必要的。預設 proguard-android-optimize.txt 檔案已包含這些標準 Android 元件的相關規則,因此可直接使用。
此外,許多程式庫都會提供自己的 Keep 規則。因此您不必為這些項目編寫自己的規則。如果使用的程式庫發生 Keep 規則問題,建議與程式庫作者聯絡,瞭解問題所在。
遵守規則最佳做法
瞭解不該做的事後,接著來看看最佳做法。
撰寫範圍較窄的保留規則
良好的保留規則應盡可能具體明確。他們應只保留必要項目,讓 R8 最佳化其他所有項目。
| 規則 | 品質 |
|---|---|
| 低:保留整個套件及其子套件 |
| 低:保留整個類別,可能還是太寬 |
| 高:只保留特定類別的相關方法和屬性 |
使用共同祖先
不必為多個不同的資料模型編寫個別的 Keep 規則,只要編寫一項規則,指定通用的基礎類別或介面即可。下列規則會告知 R8 保留實作這個介面的類別成員,而且可大幅擴充。
# Keep all fields of any class that implements SerializableModel
-keepclassmembers class * implements com.example.models.SerializableModel {
<fields>;
}
使用註解指定多個類別
建立自訂註解 (例如 @Serialize),並用來「標記」需要保留欄位的類別。這是另一種乾淨、宣告式且高度可擴充的模式。您也可以為現有註解建立保留規則,這些註解來自您使用的架構。
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}
選擇合適的 Keep 選項
保留選項是規則中最重要的一環。如果選錯,可能會不必要地停用最佳化功能。
| 保留選項 | 功能 |
-keep | 防止系統移除或重新命名宣告中提及的類別和成員。 |
-keepclassmembers | 防止指定成員遭到移除或重新命名,但允許移除類別本身,但僅限於未以其他方式移除的類別。 |
-keepclasseswithmembers | 組合:保留課程和成員,前提是所有指定成員都在場。 |
如要進一步瞭解保留選項,請參閱保留選項說明文件。
允許使用修飾符進行最佳化
allowshrinking 和 allowobfuscation 等修飾符會放寬廣泛的 -keep 規則,將最佳化功能交還給 R8。舉例來說,如果舊版程式庫強制您在整個類別中使用 -keep,您可以允許縮減和混淆處理,以回收部分最佳化:
# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it. -keep,allowshrinking,allowobfuscation class com.example.LegacyClass
新增全域選項,進一步最佳化
除了保留規則,您還可以在 R8 設定檔中新增全域標記,進一步提升最佳化效果。
-repackageclasses 是強大的選項,可指示 R8 將所有模糊處理的類別移至單一套件。這項功能會移除多餘的套件名稱字串,大幅節省 DEX 檔案空間。
-allowaccessmodification 可讓 R8 擴大存取權 (例如從 private 擴大至 public),以便啟用更積極的內嵌作業。使用 proguard-android-optimize.txt 時,這項功能現在預設為啟用。
警告:程式庫作者絕不得將這些全域最佳化標記新增至消費者規則,否則系統會強制將這些標記套用至整個應用程式。
為進一步釐清,Android Gradle 外掛程式 9.0 版將完全忽略程式庫的整體最佳化標記。
程式庫最佳做法
每個 Android 應用程式都會以某種方式依附程式庫。因此,我們來談談程式庫的最佳做法。
程式庫開發人員
如果程式庫使用反射或 JNI,您有責任為消費者提供必要的保留規則。這些規則會放在 consumer-rules.pro 檔案中,然後自動與程式庫的 AAR 檔案綁在一起。
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
...
}
程式庫消費者
篩除有問題的保留規則
如果必須使用包含有問題的保留規則的程式庫,可以從 AGP 9.0 開始,在 build.gradle.kts 檔案中篩除這些規則。這樣一來,R8 就會忽略來自特定依附元件的規則。
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}
最佳的保留規則就是沒有保留規則
最終的 R8 設定策略是完全不需要編寫保留規則。如要達成這個目標,請選擇偏好程式碼生成而非反射的現代程式庫。有了程式碼生成功能,最佳化工具就能更輕鬆地判斷執行階段實際使用的程式碼,以及可移除的程式碼。此外,不使用任何動態反射表示沒有「隱藏」進入點,因此不需要 Keep 規則。 選擇新程式庫時,請一律優先採用使用程式碼生成而非反射的解決方案。
如要進一步瞭解如何選擇程式庫,請參閱「明智地選擇程式庫」。
偵錯及排解 R8 設定問題
如果 R8 移除了應該保留的程式碼,或是 APK 大小超出預期,請使用這些工具診斷問題。
找出重複和全域的 Keep 規則
由於 R8 會合併數十個來源的規則,因此很難知道「最終」規則集為何。在 proguard-rules.pro 檔案中加入這個標記,即可產生完整報表:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
您可以搜尋這個檔案,找出多餘的規則,或將有問題的規則 (例如 -dontoptimize) 追溯至包含該規則的特定程式庫。
詢問 R8:為什麼要保留這個?
如果應用程式中仍有您預期會移除的類別,R8 可以告訴您原因。只要新增這項規則即可:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
建構期間,R8 會顯示導致保留該類別的確切參照鏈,方便您追蹤參照並調整規則。
如需完整指南,請參閱「排解 R8 問題」一節。
後續步驟
R8 是一項強大的工具,可提升 Android 應用程式效能。但前提是必須正確瞭解其運作方式 (靜態分析引擎)。
撰寫特定成員層級的規則、善用祖先和註解,並謹慎選擇合適的保留選項,即可保留必要內容。最先進的做法是選擇以程式碼生成為基礎的現代程式庫,取代以反射為基礎的舊版程式庫,完全消除規則需求。
在「成效焦點週」期間,請務必觀看今天在 YouTube 上發布的焦點週影片,並繼續參加 R8 挑戰。如有啟用或排解 R8 相關問題,請使用 #optimizationEnabled。我們很樂意提供協助。
現在就來親身體驗這些優點吧!
我們誠摯邀請您立即為應用程式啟用 R8 完整模式。
- 請按照開發人員指南開始操作:啟用應用程式最佳化功能。
- 檢查您是否仍使用
proguard-android.txt,並將其替換為proguard-android-optimize.txt。 - 接著,評估成效。別只「感覺」差異,請「驗證」差異。如要評估效能提升幅度,請改編 GitHub 上的 Macrobenchmark 範例應用程式程式碼,測量前後的啟動時間。
我們相信應用程式效能會大幅提升。
歡迎使用社群媒體標記 #AskAndroid 提出問題。在整個星期中,我們的專家都會監控並回答您的問題。
敬請期待明天的內容,我們將討論以基準和啟動設定檔為基礎的設定檔導向最佳化,分享 Compose 算繪效能在過去版本中的改善情形,並說明背景作業的效能考量。
繼續閱讀
-
產品新訊
盡可能確保 Google Play 提供最安全可靠的服務體驗。今天,我們宣布推出一系列新政策和帳戶轉移功能,進一步保障使用者隱私,並防範詐欺行為。
Bennet Manuel • 3 分鐘可讀完
-
產品新訊
現在使用 Android Emulator,就能輕鬆測試支援多種裝置的互動。
Steven Jenkins • 閱讀時間:2 分鐘
-
產品新訊
每位開發人員的 AI 工作流程和需求都不盡相同,因此選擇 AI 輔助開發的方式非常重要。我們在 1 月推出這項功能,讓您選擇任何本機或遠端 AI 模型,為 Android Studio 中的 AI 功能提供支援
Matthew Warner • 閱讀時間:2 分鐘
隨時掌握最新消息
每週透過電子郵件接收最新的 Android 開發洞察資料。