產品最新消息

設定 R8 保留規則及排解相關問題

閱讀時間:7 分鐘
查看 Ajesh Pai 的個人資料 查看 Ben Weiss 的個人資料
Ajesh Pai 和 Ben Weiss

在現代 Android 開發中,提供小型、快速且安全的應用程式是使用者的基本期望。Android 建構系統的主要工具是 R8  最佳化工具,這個編譯器會處理無效程式碼和資源移除作業,以便縮減、重新命名或壓縮程式碼,以及最佳化應用程式。

啟用 R8 是準備發布應用程式的重要步驟,但開發人員必須以「保留規則」的形式提供指引。

閱讀本文後,請觀看 Performance Spotlight Week 影片,瞭解如何在 YouTube 上啟用、偵錯及排解 R8 最佳化工具的問題。

 

 

為何需要保留規則

之所以需要編寫保留規則,是因為 R8 是靜態分析工具,但 Android 應用程式通常仰賴動態執行模式,例如反射,或使用 JNI (Java 原生介面) 呼叫原生程式碼。

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 的程式碼最佳化功能。過時的 proguard-android.txt 只會提供 Keep 規則,但「不會」啟用 R8 的最佳化功能。

progaurd.png

由於這是嚴重的效能問題,我們將從 Android Studio Narwhal 3 Feature Drop 開始,警告開發人員不要使用錯誤的檔案。此外,自 Android Gradle 外掛程式 9.0 版起,我們不再支援過時的 proguard-android.txt 檔案。因此請務必升級至最佳化版本。

如何編寫保留規則

保留規則包含三個主要部分:

  1. -keep 或 -keepclassmembers 等選項 
  2. 選用修飾符,例如 allowshrinking
  3. 類別規格:定義要比對的程式碼

如需完整語法和範例,請參閱新增保留規則的操作說明。

Keep 規則反模式

除了最佳做法,瞭解反模式也很重要。這些反面模式通常源自誤解或疑難排解捷徑,可能會對正式版建構作業的效能造成災難性影響。

全域選項

這些旗標是全域切換鈕,絕不應在發布子版本中使用。僅供暫時偵錯,用於找出問題。

使用 -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 新增保留規則。這並非必要。預設的 proguard-android-optimize.txt 檔案已包含相關規則,可讓這些標準 Android 元件直接運作。

此外,許多程式庫都有自己的保留規則。因此您不需要自行編寫規則。如果使用的程式庫保留規則有問題,建議您與程式庫作者聯絡,瞭解問題所在。

Keep 規則最佳做法

瞭解不該做的事後,接著來談談最佳做法。

編寫精確的保留規則

良好的保留規則應盡可能精確具體。他們應該只保留必要的內容,讓 R8 最佳化其他所有內容。

規則品質

 

-keep class com.example.** { ; }

 

低:保留整個套件及其子套件

 

-keep class com.example.MyClass { ; }

 

低:保留整個類別,但範圍可能還是太大
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
高:僅保留特定類別的相關方法和屬性

使用共同祖先

請勿為多個不同的資料模型編寫個別的保留規則,而是編寫一項以通用基礎類別或介面為目標的規則。以下規則會指示 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 設定策略是完全不需要編寫保留規則。許多應用程式只要選擇偏好程式碼產生而非反射的新型程式庫,就能達到這個目標。有了程式碼生成功能,最佳化工具就能更輕鬆地判斷哪些程式碼在執行階段實際使用,哪些程式碼可以移除。此外,不使用任何動態反射,代表沒有「隱藏」的進入點,因此不需要保留規則。 選擇新程式庫時,請一律優先採用使用程式碼生成而非反射的解決方案。

如要進一步瞭解如何選擇程式庫,請參閱「謹慎選擇程式庫」。

對 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 應用程式效能的強大工具。其有效性取決於對其作為靜態分析引擎運作的正確理解。

只要編寫特定的成員層級規則、善用上層和註解,並仔細選擇合適的保留選項,就能準確保留必要內容。最先進的做法是選擇以程式碼生成為基礎的新式程式庫,而非以反射為基礎的舊版程式庫,這樣就完全不需要規則。

在 Performance Spotlight Week 期間,請務必觀看今天的 Spotlight Week YouTube 影片,並繼續挑戰 R8。如有任何關於啟用或排解 R8 問題的疑問,請使用 #optimizationEnabled。我們很樂意提供協助。

現在就來親自體驗這些好處吧!

我們鼓勵您立即為應用程式啟用 R8 完整模式。

  1. 請按照開發人員指南開始操作:啟用應用程式最佳化功能。
  2. 確認您是否仍在使用 proguard-android.txt,並改用 proguard-android-optimize.txt。
  3. 接著,評估成效。不要只感受差異,還要驗證差異。如要評估效能提升幅度,請調整  GitHub 上的 Macrobenchmark 範例應用程式程式碼,評估啟動前後的時間。

我們相信,您會看到應用程式效能有顯著提升。

歡迎使用社群媒體標記 #AskAndroid 提問。我們的專家會在一週內留意並回覆您的問題。

明天我們將討論如何使用基準和啟動設定檔進行設定檔引導最佳化、分享 Compose 算繪效能在過去幾個版本中的改善情況,以及分享背景工作的效能考量。

作者:
繼續閱讀