使用 R8 最佳化平台程式碼和資源

Android 平台建構系統 (Soong) 會在編譯 DEX 位元碼的目標上執行 R8 編譯器,包括 Android 應用程式 (android_app)、測試 (android_test) 和可安裝的 Java 程式庫 compile_dex: true (例如 services.jar),以縮減、最佳化及移除未使用的程式碼和資源。在靜態程式庫 (android_library 和靜態 java_library) 上,Soong 不會直接執行 R8,而是使用 optimize 屬性區塊附加及傳播消費者保留規則 (export_proguard_flags_files: true) 至靜態連結這些規則的下游 DEX 目標。

對於建構系統映像檔或供應商套件的平台工程師來說,消除過於廣泛的保留規則可直接提升系統健康度:

  • 縮減系統分割區大小:R8 會先移除無效的類別、方法和資源,再將 APK 和 JAR 封裝至 /system、/system_ext、/product 和 /vendor。
  • 編譯後的構件較小:DEX 方法越少,dex2oat 在建構期間或裝置端編譯時產生的 .odex 和 .vdex 檔案就越小。
  • 降低執行階段記憶體壓力:由於 Android 會使用隨選分頁 mmap 呼叫,將 .odex、.vdex 和 APK 資源表對應至程序記憶體,因此縮減二進位檔大小可減少應用程式啟動期間的主要頁面錯誤,並降低程序間的常駐程式碼和資源記憶體。如要進一步瞭解編譯後的應用程式程式碼對裝置記憶體的影響,請參閱「應用程式程式碼就是記憶體」。
  • 更有效率的程式碼整體最佳化:縮小保留限制可讓 R8 內嵌方法、取消虛擬化介面和虛擬呼叫、修剪未使用的欄位,以及在類別之間傳播常數。

在 Soong 中設定 R8 最佳化

在 Android.bp 檔案中,使用 android_app 和可安裝的 java_library 目標 (或 android_library 和靜態 java_library 模組,匯出消費者保留規則) 中的 optimize 屬性區塊,設定 R8 最佳化:

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

下表匯總了 Soong 中最常見的 optimize 屬性 (定義於 build/soong/java/dex.go):

屬性 說明
enabled 控管 R8 是否在目標上執行。所有 android_app 目標的預設值為 true。
shrink 控制 tree shaking (移除沒用到的程式碼),移除無法存取的類別、欄位和方法。對於 android_app 目標,預設為 true (獨立 java_library 和測試模組則為 false,除非明確設定,否則會傳遞 -dontshrink)。
optimize 控制位元碼最佳化,例如方法內嵌、類別合併、常數傳播和無效分支移除。對於 android_app 目標 (由 RELEASE_R8_OPTIMIZE_BY_DEFAULT 發布子版本建構旗標控管),預設值為 true。
obfuscate 控管 ID 縮減和重新命名。預設為 false,以確保歷史相容性,但請盡可能設為 true,以縮減 DEX 大小並啟用更深入的最佳化功能 (請參閱「盡可能啟用模糊處理功能」)。
shrink_resources 程式碼縮減後,從封裝的 APK 移除未使用的資源 (res/ 項目)。這個變數預設為 false。
optimized_shrink_resources 執行整合式 R8 程式碼和資源縮減器管道,讓 R8 在單一階段中共同追蹤程式碼和資源參照。設定 shrink_resources: true 時,預設為 RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (標準平台建構作業中為 true)。
proguard_flags_files 列出包含自訂保留規則的模組專屬 .flags 或 .pro 檔案。如果標準註解或預設值就足夠,請避免新增自訂檔案。
export_proguard_flags_files 將這個程式庫的 proguard_flags_files 傳播至依附於該程式庫的下游模組 (靜態)。
trace_references_from 列出 Java 程式庫隨附目標,其位元碼會參照這個目標。R8 會自動追蹤並保留這些目標。

盡可能啟用混淆處理

