產品新訊

設定及排解 R8 Keep 規則的相關問題

閱讀時間:7 分鐘
Ajesh Pai & Ben Weiss

在現代 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

progaurd.png

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

如何撰寫 Keep 規則

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

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

如需完整語法和範例,請參閱「新增保留規則」指南。

保留規則反模式

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

全域選項

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

有效使用 -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 元件的多餘規則

另一個常見的錯誤,是手動為應用程式的 ActivitiesServicesBroadcastReceivers 新增 Keep 規則。這是不必要的。預設 proguard-android-optimize.txt 檔案已包含這些標準 Android 元件的相關規則,因此可直接使用。

此外,許多程式庫都會提供自己的 Keep 規則。因此您不必為這些項目編寫自己的規則。如果使用的程式庫發生 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);

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

使用共同祖先

不必為多個不同的資料模型編寫個別的 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組合:保留課程成員,前提是所有指定成員都在場。

如要進一步瞭解保留選項,請參閱保留選項說明文件

允許使用修飾符進行最佳化

allowshrinkingallowobfuscation 等修飾符會放寬廣泛的 -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 完整模式。

  1. 請按照開發人員指南開始操作:啟用應用程式最佳化功能
  2. 檢查您是否仍使用 proguard-android.txt,並將其替換為 proguard-android-optimize.txt
  3. 接著,評估成效。別只「感覺」差異,請「驗證」差異。如要評估效能提升幅度,請改編 GitHub 上的 Macrobenchmark 範例應用程式程式碼,測量前後的啟動時間。

我們相信應用程式效能會大幅提升。

歡迎使用社群媒體標記 #AskAndroid 提出問題。在整個星期中,我們的專家都會監控並回答您的問題。

敬請期待明天的內容,我們將討論以基準和啟動設定檔為基礎的設定檔導向最佳化,分享 Compose 算繪效能在過去版本中的改善情形,並說明背景作業的效能考量。

撰寫者:

繼續閱讀