如何安全設定 Android SDK 的 ProGuard 混淆規則

opoinstall
2026-11-11
5 min read

如何將行動端 SDK 排除在 Android ProGuard 混淆之外? 將 Android SDK 排除在混淆之外,需要先確認該函式庫是否已內建 Consumer ProGuard 規則,接著針對透過反射(Reflection)存取的進入點、Callback 介面、JNI 橋接層與資料模型,加入作用範圍精準的 -keep 與 -keepclassmembers 指令,最後透過 R8 診斷報告與原始 DEX 檢查來稽核保留下來的符號。當 Android 工程團隊為正式環境的 Release 建置啟用程式碼縮減與最佳化時,R8 編譯器會移除未被參照的 Bytecode,並將類別、欄位與方法重新命名為簡短的識別碼。如果行動歸因軟體開發套件(SDK)依賴動態反射或 JNI Callback,且未針對高強度的 Tree-shaking 與重新命名機制進行保護,那麼即使在未壓縮的 Debug 建置中通過所有測試,延遲深度連結(Deferred Deep Linking)、JSON 模型反序列化以及渠道包建置仍可能在正式環境中無預警失效。OpoInstall 作為行動歸因與深度連結平台,提供原生 Android SDK 套件與渠道包工作流程,方便工程團隊在啟用 R8 壓縮的 Release 流程中進行設定與驗證。