在 Soong 中,obfuscate 預設為 false,以維持歷史相容性,但您應盡可能在 android_app 和獨立 DEX 目標上明確設定 obfuscate: true:

  • 縮減 DEX 和 .vdex 足跡:將套件、類別、欄位和方法重新命名為簡短 ID (a、b),可縮減 DEX 字串集區 (string_ids 和 string_data_item) 和型別描述元。由於 Android 會將 .vdex 和 .odex 檔案對應至程序記憶體,因此縮減符號表可直接減少系統分割區大小和執行階段記憶體用量。
  • 更深入的程式碼最佳化:允許 R8 重新命名 ID,即可合併類別、簡化套件,以及在 R8 必須略過的套件中重複使用合成類別,避免名稱衝突。
  • 完整堆疊追蹤符號化:在 Soong 中啟用混淆處理不會降低偵錯能力。針對每個 R8 編譯目標,Soong 會在模組的中繼目錄中發出 proguard_dictionary 對應檔案,將所有模組字典捆綁到建構的 proguard-dict.zip 構件中,將對應雜湊 (--map-id-template) 嵌入 DEX 標頭,並重新編寫類別 SourceFile 屬性 (--source-file-template),以便 retrace 自動符號化堆疊追蹤。

僅針對下列項目保留 obfuscate: false (或明確保留公用 API 名稱):

  • 啟動路徑或 system_server 路徑上的共用程式庫:模組 (java_library 或 java_sdk_library) 會公開外部 API,並在執行階段由其他模組動態連結 (例如 framework.jar、services.jar 或 <uses-library> 目標),因此必須保留 obfuscate: false,或使用 @KeepForApi 或 -keep 規則 (以及啟動路徑目標的 protect_api_surface: true),明確保留 API 介面,這樣針對其存根編譯的呼叫端才能在執行階段解析類別和成員名稱。

在應用程式上啟用 obfuscate: true 前,請先確認下列遷移作業的必要條件:

  • 外部測試 APK 依附元件:如果外部 android_test APK 設定 instrumentation_for: "MyApp" 並直接叫用 MyApp 的內部類別或方法,重新命名這些符號會導致測試執行階段發生 NoSuchMethodError 或 NoClassDefFoundError。建議將測試建構為靜態連結 MyApp.impl 的自我檢測 APK,使用 @VisibleForTesting 註解測試掛鉤,或設定 trace_references_from (請參閱「步驟 2:遷移測試驅動的保留規則」),以便在模糊處理應用程式其餘部分時,保留測試所需的內部符號。
  • 以字串為基礎的反射和 JNI:如果模組透過字串常值名稱 (Class.forName、getDeclaredMethod 或 JNI FindClass 和 GetMethodID) 查閱類別、方法或欄位,但沒有保留規則或註解,混淆處理就會重新命名這些目標,導致執行階段查閱作業中斷。(請注意,未註解的反射也會在 shrink: true 和 optimize: true 下失敗)。請使用「Keep 註解指南」(@UsesReflection、@UsedByReflection 或 @UsedByNative) 註解這些進入點,讓 R8 保留其名稱,並模糊處理模組的其餘部分。

遵循零自訂標記原則

理想的 Android 平台模組不含任何自訂 proguard.flags 或 keep.xml 檔案。在 Android 平台建構作業中,絕大多數的自訂保留規則都是多餘的:

  • 平台基準和 AAPT2:大部分的進入點都已由全域平台基準 (例如 @Keep、JNI native 方法和 @VisibleForTesting) 自動保留,或是由 AAPT2 從 AndroidManifest.xml 和版面配置資源產生 (請參閱「瞭解預設平台保留規則」)。
  • 目標替代方案:如要保留進入點,請優先使用宣告網站註解 (keepanno、@VisibleForTesting)、匯出的程式庫規則 (export_proguard_flags_files: true) 或靜態測試連結,而非獨立的 .flags 檔案 (請參閱「稽核及遷移現有保留規則」)。

新增或保留自訂 proguard.flags 或 keep.xml 檔案前,請先確認預設基準是否已處理該規則,或是否可將規則遷移至程式碼註解。

瞭解預設平台保留規則

