如何將行動端 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

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 在 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 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 加密簽章的有效性與供應商渠道中繼資料的完整性:
- 建置期的 R8 程式碼縮減:R8 會在基礎 APK 被打包與簽章之前,於 Gradle 建置流程內部將 Java/Kotlin Bytecode 編譯為
classes.dex時執行。R8 絕不會在已經匯出的 APK 上執行。 - Android APK 對齊(
zipalign)與加密簽章(apksigner):由 AGP 建置的標準 Release APK 會自動完成對齊。然而,如果您的發佈流程在簽章前套用了自訂的後處理或第三方 APK 加固,您必須依照官方的 Androidzipalign規範與 Androidapksigner規範重新對齊 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 加密簽章失效。 - 供應商專屬的渠道中繼資料與簽章驗證:在從 OpoInstall 控制台匯出渠道 APK 之後,若再執行第三方 APK 加固(加殼)或重新打包工具,可能會覆寫內嵌的渠道中繼資料或導致 APK 簽章失效。如果第三方渠道包解析器要求特定的 APK 簽章配置(例如在啟用 V2/V3 完整 APK 簽章的同時一併啟用 V1 JAR 簽章),請在修改正式環境簽章設定前,直接對照您當前的 OpoInstall 控制台文件或聯繫供應商支援團隊確認該需求,切勿將 V1 簽章視為 Android 作業系統的通用要求。
Android 渠道包的建議發佈流程順序
為確保 APK 簽章有效、R8 Bytecode 最佳化正常運作,並維持供應商渠道中繼資料的完整性,請嚴格依照以下順序執行您的發佈流程:
- 使用 R8 編譯並壓縮基礎 APK:搭配已驗證的保留規則執行
./gradlew :app:assembleRelease,讓 R8 在建置期完成classes.dex的最佳化。 - 完成所有上傳前的加固、對齊與簽章作業:若您的資安團隊使用第三方 APK 加固工具,請先對基礎 APK 進行加固,接著執行
zipalign -P 16 -f -v 4(若在簽章前需要重新對齊),使用zipalign -c -P 16 -v 4驗證對齊狀態,最後為基礎 APK 進行簽章。 - 上傳基礎 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

切勿僅因為 ./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 混淆之外?
如何驗證我的 Android SDK 類別已成功排除在 R8 混淆之外?
為何在進行 APK 加固或後處理之後,Android 渠道包的統計數據會失效?
總結與 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 建置的參數還原功能。
相關參考資料
-
核心概念:Android R8 程式碼縮減、識別碼混淆、Consumer ProGuard 規則、渠道包建置順序
-
相關技術:Android Gradle Plugin (AGP)、R8 編譯器、
proguard-rules.pro、Android Studio APK Analyzer -
平台與整合介面:
isMinifyEnabled、optimization.enable、analyzeReleaseR8Config、consumerProguardFiles、seeds.txt、usage.txt、apkanalyzer dex packages、apksigner、zipalign -
官方文件與參考文獻:
Share this article