在現代 Android Gradle Plugin (AGP) 工具鏈中,保留規則檔案(舊版 DSL 中的 proguard-rules.pro,或 AGP 9.3+ 中的 src/<variant>/keepRules/*.keep)是 Google R8 編譯器的規則設定契約。安全地將第三方 Android SDK 排除在混淆之外,意味著僅保留執行階段所需的特定公開 API 進入點、反射建構子、Callback 介面、JNI Upcall 與模型欄位,同時避免使用範圍過大的整個套件保留規則,或未經查證的警告抑制指令。

術語 定義 關聯實體 搜尋意圖角色
Android SDK 整合至 Android App 中的已編譯 Java 或 Kotlin 函式庫,用於提供安裝歸因等特定功能。 classes.dex Bytecode 導覽型 / 交易型
ProGuard / R8 Android 建置期工具,使用 ProGuard 規則語法來縮減未使用的程式碼、最佳化 Bytecode 並混淆識別碼。 proguard-rules.pro 技術型 / 資訊型
Consumer ProGuard Rules 封裝在 .aar(/proguard.txt)或 .jar(/META-INF/proguard/)內部的混淆規則,會自動合併至 R8 設定中。 consumerProguardFiles 技術型 / 疑難排解
R8 Diagnostic Reports 輸出檔案(mapping.txt、seeds.txt、usage.txt、configuration.txt),詳細記錄符號重新命名、符合的保留種子(Keep Seeds)以及被剔除的程式碼。 Release 建置稽核 技術型 / 疑難排解

為何 R8 與 ProGuard 混淆會破壞 Android SDK 的歸因 Callback

Android R8 最佳化在 Release 編譯期間保留關鍵的 SDK 符號。

Debug 與 Release 的差異陷阱:為何歸因在測試時正常,上線後卻失效

在行動用戶獲取的發佈工程中,最常見的退化問題(Regression)之一,就是應用程式的延遲深度連結(handleDeepLink)與安裝參數還原(getInstallParam)在 Debug 版本的 QA 測試期間運作穩定,但一旦編譯為已簽章的 Release APK 或 Android App Bundle (.aab),就會回傳空的 Payload 或停止觸發 Callback。

根本原因在於 Gradle 中設定的建置類型(Build Type)差異。預設情況下,debug 建置類型會停用程式碼縮減以加速增量建置。當工程師在啟用 R8 最佳化的情況下編譯 release 變體時,Android Gradle Plugin 會呼叫 Android R8 編譯器,針對宿主 App 與連結的第三方函式庫執行三項 Bytecode 轉換:

  • 程式碼縮減(Code Shrinking / Tree Shaking):R8 會從宣告的進入點(例如 AndroidManifest.xml 元件與明確的 -keep 種子)開始分析應用程式的呼叫圖(Call Graph),並剔除任何在靜態分析中看似無法觸及的類別、建構子、方法或欄位。
  • Bytecode 最佳化與存取權限修改:R8 會內嵌(Inline)方法、移除未使用的參數、合併類別,並且在搭配 Android 預設最佳化設定中的 -allowaccessmodification 指令時,修改類別與成員的可見度旗標(例如在內嵌期間將 private 或套件私有成員放寬為 public),藉此縮減 Dalvik 執行檔(.dex)的大小並提升執行階段的運算速度。
  • 識別碼混淆(重新命名與重新封裝):R8 會將剩餘的類別、套件、欄位與方法重新命名為簡短的識別碼(例如 a.a.b()),並可能將混淆後的類別重新封裝至扁平化的套件階層中,以降低 classes.dex 內部的字串池(String Pool)開銷。

自 Android Gradle Plugin (AGP) 8.0 起,系統預設已啟用 R8 Full Mode(完整模式)。相較於舊版的 ProGuard 相容模式,R8 Full Mode 套用了更為嚴格的最佳化機制——除非相關類別與成員被符合的保留規則明確留存,否則系統會移除預設的無參數建構子(<init>())、更改成員存取修飾詞,並剔除中繼資料屬性(Metadata Attributes)。若需了解 .aar 二進位結構與 SDK 評估標準的基礎背景,請參閱我們的配套指南:了解行動 SDK 的定義與技術成長協定以及如何選擇支援精準歸因的軟體開發套件。

混淆後歸因 SDK 常見的六種失效模式

Android SDK 的反射 Callback、資料模型與 JNI 需要針對性的 R8 保留規則。

由於靜態呼叫圖分析無法預測執行階段的動態查找,未受保護的 Android SDK 在 R8 Full Mode 下可能會出現六種不同的失效模式。工程師必須仔細區分這些模式,因為每一種模式都需要特定的 ProGuard 保護目標:

執行階段失效模式 底層 R8 Bytecode 成因 所需的特定保護目標
1. 反射類別查找失敗(ClassNotFoundException) 透過 Class.forName() 以字串名稱動態載入的類別,被 R8 重新命名、重新封裝或剔除 使用 -keep class ... 保留目標類別及其原始識別碼
2. 反射物件實體化失敗(NoSuchMethodException: <init>) 在 R8 Full Mode 中,即使保留了類別,若其預設無參數建構子(<init>())未在程式碼中被直接呼叫,仍會被剔除 在類別的 -keep 區塊內明確保留建構子簽章(<init>();)
3. 動態 Callback 與存取權限修改失敗 R8 移除或重新命名了 Callback 介面方法,或更改了反射呼叫 getDeclaredMethod() / getDeclaredField() 所預期的成員可見度旗標 保留 Callback 介面方法(-keep interface ... { <methods>; })以及任何透過反射存取的成員
4. Callback 監聽器中的泛型簽章中繼資料遺失 Java 泛型在編譯期會經歷型別擦除(Type Erasure),而 Bytecode 的 Signature 屬性會保留泛型中繼資料供執行階段反射使用(如 ResultCallBack<OpoData>)。R8 Full Mode 除非明確設定保留,否則會剔除 Signature 中繼資料 除了保留泛型 Callback 介面與目標模型類別之外,還需加上 -keepattributes Signature
5. JSON 資料模型反序列化失敗 R8 剔除了模型類別(若僅透過反射實體化),或將其欄位重新命名為 a、b、c,導致基於反射的 JSON 解析器回傳 null 或空欄位 若模型是動態實體化的,請使用 -keep class ...;若該類別已透過直接參照而被留存,則使用 -keepclassmembers
6. JNI Downcall 與 Native-to-Java Upcall 被移除(UnsatisfiedLinkError / NoSuchMethodError) 預設的 Android 規則會保留留存下來的 Java native Downcall 名稱,但無法防止未使用的 RegisterNatives 方法被縮減,也無法察覺 C/C++ .so 程式碼透過 JNI(FindClass、GetMethodID)回呼 Java 的行為 明確使用 -keep 保護動態註冊的 native 宣告,以及任何由 C/C++ 透過 JNI Upcall 呼叫的 Java 類別、建構子與方法
[Android Release Build: R8 Full Mode Enabled]
                      |
                      v
     [R8 Evaluates Bundled & Host Keep Rules]
                      |
      +---------------+---------------+
      |                               |
      v (Missing / Mismatched Rules)  v (Scoped Keep Rules + Attributes Active)
[SDK Symbols Stripped or Renamed] [Required SDK Entry Points & Models Preserved]
- Reflective () Removed     - Confirmed in -printseeds (seeds.txt)
- JNI Upcall / Model Fields Lost  - Verified in Raw DEX (apkanalyzer dex packages)
- Generic Signature Metadata Lost - Release Attribution Matches Debug Behavior

AAR 套件內建的 Consumer 規則與手動匯入 JAR 的差異

AAR 與 JAR 函式庫如何封裝 Consumer ProGuard 規則

Android AAR 與 JAR 套件會將 Consumer 規則匯入至 R8 設定中。

在向宿主應用程式手動新增排除規則之前,請先檢查第三方 Android SDK 是否已內建 R8 規則。根據 Google Android 開發者指南:函式庫最佳化中的說明,AGP 與 R8 支援在 .aar 與 .jar 散佈套件中自動探索 Consumer 規則,其中也包含針對特定版本的 R8 規則:

SDK 散佈格式 內嵌 Consumer 規則位置 Android Gradle Plugin / R8 建置行為
Android Archive (.aar) AAR 根目錄下的 /proguard.txt(透過 consumerProguardFiles),或 /classes.jar 內部 /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ 下的版本限定規則 在編譯期自動解壓縮並合併至宿主 App 的 R8 設定中
內建規則的 Java Archive (.jar) /META-INF/proguard/*.pro 或 /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/(包含 r8、r8-from-<X> 與 r8-upto-<Y> 變體) 由 AGP 與 R8 根據當前啟用的 R8 編譯器版本自動探索並套用
未內建規則的獨立 .jar 或 .aar 套件內部無規則檔案 不提供內建的 Consumer 保護;僅在執行階段反射、JNI、序列化或其他動態存取的符號需要明確保留時,才在宿主端新增保留規則

(關於 Gson 序列化規則的說明:根據 Gson 針對 ProGuard 與 R8 的疑難排解指南,Gson 2.11.0 及後續版本已針對 TypeToken 與標註了 @SerializedName 的欄位內建 R8 規則。但這些規則並不能取代對反射模型存取的限制、提供相容的無參數建構子,或是在壓縮後的 Release 建置中測試所有序列化與反序列化路徑的必要性。)

在撰寫規則前先檢查下載的 SDK 套件

ProGuard 與 R8 的規則比對嚴格區分大小寫,且套件路徑必須完全吻合。在不同的 SDK 版本或地區散佈套件中,套件命名空間(Namespace)可能會有所差異——如果您從舊版教學中複製了規則,而其套件大小寫與您套件內實際的 .class 檔案不符,R8 將會安靜地略過且不比對出任何類別。

在編輯保留規則之前,請先下載目標 SDK 套件(例如從 行動 SDK 下載中心下載 Android SDK v2.1.0),並一併檢查其內嵌規則(包含 /proguard.txt 與 classes.jar 內部)以及確切的 .class 階層結構:

# 1. Inspect an .aar archive for root consumer rules (proguard.txt) and extract classes.jar to check META-INF rules
unzip -l opoinstall-android-sdk.aar
unzip -p opoinstall-android-sdk.aar proguard.txt
unzip -p opoinstall-android-sdk.aar classes.jar > temp_classes.jar
jar tf temp_classes.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"

# 2. If using a standalone .jar archive, inspect embedded META-INF rules and exact case-sensitive .class paths
jar tf libs/OpoInstall_SDK.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"

為歸因 Android SDK 設定 proguard-rules.pro

避免整個套件保留的反模式(Anti-Patterns)與無差別的警告抑制

在撰寫自訂規則時,工程師必須遵循 Google 的 選擇要保留的內容(R8 保留規則)最佳實踐,以避免不必要地停用最佳化功能:

  • 優先使用精準規則,避免 -keep class package.** { *; } 這類涵蓋整個套件的反模式:涵蓋整個套件的廣泛規則會強制 R8 保留該套件中的所有內部輔助類別,並略過對它們的最佳化。請將規則範圍縮小至執行階段會動態存取的特定類別、建構子(<init>();)、Callback 介面、JNI 目標與模型欄位(或者在您可控的程式碼上使用 Jetpack 的 @UnconditionallyKeep 註解)。
  • 在資料模型上區分 -keep 與 -keepclassmembers:-keepclassmembers 僅在包含該成員的類別本身已留存在呼叫圖中時,才會保留指定的欄位或方法。如果資料模型(例如 OpoData)僅透過反射進行實體化,或僅透過已被擦除的泛型型別進行參照,請使用 -keep class ...,以免 R8 將類別本身移除。
  • 了解 -keepclasseswithmembernames 對 JNI 的限制:Android 的預設最佳化規則包含 -keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; }。根據 Google R8 保留規則文件與 ProGuard 保留指令手冊的定義,-keepclasseswithmembernames 等同於 -keepclasseswithmembers,allowshrinking。它會為通過縮減階段而留存下來的符合類別與成員保留名稱,但不會防止未被引用的類別或 native 方法在縮減期間被移除。如果原生方法是透過 RegisterNatives 動態註冊且沒有直接的 Java 呼叫點,或者 C/C++ 程式碼透過 JNI Upcall 呼叫 Java 方法,您必須根據目標類別的實體化方式,明確使用 -keep 或 -keepclassmembers 規則。
  • 避免使用未經查證的廣泛 -dontwarn 指令:-dontwarn 對於防止程式碼縮減或重新命名毫無保護作用,且會抑制未解析的參照警告,進而掩蓋執行階段的 NoClassDefFoundError 崩潰風險。僅當建置日誌回報特定遺失的僅限編譯期(Compile-only)類別,且您已確認該類別在執行階段為非必要項目時,才加入範圍精準的 -dontwarn。
ProGuard / R8 指令 等效的完整語法 縮減(移除) 混淆(重新命名) 建議的工程適用範圍
-keep class ... -keep 防止移除(保留類別與指定成員) 防止重新命名(保留原始名稱) 公開的 SDK 進入點、Callback 介面,以及透過反射或 JNI 動態實體化的類別(需包含 <init>(...);)
-keepclassmembers class ... -keepclassmembers 僅在類別被留存時保留成員 防止符合的成員被重新命名 為已在 Java/Kotlin 中直接實體化的類別保留 <fields>; 或 JNI Upcall 方法
-keepclasseswithmembernames -keepclasseswithmembers,allowshrinking 若未被參照則允許移除(allowshrinking) 若類別與成員在縮減後留存,則防止重新命名 當 Java 程式碼直接呼叫 native 方法時,用於預設的 JNI Downcall 名稱保留(native <methods>;)
-keepnames class ... -keep,allowshrinking 若未被參照則允許移除(allowshrinking) 若類別被留存,則防止重新命名 為堆疊追蹤(Stack Traces)保留可讀的類別名稱,同時不強制將未使用的類別打包進 classes.dex
-keepattributes ... -keepattributes 保留已留存項目上的中繼資料 不適用(Bytecode 屬性) 在明確保留的類別上留存 Signature(泛型)、InnerClasses、EnclosingMethod 與 *Annotation*
-dontwarn <specific_class> -dontwarn 對縮減無影響 對重新命名無影響 僅針對已驗證且非必要的編譯期相依項目抑制編譯期警告

跨 AGP 8.x 與 AGP 9.3+ 工具鏈設定 Gradle

Android Gradle Plugin 9.3 引入了簡化的 optimization DSL 區塊與特定變體專屬的 keepRules 目錄(.keep 檔案),而 AGP 8.x 至 9.2(以及 AGP 9.3+ 中仍支援的舊版設定)則使用 isMinifyEnabled 與 proguardFiles。工程師應清楚區分以下三個設定物件:

設定物件 檔案或 DSL 位置 在 R8 建置中的架構角色
舊版 DSL 保留規則(AGP 8.x–9.2 及舊版 9.3+) app/proguard-rules.pro(透過 proguardFiles(...) 參照) 與 getDefaultProguardFile("proguard-android-optimize.txt") 並列宣告宿主端的保留規則
新版 Optimization DSL 保留規則(AGP 9.3+) app/src/main/keepRules/*.keep 或 app/src/<variant>/keepRules/*.keep 在使用 optimization { enable = true } 時宣告 .keep 檔案(系統會自動套用預設的 Android 平台規則;舊版 DSL 亦支援 .keep Source Sets)
R8 診斷報告指令 位於已驗證的 R8 設定檔(proguard-rules.pro)中的 -printseeds、-printusage、-printconfiguration 指示 R8 將稽核報告輸出至 app/build/outputs/mapping/<buildVariant>/
// Option A: AGP 8.x - 9.2 Legacy DSL (also supported in AGP 9.3+) — app/build.gradle.kts
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

// Option B: AGP 9.3+ Simplified Optimization DSL — app/build.gradle.kts
// Place scoped class/member keep rules in app/src/main/keepRules/sdk-attribution.keep
android {
    buildTypes {
        release {
            optimization {
                enable = true
            }
        }
    }
}

精準保留規則與診斷指令範例

由於 OpoInstall 的公開文件與歷年 FAQ 頁面曾提及不同的套件前綴(如 com.opoinstall.api.** 與 com.fm.OpoInstall.** 或 io.OpoInstall.**),下方的規則集僅供參考範例——並非針對 OpoInstall SDK v2.1.0 驗證過的正式環境規則集。請先檢查您下載的 v2.1.0 套件內部實際的類別路徑與內建的 Consumer 規則,並查閱 OpoInstall 技術文件(關於底層 Java 生命週期程式碼,請參閱軟體開發套件如何驅動行動歸因與業務成長):

# File: app/src/main/keepRules/sdk-attribution.keep (or app/proguard-rules.pro)
# IMPORTANT: Illustrative only — not a verified production rule set for OpoInstall SDK v2.1.0.
# Verify exact case-sensitive package and class names inside your downloaded v2.1.0 archive
# via `jar tf` or `apkanalyzer`, and check `configuration.txt` for existing consumer rules.

# ---------------------------------------------------------------------------
# 1. Preserve Bytecode Attributes on Explicitly Kept Classes
# Required so generic callback interfaces (e.g., ResultCallBack<OpoData>)
# retain their parameterized type metadata in R8 Full Mode.
# ---------------------------------------------------------------------------
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*

# ---------------------------------------------------------------------------
# 2. Scoped SDK Entry Point, Callback Interface, and Data Model Rules
# ---------------------------------------------------------------------------

# Preserve public SDK entry point and any reflectively invoked constructors/methods:
-keep public class com.opoinstall.api.OpoInstall {
    <init>(...);
    public <methods>;
}

# Preserve callback listener interfaces and their callback methods:
-keep public interface com.opoinstall.api.ResultCallBack {
    public <methods>;
}

# Model Scenario A: If OpoData / OpoError models are instantiated dynamically via reflection
# or referenced only via erased generics, use -keep to preserve both the class and its members:
-keep class com.opoinstall.api.OpoData {
    <init>(...);
    <fields>;
    public <methods>;
}
-keep class com.opoinstall.api.OpoError {
    <init>(...);
    <fields>;
    public <methods>;
}

# Model Scenario B: If a model class is already directly retained in your host call graph
# and only its fields require protection from renaming, use -keepclassmembers:
# -keepclassmembers class com.opoinstall.api.OpoData { <fields>; }

# ---------------------------------------------------------------------------
# 3. JNI Downcall and Native-to-Java Upcall Protection (Illustrative JNI Patterns)
# ---------------------------------------------------------------------------
# JNI Case A: If Java/Kotlin code already directly instantiates the JNI bridge class,
# use -keepclassmembers,includedescriptorclasses for C/C++ upcall methods, and keep
# any parameter model classes/constructors instantiated by native code:
-keepclassmembers,includedescriptorclasses class com.example.sdk.internal.JniBridge {
    public void onNativeAttributionEvent(com.example.sdk.internal.NativeEventData);
}
-keep class com.example.sdk.internal.NativeEventData {
    <init>(...);
}

# JNI Case B: If native C/C++ code dynamically looks up or instantiates the Java class
# itself via FindClass / NewObject, or registers native methods via RegisterNatives
# without direct Java call sites, use -keep to prevent class and method removal:
-keep,includedescriptorclasses class com.example.sdk.internal.DynamicNativeBridge {
    <init>(...);
    public native <methods>;
    public void onNativeCallback(java.lang.String);
}
# File: app/proguard-rules.pro (Diagnostic Output Directives)
# Place R8 diagnostic report flags in your verified R8 configuration file.
# Paths resolve relative to the module working directory (`app/`).
# Adjust the directory name for product flavors (e.g., build/outputs/mapping/prodRelease/).
-printseeds build/outputs/mapping/release/seeds.txt
-printusage build/outputs/mapping/release/usage.txt
-printconfiguration build/outputs/mapping/release/configuration.txt

解決 Android 渠道包的混淆與加固衝突

區分建置期 R8 編譯、APK 對齊、簽章與渠道包生成

針對 Google Play 以外的 Android 發佈管道,OpoInstall 支援匯出專屬的 Android 渠道包——系統會以開發者上傳的基礎 APK 為基準,生成帶有不同 channelCode 追蹤中繼資料的特定渠道 APK。

在排查匯出的渠道包為何遺失渠道統計數據或安裝失敗時,工程師必須將兩項獨立的發佈驗證標準區分開來——APK 加密簽章的有效性與供應商渠道中繼資料的完整性:

  1. 建置期的 R8 程式碼縮減:R8 會在基礎 APK 被打包與簽章之前,於 Gradle 建置流程內部將 Java/Kotlin Bytecode 編譯為 classes.dex 時執行。R8 絕不會在已經匯出的 APK 上執行。
  2. Android APK 對齊(zipalign)與加密簽章(apksigner):由 AGP 建置的標準 Release APK 會自動完成對齊。然而,如果您的發佈流程在簽章前套用了自訂的後處理或第三方 APK 加固,您必須依照官方的 Android zipalign 規範與 Android apksigner 規範重新對齊 APK。zipalign 會將未壓縮的資料對齊至 4-byte 邊界——且若 APK 包含未壓縮的原生 .so 函式庫,團隊應使用 zipalign -P 16 -f -v 4 infile.apk outfile.apk 以滿足 16 KB 分頁對齊(Page Alignment)要求,並透過 zipalign -c -P 16 -v 4 驗證對齊狀態。zipalign 必須在使用 apksigner 簽章之前執行,因為在簽章後修改 APK 會使其 V2/V3 完整 APK 加密簽章失效。
  3. 供應商專屬的渠道中繼資料與簽章驗證:在從 OpoInstall 控制台匯出渠道 APK 之後,若再執行第三方 APK 加固(加殼)或重新打包工具,可能會覆寫內嵌的渠道中繼資料或導致 APK 簽章失效。如果第三方渠道包解析器要求特定的 APK 簽章配置(例如在啟用 V2/V3 完整 APK 簽章的同時一併啟用 V1 JAR 簽章),請在修改正式環境簽章設定前,直接對照您當前的 OpoInstall 控制台文件或聯繫供應商支援團隊確認該需求,切勿將 V1 簽章視為 Android 作業系統的通用要求。

Android 渠道包的建議發佈流程順序

為確保 APK 簽章有效、R8 Bytecode 最佳化正常運作,並維持供應商渠道中繼資料的完整性,請嚴格依照以下順序執行您的發佈流程:

  1. 使用 R8 編譯並壓縮基礎 APK:搭配已驗證的保留規則執行 ./gradlew :app:assembleRelease,讓 R8 在建置期完成 classes.dex 的最佳化。
  2. 完成所有上傳前的加固、對齊與簽章作業:若您的資安團隊使用第三方 APK 加固工具,請先對基礎 APK 進行加固,接著執行 zipalign -P 16 -f -v 4(若在簽章前需要重新對齊),使用 zipalign -c -P 16 -v 4 驗證對齊狀態,最後為基礎 APK 進行簽章。
  3. 上傳基礎 APK 並獨立驗證匯出的渠道 APK:將已簽章且混淆的基礎 APK 上傳至 OpoInstall 控制台,匯出渠道 APK,並在公開發佈前,針對匯出的 APK 獨立驗證加密簽章有效性(apksigner verify --verbose)與實體裝置上的渠道歸因功能。
[Recommended Channel Package Pipeline]:
Source Code -> Gradle R8 Minification -> (Optional Pre-Upload Hardening + zipalign -P 16 + Signing) -> Upload Base APK to Console -> Export Channel APK -> Verify Both Signature (apksigner) & Channel Callback

使用 R8 報告與 APK Analyzer 稽核保留的 SDK 符號

在 AGP 9.3+ 中使用獨立的 R8 設定分析器

在 Android Gradle Plugin 9.3 及後續版本中,您無需等待完整的 Release APK 或 App Bundle 編譯完成,即可稽核保留規則的合併與比對結果。AGP 9.3+ 提供了專屬的 analyze<Variant>R8Config Gradle 任務(例如 ./gradlew :app:analyzeReleaseR8Config),能快速評估 Consumer 與宿主端的保留規則並輸出設定診斷資訊,讓工程師在執行完整 Release 建置前先行迭代調整 .keep 規則。

交叉比對 configuration.txt、seeds.txt、usage.txt 與 mapping.txt

透過 Android R8 報告與原始 DEX 檢查來驗證渠道 APK 的發佈完整性。

切勿僅因為 ./gradlew :app:assembleRelease 在沒有建置錯誤的情況下完成,就假設 -keep 規則已成功生效。若要確切驗證您的設定,請檢查生成於 app/build/outputs/mapping/<buildVariant>/ 目錄下的四個 R8 診斷檔案(在排查為何特定符號被留存時,可使用 -whyareyoukeeping class <ClassName>):

  • configuration.txt:包含 R8 實際套用的完整合併規則集——結合了您的宿主端保留規則、預設 Android 平台規則、AAPT2 規則,以及連結的 .aar 與 .jar 函式庫內部封裝的任何 /proguard.txt 或 /META-INF/ 規則。
  • seeds.txt:列出符合當前啟用的 -keep 規則的所有類別、建構子(<init>())、方法與欄位。請注意,出現在 seeds.txt 中僅確認了規則語法有比對成功,仍須搭配 DEX 檢查與執行階段測試。
  • usage.txt:列出 R8 在 Tree-shaking 期間從 APK 中剔除的無用程式碼(Dead Code)。請確認您所需的 SDK 類別、反射建構子或 JNI Upcall 方法皆未出現在 usage.txt 中。
  • mapping.txt:記錄留存類別與成員在混淆前後的識別碼對照表。請注意,未出現在 mapping.txt 中並不代表該類別已以原始名稱保留下來——它可能已在縮減期間被完全移除(並列於 usage.txt 中)。請務必將 mapping.txt 與 seeds.txt、usage.txt 及編譯後的 DEX 檔案進行交叉比對。

使用 apkanalyzer 驗證原始 DEX 識別碼與反混淆報告狀態

在使用官方的 Google Android 開發者 APK Analyzer CLI 檢查 Release APK 時,請區分編譯後的原始 DEX 識別碼與透過 Mapping 反混淆後的名稱:

  • 檢查編譯後的原始 DEX 名稱(不帶 --proguard-mappings):當您將 --proguard-mappings mapping.txt 傳遞給 apkanalyzer 時,該工具會將混淆後的 DEX 名稱(例如 a.b.c)轉換回原始碼名稱以供顯示。因此,在啟用 --proguard-mappings 時看到原始類別名稱,並不能證明該符號在 classes.dex 中未被重新命名。請先在不帶 --proguard-mappings 的情況下執行 apkanalyzer dex packages。此外,由於 R8 的類別重新封裝功能可能會將類別移至扁平化的套件路徑中,請勿僅依賴對套件名稱進行 grep;請檢查原始 DEX 類別樹、驗證反射進入點的類別名稱、檢查 JNI 方法名稱與描述符簽章(Descriptor Signatures),並交叉比對 mapping.txt,以確認必要的方法與欄位未被重新命名。
  • 檢查 R8 留存狀態(帶入 --proguard-mappings、--proguard-seeds 與 --proguard-usages):在第二輪檢查中,同時提供這三個 R8 報告檔案,以重建每個套件(P)、類別(C)、方法(M)與欄位(F)的留存旗標(k 代表由種子規則保留、x 代表在縮減期間被移除、r 代表被參照、d 代表已定義):
# Run from the Android project root directory (adjust variant path for product flavors, e.g., prodRelease)

# 1. (AGP 9.3+) Run the standalone R8 configuration analyzer for rapid rule verification, then build release APK
./gradlew :app:analyzeReleaseR8Config
./gradlew :app:assembleRelease

# 2. Inspect the merged R8 configuration, matched seeds, and stripped usage reports
grep -i -C 3 "opoinstall" app/build/outputs/mapping/release/configuration.txt
grep -i "opoinstall" app/build/outputs/mapping/release/seeds.txt
grep -i "opoinstall" app/build/outputs/mapping/release/usage.txt

# 3A. Inspect RAW compiled DEX identifiers (WITHOUT --proguard-mappings)
# Also inspect the full raw DEX tree and cross-check mapping.txt in case R8 repackaged classes
apkanalyzer dex packages app/build/outputs/apk/release/app-release.apk | grep -i "opoinstall"

# 3B. Inspect R8 retention state flags (k = kept, x = removed, r = referenced, d = defined) WITH R8 report files
apkanalyzer dex packages \
  --proguard-mappings app/build/outputs/mapping/release/mapping.txt \
  --proguard-seeds app/build/outputs/mapping/release/seeds.txt \
  --proguard-usages app/build/outputs/mapping/release/usage.txt \
  app/build/outputs/apk/release/app-release.apk | grep -i "opoinstall"

# 4. Verify 16 KB page alignment (if native .so files are present) and cryptographic APK signatures
zipalign -c -P 16 -v 4 app/build/outputs/apk/release/app-release.apk
apksigner verify --verbose app/build/outputs/apk/release/app-release.apk

Release 建置歸因疑難排解矩陣

診斷壓縮後 Android 建置中的無預警歸因與深度連結失效

在排查僅發生於壓縮後 Release 建置的歸因異常時,請使用下方的診斷矩陣來定位根本原因:

觀察到的 Release 異常現象 可能的 R8 / 建置根本原因 診斷驗證步驟 修正措施
getInstallParam 或 handleDeepLink Callback 在 Release 建置中從未觸發 由於套件名稱不符,導致 SDK 進入點類別、反射 <init>() 建構子或 ResultCallBack 方法被剔除或重新命名 檢查 seeds.txt 與 usage.txt;檢查原始的 apkanalyzer dex packages 輸出(不帶 --proguard-mappings)與 mapping.txt 使用 jar tf 檢查 .jar / .aar 以找出區分大小寫的確切類別路徑,並加入包含 <init>(...); 的精準 -keep 規則
Callback 有觸發,但 OpoData 自訂參數解析為空值或 null OpoData 僅受 -keepclassmembers 保護(導致類別本身被剔除),或泛型的 Signature 中繼資料遺失 在 usage.txt 與 mapping.txt 中檢查 OpoData,並在 configuration.txt 中確認是否有 -keepattributes Signature 若 OpoData 是透過反射實體化,請使用帶有 <init>(...); <fields>; 的 -keep class ...,並保留 -keepattributes Signature
當 SDK 載入 .so 函式庫時發生 UnsatisfiedLinkError 或遺失原生 Callback 動態註冊的 RegisterNatives Downcall 被縮減,或 C/C++ 對 Java 的 Upcall 目標方法被 R8 重新命名 檢查 logcat 找出確切遺失的 JNI 符號,並檢查原始的 apkanalyzer dex packages 與 mapping.txt 中的目標 Java 類別 針對 JNI Upcall 與 RegisterNatives 目標,使用帶有 includedescriptorclasses 的 -keep(若橋接類別已在 Java 中直接實體化,則使用 -keepclassmembers)
匯出的 Android 渠道包安裝失敗或遺失渠道統計數據 匯出後的 APK 修改作業導致 APK 簽章失效,或覆寫了內嵌的渠道中繼資料 對匯出的渠道 APK 執行 zipalign -c -P 16 -v 4 與 apksigner verify --verbose,並在實體裝置上進行測試 在將基礎 APK 上傳至控制台之前,完成所有 R8 壓縮、加固、對齊與簽章作業;並在匯出後同時驗證簽章與歸因功能

常見問題 (FAQ)

如何將行動端 SDK 排除在 Android ProGuard 與 R8 混淆之外?
首先,請檢查您的 `.aar`(`/proguard.txt` 與 `classes.jar/META-INF/com.android.tools/`)或 `.jar`(`/META-INF/proguard/` 與 `/META-INF/com.android.tools/`)套件,並查看 `configuration.txt` 以確認是否已內建 Consumer ProGuard 規則。若需要手動設定規則,請針對區分大小寫的確切 SDK 進入點類別、反射建構子(`<init>(...);`)、Callback 介面、JNI 目標以及模型的 `<fields>;`,新增作用範圍精準的 `-keep` 與 `-keepclassmembers` 指令,並搭配 `-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*`。
如何驗證我的 Android SDK 類別已成功排除在 R8 混淆之外?
請在 R8 設定中啟用 `-printseeds`、`-printusage` 與 `-printconfiguration`(或在 AGP 9.3+ 中執行 `./gradlew :app:analyzeReleaseR8Config`),並建置您的 Release APK。確認目標 SDK 類別與建構子有出現在 `seeds.txt` 中,且未出現在 `usage.txt` 中。接著,在**不帶** `--proguard-mappings` 的情況下執行 `apkanalyzer dex packages app-release.apk`,並交叉比對 `mapping.txt`,確認編譯後的原始 DEX 符號保留了未混淆的原始名稱與方法簽章;最後再帶入 `--proguard-mappings`、`--proguard-seeds` 與 `--proguard-usages` 執行第二次 `apkanalyzer`,以稽核留存狀態旗標(`k`、`x`、`r`、`d`)。
為何在進行 APK 加固或後處理之後,Android 渠道包的統計數據會失效?
當您從歸因控制台匯出 Android 渠道包時,渠道識別中繼資料會被內嵌至匯出的 APK 中。如果您隨後對該匯出的渠道 APK 執行第三方 APK 加固、加殼或重新簽章工具,這些後處理步驟可能會導致 APK 的加密簽章失效,或覆寫供應商的渠道中繼資料。請務必在將基礎 APK 上傳至控制台*之前*,完成 R8 壓縮、加固、`zipalign` 與簽章作業,並使用 `apksigner verify --verbose` 驗證匯出的渠道 APK。

總結與 Release 建置驗證檢查表

了解如何將行動端 SDK 排除在 Android ProGuard 與 R8 混淆之外,是防止從 Debug 測試轉換至正式環境 Release 建置時發生無預警歸因退化的關鍵。透過驗證內建的 Consumer 規則、撰寫與 SDK 套件內區分大小寫的確切類別路徑相符的精準 -keep 指令、遵守 Android 渠道包的上傳前建置順序,以及將 R8 診斷輸出檔案與原始 DEX Bytecode 進行交叉比對,Android 工程師即可在維持完整程式碼縮減效益的同時,確保延遲深度連結與參數還原功能正常運作。

在將壓縮後的 Android Release 建置發佈至 Google Play 或匯出渠道包之前,請執行以下四步驟驗證檢查表:

  • 檢查 SDK 套件與合併後的設定:使用 jar tf 或 unzip -l 檢查下載的 SDK 套件(例如 v2.1.0),確認確切的類別路徑與內建的 Consumer 規則,並執行 ./gradlew :app:analyzeReleaseR8Config(適用於 AGP 9.3+)或檢視 configuration.txt。

  • 交叉比對 R8 報告與原始 DEX 識別碼:確認必要的 SDK 進入點、反射 <init>() 建構子、OpoData 模型欄位、ResultCallBack 方法與 JNI 簽章皆有出現在 seeds.txt 中、未出現在 usage.txt 中,且在原始的 apkanalyzer dex packages 輸出(不帶 --proguard-mappings 執行)與 mapping.txt 中保留了原始名稱。

  • 落實上傳前壓縮並驗證渠道 APK 簽章:在將基礎 APK 上傳以匯出 Android 渠道包之前,完成所有 R8 壓縮、任何第三方加固、zipalign(zipalign -c -P 16 -v 4)與 APK 簽章作業,並對匯出的套件執行 apksigner verify --verbose。

  • 執行實體裝置 Release 冒煙測試(Smoke Test):將已簽章且壓縮的 Release APK 安裝至實體 Android 裝置上,同時驗證 H5 帶參安裝(getInstallParam)與一鍵喚醒(handleDeepLink)功能。

工程團隊可前往 OpoInstall 開發者控制台註冊應用程式並上傳混淆後的基礎 APK,以測試渠道包匯出流程並驗證 Release 建置的參數還原功能。

相關參考資料

Share this article