Soong 會自動將下列全域基準規則傳遞至 DEX 編譯目標 (android_app、android_test 和可安裝的 java_library 模組) 的每個 R8 叫用,這些目標是在 build/soong/java/dex.go 中設定:

  • build/make/core/proguard.flags:保留以 @com.android.internal.annotations.VisibleForTesting 註解的類別和成員,適用於所有套件,以及 android.**、com.android.** 和 com.google.android.** 套件中的 @VisibleForTesting (androidx.annotation.VisibleForTesting 或 com.google.common.annotations.VisibleForTesting)。也會保留 @TestApi、@Keep (androidx.annotation、android.support.annotation、com.android.internal.annotations)、@KeepForWeakReference、@WeaklyReferencedCallback 和 @dalvik.annotation.optimization.**。
  • build/make/core/proguard_basic_keeps.flags:保留堆疊追蹤的 SourceFile 屬性、執行階段可見性註解 (RuntimeVisible*Annotations)、Exceptions 和 AnnotationDefault 屬性、native 方法、Serializable 成員、@JavascriptInterface 方法、Throwable(String) 建構函式、Parcelable$CREATOR 欄位,以及 protobuf MessageLite 欄位。
  • build/make/core/proguard/kotlin.flags:針對特定 Kotlin 中繼註解 (kotlin.Metadata 和 kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) 停用無害的警告,並在發布版本中移除 Kotlin DebugMetadata 註解。
  • build/make/core/proguard/checknotnull.flags:以簡潔的位元碼空值檢查取代常見的空值檢查輔助函式呼叫 (com.google.common.base.Preconditions.checkNotNull 和 dagger.internal.Preconditions.checkNotNull*)。這個檔案刻意省略 Objects.requireNonNull,以便在架構 API 邊界保留明確的例外狀況訊息。
  • build/make/core/proguard/enumvalues.flags:除非建構設定停用,否則會在 enum 型別上保留 values 和 valueOf 方法。
  • AAPT2 自動產生的規則:AAPT2 會檢查合併的 AndroidManifest.xml、版面配置 XML 和偏好設定 XML 檔案,為每個已註冊的 Activity、Service、BroadcastReceiver、ContentProvider、BackupAgent、Application、自訂 View 子類別、Preference 子類別和 XML android:onClick 方法產生確切的保留規則。

稽核及遷移現有保留規則

在平台存放區稽核現有 proguard.flags 或 keep.xml 檔案時,請依序評估下列四個步驟的階層架構中的每項規則:

步驟 1:刪除多餘或過時的規則

刪除全域基準、AAPT2 資訊清單和版面配置規則,或 R8 預設值已涵蓋的規則,以及參照已不存在的類別或套件的規則。

如果 .flags 檔案中的所有規則都是多餘的,請刪除該檔案並從 Android.bp 中移除 proguard_flags_files。如果其餘 optimize 區塊只會重述預設設定,請移除多餘的區塊,並使用 bpfmt -w Android.bp 格式化建構檔案。清理套件時,請檢查子目錄中的同伴模組 (例如 Kotlin 目標變體) 是否參照相同的旗標檔案,並一併更新。

步驟 2:遷移測試驅動的保留規則

使用下列其中一種模式,將測試驅動的保留規則移出自訂正式版 .flags 檔案:

  • 將實作程式庫連結至測試 (static_libs):當 android_test 練習應用程式的套件私有或內部實作詳細資料時,建議的平台架構是將應用程式來源檔案放在 android_library (MyApp.impl) 中,並將 MyApp.impl 靜態連結至 MyApp 和 MyAppTests:

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    這個 3 個模組的模式可讓 MyAppTests 以自我檢測測試的形式執行,並完整存取內部類別;可讓 MyApp 自由啟用 obfuscate: true,而不會中斷測試;可避免在正式版 APK 中運送僅供測試的進入點;並可避免在測試程式碼變更時重建 MyApp。

  • 使用 @VisibleForTesting 註解測試掛鉤:如果外部測試 APK 在 android_app 目標上呼叫少數內部方法或建構函式,且重組為 .impl 程式庫不切實際,請使用 @VisibleForTesting 註解這些宣告。全球build/make/core/proguard.flags基準會自動保留 android.**、com.android.** 和 com.google.android.** 套件中的項目,無須自訂 .flags 檔案。@VisibleForTesting(如為這些命名空間以外的供應商模組,請使用 @UsedByReflection 或匯出的程式庫規則)。

  • 跨平台程式庫界線使用 trace_references_from:如果測試無法靜態連結目標實作項目 (例如,測試會運用系統伺服器服務或平台 JAR,如 framework-connectivity),請在目標模組上設定 trace_references_from,指向包含測試來源的 Java 程式庫隨附模組。R8 會追蹤並保留隨附位元碼參照的所有類別和成員。

步驟 3:從擁有者程式庫匯出規則

如果共用 java_library 或 android_library 需要保留規則,才能進行內部反射或 JNI 回呼,請在程式庫目標中定義規則,並設定 export_proguard_flags_files: true:

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

所有靜態連結 my-shared-library 的下游 android_app 和程式庫目標都會自動繼承這些規則,因此下游應用程式不需要重複這些規則。如果不同目錄中的多個模組必須共用一組與單一程式碼程式庫無關的規則集,請透過 Android.bp 中的明確 filegroup 發布 .flags 檔案,這樣模組就能在套件界線之間乾淨地參照 :my-shared-flags。如需撰寫程式庫消費者保留規則的一般指引,且不限制下游應用程式最佳化,請參閱「程式庫作者的最佳化做法」。

步驟 4:在原始碼中註解宣告

如果透過反射或 JNI 存取類別、方法、建構函式或欄位,且 AAPT2 或全域基準未涵蓋這些項目,請將 .flags 檔案中已分離的 -keep 規則,替換為「保留註解指南」中的來源註解 (請參閱 keepanno Javadoc 參考資料):

  • @UsesReflection:如果您控管執行反射的程式碼,建議使用這個註解。將其放在反射呼叫網站,宣告要動態存取哪些目標類別、方法或欄位。由於 @UsesReflection 會自動編碼註解呼叫網站本身可連線的先決條件,因此如果呼叫網站未使用,R8 會修剪呼叫端和反射目標。
  • @UsedByReflection:放在由外部程式碼或程式庫 (例如從 Bundle 或 Settings 金鑰載入的 Class.forName 類別,或跨動態類別載入器載入的外掛程式類別) 例項化或以反射方式叫用的類別、方法、欄位或建構函式上。指定 kind (例如 KeepItemKind.CLASS_AND_METHODS)、preconditions (@KeepCondition) 和參數限制,讓 R8 只保留確切的反射合約,並繼續最佳化或修剪類別中未使用的成員。請勿將 @UsedByReflection 新增至資訊清單註冊的元件,例如 JobService 或 BroadcastReceiver,因為 AAPT2 已保留這些元件。
  • @UsedByNative:放在使用 GetMethodID、GetStaticMethodID 或 GetFieldID 從 C 或 C++ JNI 程式碼存取的方法或欄位上。
  • @KeepForApi:放置在程式庫 API 類別或成員上,這些類別或成員在程式庫本身縮減後,必須保持完整才能發布。
  • @Keep (androidx.annotation.Keep):在 keepanno 不適用時做為備用選項。對類別套用 @Keep 時,系統會無條件保留類別及其所有成員。將 @Keep 套用至方法或欄位時,會做為無條件進入點,保留成員及其所含類別,即使類別從未例項化也一樣;而 keepanno 則表示有條件的可存取性。

如要在 Soong 模組中使用 R8 keepanno 註解,請按照下列步驟操作:

  1. 在 Android.bp 中,將 "keepanno-annotations" 新增至 libs:

    libs: [
        "keepanno-annotations",
    ],
    
  2. 匯入 com.android.tools.r8.keepanno.annotations.*,並在 Java 或 Kotlin 原始碼中註解聲明或呼叫位置:

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8 會直接將 keepanno 註解轉換為內部保留規則模型,並從最終 DEX 輸出內容中移除註解,不會增加任何執行階段位元碼負擔。

避免常見錯誤

以下各節說明平台程式碼中常見的 proguard.flags 和 keep.xml 陷阱,以及修正方法。

廣泛元件保留規則

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • 為何會影響效能:這項規則與 AAPT2 完全多餘。 由於它使用萬用字元,且未檢查元件是否已在最終合併的 AndroidManifest.xml 中註冊,因此會強制 R8 保留類路徑上任何位置找到的每個 Activity、Service 或 BroadcastReceiver 子類別,包括未使用的程式庫元件和已停用的偵錯活動。
  • 建議修正方式:刪除規則。AAPT2 會檢查合併的資訊清單,並為已註冊的元件產生確切的 -keep 規則。

廣泛 XML 點擊事件處理常式保留規則

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • 為何會影響效能:保留模組中每個類別的每個 public void *(View) 方法,會導致 R8 無法剝除或內嵌任何接受單一 View 參數的方法。
  • 建議修正方式:刪除規則。AAPT2 會掃描版面配置 XML 檔案,並為 android:onClick 屬性參照的方法產生目標保留規則。

廣泛檢視畫面 getter 和 setter 保留規則

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • 為何會影響效能:這項規則會強制 R8 保留應用程式中每個 View 子類別和所有連結程式庫 (包括 AndroidX 和 Material 程式庫) 的每個 getter 和 setter,導致無法移除無效方法,也無法在 UI 程式碼中內嵌。
  • 建議修正方式:刪除規則。AAPT2 已保留從版面配置 XML 檔案加載的自訂 View 類別建構函式。如果程式碼使用反射字串名稱 (例如 ObjectAnimator.ofFloat(view, "translationZ", ...)) 為檢視畫面屬性建立動畫,請將字串名稱替換為型別屬性參照 (View.TRANSLATION_Z 或自訂 FloatProperty 或 IntProperty 實作),完全避免反射。如果無法避免反射屬性存取,請使用 @UsedByReflection 註解特定 getter 或 setter。

停用全面最佳化或混淆處理

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • 為何會影響效能:在 .flags 檔案中放置 -dontoptimize 或 -dontshrink,會以無聲無息的方式覆寫模組的 Android.bp 設定,並停用整個目標的最佳化傳遞。更糟的是,如果程式庫匯出含有 -dontoptimize 的 .flags 檔案,則連結該程式庫的每個下游 android_app 都會停用 R8 最佳化功能。
  • 建議修正方式:從 .flags 檔案中移除 -dontoptimize、-dontshrink 和 -dontobfuscate。在 Android.bp 中使用 optimize 區塊 (shrink、optimize、obfuscate),明確控管最佳化行為。

已提交規則中的診斷標記

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • 為何會影響建構作業:診斷標記會大量產生建構記錄,或嘗試在沙箱 Soong 建構作業期間,將輸出檔案寫入本機路徑。
  • 建議修正方式:從已提交的 .flags 檔案中刪除這些標記。Soong 會自動將 R8 對應和使用情形輸出內容 (例如 proguard_dictionary 和 proguard_usage.zip) 寫入 out/soong/.intermediates/ 下的模組中繼目錄。

測試程式碼的正式環境保留規則

  • 為何會影響效能:僅為測試存取權新增自訂 -keep 規則,會導致該程式碼在正式版建構中保持未混淆狀態。使用 @VisibleForTesting 註解方法或設定 trace_references_from 可避免維護手動 -keep 規則,但這些符號仍會隨附在正式版二進位檔中。
  • 建議修正方式:如要將僅供測試的進入點完全排除在正式版 APK 之外,請將應用程式架構為 .impl 程式庫,並使用 static_libs 將其連結至自我檢測的測試 APK,如「步驟 2:遷移測試導向的保留規則」所述。

keep.xml 中的資源綜合萬用字元

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • 為何會影響效能:全面性萬用字元會保留模組及其依附元件中相符類型的所有資源,導致資源縮減 (shrink_resources: true) 無效,並膨脹 APK 和對應的 resources.arsc 表格。
  • 建議修正方式:
    • 優先使用靜態資源參照,而非 keep.xml:避免使用 Resources.getIdentifier 方法動態查閱有界限的資源集,例如編號實驗字串或主題可繪項目。請改用編譯時間 switch 陳述式,或對靜態 R.id、R.string 或 R.drawable 常數進行對應。靜態參照可讓 R8 和 AAPT2 追蹤確切的即時資源、消除執行階段字串查閱的負擔,並完全移除 keep.xml。
    • 列出特定資源名稱:如果需要動態查閱 (例如透過第三方授權讀取器),請在 tools:keep 中列出確切的資源 ID (例如 tools:keep="@raw/third_party_licenses")。
    • 刪除多餘的 keep.xml 檔案:如果列出的資源已在程式碼 (R.raw.foo) 或 XML (@raw/foo) 中靜態參照,縮減器會自動保留這些資源,因此您可以刪除 res/raw/keep.xml。

驗證及稽核規則變更

在 proguard.flags 或 keep.xml 中移除或縮減保留規則時,請確認模組可順利建構、通過單元和插樁測試,並保留所有必要進入點。

建構及測試模組

  1. 請乾淨地建構模組,確認 R8 和 AAPT2 完成時不會出現缺少參照的警告:

    m <MODULE_NAME>
    
  2. 使用 atest 執行模組的單元測試和檢測測試:

    atest <TEST_MODULE_NAME>
    
  3. 如果是系統應用程式、具備特殊權限的服務或與硬體相關的模組,請在目標實體裝置或裝置測試實驗室中執行插樁和 UI 測試,以練習執行階段反射路徑、IPC 繫結和資源膨脹。

檢查 DEX 和資源差異

比較規則變更前後編譯的 APK 或 JAR,確認 R8 會移除無效的程式碼和資源,但不會剝除預期的進入點:

  • 使用 apkanalyzer 或 dexdump 檢查輸出 APK 或 DEX 檔案中保留的類別、方法和欄位:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • 修改 keep.xml 或啟用 shrink_resources 時,請使用 aapt2 dump resources 驗證建構作業是否已移除未使用的資源,並保留必要資源:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    

使用 R8 分析保留半徑和規則涵蓋範圍

開放原始碼的 R8 編譯器內含「保留半徑」分析工具 (在 Android Studio 和 Gradle 中也以「R8 設定分析工具」的形式提供),可測量編譯期間每項保留規則的確切影響。Soong 會將這個分析器直接整合到 Android 平台建構作業中 (在 build/soong/java/dex.go 中設定)。

如要分析平台模組或整個建構作業的保留規則,請按照下列步驟操作:

  1. 使用 R8_DUMP_KEEP_RADIUS=true 環境變數執行建構作業:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    設定 R8_DUMP_KEEP_RADIUS=true 後,Soong 會指示 R8 將每個已編譯模組的保留規則指標記錄在 out/soong/.intermediates/ 下的中繼 r8keepradius.pb 檔案中。針對每項保留規則和 keepanno 註解,R8 會記錄:

    • 立即保留半徑:規則保留的確切類別、欄位和方法,以及規則對縮減、最佳化或混淆處理強制執行的特定限制。
    • 規則 subsumption:哪些其他保留規則或註解已保留相同項目。如果自訂規則完全被 AAPT2 規則或全域基準規則取代,即可放心刪除。
    • 套件範圍和全域規則:使用廣泛套件範圍萬用字元或套用全域設定指令的規則。
  2. 使用 KeepRadiusHtmlReportGenerator (隨附於 prebuilts/r8/r8.jar) 將 r8keepradius.pb 輸出內容轉換為互動式 HTML 報表:

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    如果提供目錄,KeepRadiusHtmlReportGenerator 會走訪所有 *keepradius*.pb 檔案,為每個模組產生 HTML 報表,並建立 out/keep_radius_reports/keepradius.html,彙整建構作業中保留的項目數量、保留的項目數量,以及半徑最大的保留規則。如要進一步瞭解如何解讀所產生報告中的縮減、最佳化和模糊化分數,請參閱「使用 R8 設定分析器」。

Soong 也支援 build/soong/java/dex.go 中的兩個額外 R8 診斷環境變數:

  • R8_DUMP_INPUT=true:將 r8inputs.zip 寫入模組的中繼目錄,其中包含所有輸入 JAR、程式庫 JAR,以及合併的 ProGuard 設定,用於獨立 R8 複製作業。
  • R8_DUMP_PERFETTO_TRACE=true:將 r8trace.ptrace 寫入模組的中繼目錄,以便在 Perfetto 中檢查 R8 編譯階段。

驗證程式庫消費者規則

當 java_library 或 android_library 匯出作業將保留規則傳送給下游消費者 (export_proguard_flags_files: true) 時,這些規則不得包含會變更或停用消費者應用程式最佳化的全域標記。

R8 提供開放原始碼的保留規則分析器 (ProcessKeepRules),在 Android 平台建構作業中公開為 process-keep-rules 主機工具 (定義於 prebuilts/r8/Android.bp):

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

process-keep-rules 工具會剖析每個設定檔,如果遇到程式庫消費者規則不允許的指令,就會失敗並提供檔案和行診斷資訊。不允許的指令包括全域最佳化、縮減或模糊化停用 (例如 -dontoptimize 或 -dontshrink)、套件重新封裝和存取修改標記、診斷或對應標記,以及應用程式層級的 -keepattributes。

使用 pgaudit.py 稽核來源樹狀結構

如要使用 R8 的建構時間分析器稽核未建構的來源目錄,Android 平台樹狀結構會在 build/make/core/proguard/tools/ 下方加入 pgaudit.py 指令碼。這項靜態分析工具會掃描存放區中的 Android.bp、proguard.flags 和 keep.xml 檔案,標記 AAPT2 或全域平台基準已涵蓋的規則、廣泛的萬用字元、-dontoptimize 覆寫和僅供測試的保留規則:

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

平台和原始設備製造商 (OEM) 團隊可以結合 pgaudit.py 快速分類來源樹狀結構、process-keep-rules 驗證匯出的程式庫規則,以及 R8_DUMP_KEEP_RADIUS=true 和 KeepRadiusHtmlReportGenerator,測量建構作業中每項規則的確切類別、欄位和方法保留半徑。