ProGuard/R8:Android 应用混淆与保护的本质

作者: IT Sectr 发布日期: 2026-02-14 阅读时间: 8 分钟

ProGuardR8 — 用于 Android 应用的混淆、压缩和优化工具。ProGuard 创建于 2002 年,长期以来一直是保护 Java 代码的事实标准。R8 — 它的继任者,由 Google 开发并从 AGP 3.4 开始内置于 Android Gradle Plugin 中。这两种工具都能减小 APK 体积、删除死代码并增加逆向工程的难度。根据 Android Developers,R8 在同等混淆质量下的构建速度比 ProGuard 快 2-3 倍。

要点概述

  • ProGuard — Java 字节码混淆和优化工具,自 2000 年代以来一直是 Android 标准
  • R8 — Google 出品的 ProGuard 继任者,内置于 AGP,一次遍历完成混淆、压缩和优化
  • 混淆 将类和方法的名称重命名为简短名称,增加应用的逆向工程难度
  • 压缩 删除未使用的类、方法和字段,减小最终 APK/AAB 的大小
  • ProGuard rules(.pro 文件)控制代码的哪些部分被保留、混淆或删除

什么是 ProGuard?

ProGuard — 是一个开源(Apache 2.0)工具,用于混淆、压缩、优化和预验证 Java 字节码。由 Eric Lafourge 于 2002 年在 SourceForge 项目框架内开发。ProGuard 接收已编译的 Java 类(.class)或 JAR 存档作为输入,并输出相同格式但体积更小且元素已重命名的已处理类。

长期以来,ProGuard 是保护 Android 应用免受逆向工程的唯一标准。Google 官方推荐在 Android SDK 中使用它,并在 SDK tools 内的 proguard-android-optimize.txt 文件中提供默认配置。ProGuard 作为一个独立工具运行,在 Java 代码编译为字节码之后、打包为 DEX 之前执行。

ProGuard 架构

ProGuard 由四个连续阶段组成:shrink(删除未使用的类)、optimize(字节码优化——内联、删除死代码)、obfuscate(将类、方法和字段重命名为简短名称)、preverify(检查与 JVM 的兼容性)。每个阶段由配置文件中的独立规则控制。

在混淆阶段,ProGuard 生成一个 映射文件(mapping.txt),将原始名称映射到混淆后的名称。该文件对于通过 retrace 工具解码 release 版本中的崩溃日志至关重要。没有映射文件,堆栈跟踪将变成一组字母 a()、b()、c(),无法恢复原始上下文。

ProGuard 阶段目的结果
Shrink分析调用图并删除死代码减少 APK 中的类数量
Optimize内联方法,删除未使用的参数加速代码执行
Obfuscate重命名类、字段和方法防止逆向工程
Preverify为 JVM 添加 StackMap 属性与 Java 6+ 兼容

什么是 R8?

R8 — 是 Google 的下一代混淆和压缩工具,首次在 Android Studio 3.3(2018 年 11 月)中引入,并在 AGP 3.4(2019 年 8 月)中成为标准。与 ProGuard 不同,R8 是将 Java 字节码转换为 DEX 格式的 D8/R8 编译器的一部分。R8 在一个过程中完成所有阶段——混淆、压缩和优化——无需在工具之间传递中间文件。

Google 开发 R8 有两个目标:加快构建速度(ProGuard 作为外部工具运行)并确保与现代 Android 栈(Desugar、Core Library Desugaring、D8)的无缝集成。R8 使用 Kotlin 和 Java 编写,是 AOSP(Android 开源项目)中 R8/Desugar 仓库的一部分。

R8 的一个重要优势——与 ProGuard rules 完全向后兼容。现有的 .pro 文件无需修改即可使用。R8 甚至支持 ProGuard 特定的指令,包括 -whyareyoukeeping、-printconfiguration 和 -printmapping。这意味着从 ProGuard 到 R8 的过渡是透明的:只需更新 AGP 即可。

kotlin
// build.gradle.kts — 通过 minifyEnabled 启用 R8
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            isShrinkResources = true

            proguardFiles(
                // 来自 Android SDK 的基本配置
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // 项目自定义规则
                "proguard-rules.pro"
            )
        }
    }
}

代码演示了 release 构建的标准配置。isMinifyEnabled = true 标志激活 R8 进行混淆和优化。isShrinkResources = true 还删除未使用的资源。getDefaultProguardFile 从 SDK 加载基本规则,而 proguard-rules.pro 包含项目特定的设置。

Android 中的代码混淆

混淆 — 是将源代码转换为人类难以分析但保持完整功能的过程。在 Android 上下文中,混淆意味着将类、方法和字段重命名为简短、无意义的名称:com.example.app.auth.LoginManager 变为 a.a.aauthenticateUser 方法变为 auserToken 字段变为 b

为什么需要混淆

Android APK 文件是可以用任何解压工具(ZIP、7z、WinRAR)打开的存档。没有混淆,攻击者可以获得应用的完整地图:包名、类名、方法名和字段名。像 jadxBytecode Viewer 这样的工具可以在几秒钟内从 DEX 文件中恢复几乎原始的 Java 代码。混淆不会使代码无懈可击,但会显著提高进入门槛:读者看到的不是有意义的名称,而是 a()、b()、c()。

混淆的典型目标:保护商业逻辑(算法、计算公式)、增加 API 密钥和令牌窃取的难度、防止通过反射替换类、防止 APK 的修补和修改(重打包攻击)。在实践中,70% 的任务正是通过重命名解决的——这就是为什么运行 ProGuard / R8 的原因。

ProGuard 规则示例

以下是一个使用 Retrofit、Gson 和 Parcelable 的 Android 项目的典型 proguard-rules.pro 文件。-keep 规则保留通过反射运行库所需的方法和类。没有这些规则,R8 将删除或重命名库通过字符串名称访问的类。

pro
# =====================
# Retrofit — 保留接口
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions

# =====================
# Gson — JSON 序列化
# =====================
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class *.serialization.** {
    <fields>;
}

# =====================
# Parcelable — Creator
# =====================
-keepclassmembers class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

# =====================
# Logging — 从 release 中删除日志
# =====================
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(String, int);
    public static int v(...);
    public static int d(...);
    public static int i(...);
    public static int w(...);
    public static int e(...);
}

# =====================
# Kotlin 数据类 — 保留构造函数
# =====================
-keepclassmembers class * {
    @kotlin.Metadata <fields>;
}

# =====================
# Activity — 入口点
# =====================
-keep class * extends android.app.Activity {
    @android.annotation.SuppressLint <methods>;
}

.pro 文件中的每个指令解决特定的任务。-keep 防止删除或重命名整个类。-keepclassmembers 仅保护类的成员(字段和方法),但允许在不使用时删除类本身。-assumenosideeffects 向 R8 指示方法调用没有副作用,可以安全删除。-keepattributes 指令保留字节码中的元数据——注释、签名、异常。

Retrofit 的 -keep,allowobfuscation,allowshrinking 规则允许 R8 重命名接口,但不能删除它们。这是必要的,因为 Retrofit 通过动态代理(java.lang.reflect.Proxy)访问接口,删除将导致运行时出现 ClassNotFoundException。类似地,Gson 使用反射访问带有 @SerializedName 注释的字段——没有 -keepclassmembers,字段将作为未使用而被删除。

压缩与 ShrinkResources

压缩(shrinking)——从最终构建中删除未使用的代码和资源的过程。ProGuard 和 R8 从入口点(Activity、Service、BroadcastReceiver)开始分析调用图,并删除无法通过调用链到达的类和方法。ShrinkResources——额外的阶段,从 res/(layout、drawable、string、color)中删除未使用的资源。

压缩在使用库的大型项目中带来最大的收益。典型情况:项目只使用所连接库(例如 Google Play Services)代码的 10%。没有压缩,整个库代码都会进入 APK。使用压缩,R8 删除库代码的 70-90%,只留下实际使用的类和方法。这直接影响 APK 大小、加载时间和内存消耗。

ShrinkResources 的作用

ShrinkResources 机制与代码压缩协同工作。在 R8 确定哪些类被使用之后,资源缩减分析来自代码的对资源的引用:R.layout.mainR.drawable.icongetString(R.string.title)。所有没有直接或间接引用的资源都将从最终 APK 或 AAB 中删除。为此,使用 resources.arsc 资源文件和 res/ 文件夹。

一个重要细节:资源可以通过 getIdentifier()Resources.getResourceName() 以字符串名称调用,绕过 R 类。在这种情况下,R8 看不到直接联系,可能会删除实际使用的资源。为了保护此类资源,存在 -keep class **.R$* { *; } 指令——它保留 R 类的所有标识符。

xml
<!-- 示例:仅通过 getIdentifier() 使用的资源 -->
<string name="dynamic_title_welcome">欢迎</string>
<string name="dynamic_title_share">分享</string>

<!-- 通过字符串访问的 Kotlin 代码 -->
<!-- val title = getString(resources.getIdentifier( -->
<!--     \"dynamic_title_${type}\", \"string\", packageName)) -->

在这种情况下,R8 在 R 类中看不到对 dynamic_title_welcome 的静态引用,因为访问是通过 getIdentifier 使用动态名称进行的。要保留此类资源,需要在 proguard-rules.pro 中添加 -keepclassmembers class **.R$string { *; } 指令——它禁止删除所有 R$string 类中的任何字段。

指令目的示例
-keep保留类及其所有成员-keep class com.example.api.** { *; }
-keepclassmembers仅保留类的成员-keepclassmembers class * { @SerializedName <fields>; }
-keepattributes保留字节码的元数据-keepattributes *Annotation*, Signature
-assumenosideeffects删除没有副作用的调用-assumenosideeffects class Log { d(...); }
-dontwarn抑制警告-dontwarn com.example.legacy.**

R8 与 ProGuard:主要区别

尽管 R8 是 ProGuard 的继任者,但两者在架构、性能和行为上存在根本差异。Google 从 AGP 7.0 开始正式停止了对 Android Gradle Plugin 中 ProGuard 的支持,但 ProGuard 仍然在需要 R8 中不可用的特定优化行为的项目中使用。

对比表

特性ProGuardR8
开发者GuardSquare(Eric Lafourge)Google
发布年份20022018(2019 年稳定)
架构4 个独立阶段(shrink → optimize → obfuscate → preverify)一次通过:shrink + optimize + obfuscate 同时进行
AGP 集成外部工具,在 javac 之后运行内置于 D8 DEX 编译器
构建速度慢 2-3 倍更快,得益于单次通过和原生集成
Kotlin 支持有限(与 inline、lambdas、coroutines 有问题)完整:coroutines、inline 函数、data class
映射文件mapping.txt(与 retrace 兼容)mapping.txt(相同格式)
优化定制60+ 选项 -optimizationpasses、-optimizations有限:大多数优化默认开启
支持状态已被 R8 取代(AGP 7.0+ 不使用)积极开发中,是 AOSP 的一部分

R8 何时可能破坏构建

R8 比 ProGuard 更激进地删除认为已死的代码。这导致了 debug 构建工作但 release 因 ClassNotFoundException 或 NoSuchMethodException 而崩溃的情况。典型情况:使用基于类名的反射的库(Gson、Moshi、Retrofit、Room、Dagger);ServiceLoader 或 java.util.ServiceLoader 调用;动态代理(java.lang.reflect.Proxy);本地方法(JNI)。解决方案——为所有通过反射调用的类添加 -keep。

pro
# 典型的反射问题 — R8 看不到静态关联

# Room — 保留 DAO 和迁移
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }

# Dagger / Hilt — 保留组件
-keep class * extends dagger.hilt.android.components.** { *; }

# JNI — 不重命名本地方法
-keepclasseswithmembernames class * {
    native <methods>;
}

# Data Binding — 保留 Binding 类
-keep class *.databinding.** { *; }

如果在添加规则后构建仍然崩溃,请在 proguard-rules.pro 中使用 -printconfiguration full-config.txt 标志。R8 将生成一个完整的配置文件,显示哪些规则已应用以及哪些类被保留。-whyareyoukeeping class com.example.MyClass 指令也很有用——它输出 R8 决定保留该类的原因为什么。

配置 ProGuard rules

正确配置 ProGuard rules——是运行时无错误地稳定运行混淆的关键。以下是新项目或混淆导致错误的项目的分步配置过程。

步骤 1:基本配置

从连接标准 Android SDK 文件开始——proguard-android-optimize.txt。它包含基本 Android 组件的规则:Activity、Service、BroadcastReceiver、ContentProvider、View、Fragment。此文件位于 SDK 文件夹中:$ANDROID_HOME/tools/proguard/proguard-android-optimize.txt。如果您使用 AGP,getDefaultProguardFile 将自动加载它。

步骤 2:库

每个流行的库都有推荐的 ProGuard 规则。Retrofit、OkHttp、Glide、Fresco、Coil、Room、Dagger/Hilt、Kotlin Coroutines——所有这些都需要特定的 -keep 规则。通常规则包含在 AAR 库中,并通过 consumer guard rules 自动连接。检查库是否在其 AAR 中提供 proguard.txt 文件——这表明规则已被考虑。

步骤 3:测试 release 构建

在发布之前,必须在真实设备或模拟器上测试 release 构建。混淆问题只在运行时出现。检查:身份验证(登录/注册)、从网络加载数据、屏幕间导航、相机和图库、推送通知、Deep links、WebView。release 构建中的每次崩溃都必须通过 retrace 和映射文件进行解码,并添加缺少的 -keep 规则。

步骤 4:映射文件和 CI

映射文件在 build/outputs/mapping/release/mapping.txt 中生成。此文件必须保存:没有它无法解码来自 Google Play Console 的崩溃日志。将 mapping.txt 包含在版本控制系统中或将其作为 CI 工件上传。Google Play Console 在启用 uploading mapping.txt 的情况下上传 AAB 时自动接受映射文件。

以下是在 proguard-rules.pro 文件中进行混淆配置的完整工作流程,每个规则组都有注释。

pro
# ===========================================
# proguard-rules.pro — 完整示例
# ===========================================

# --- 通用设置 ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify

# --- Android 组件 ---
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.Fragment
-keep public class * extends androidx.fragment.app.Fragment
-keep public class * extends android.view.View

# --- OkHttp / Retrofit ---
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions

# --- Gson / Moshi ---
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }

# --- Firebase ---
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }

# --- Kotlin Coroutines ---
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}

# --- 序列化 ---
-keepclassmembers class * implements java.io.Serializable {
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# --- 仅 R8:强制保留 ---
# (ProGuard 忽略此指令)
-keep,allowobfuscation class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

配置后,执行构建:./gradlew assembleRelease。检查 build/outputs/mapping/release/ 中是否出现了文件:mapping.txt(原始名称与混淆名称的对应关系)、seeds.txt(由 -keep 规则保留的类)、usage.txt(压缩期间删除的类)。混淆后 APK 大小应减少 20-50%,具体取决于连接的库的数量。

常见问题

R8 与 ProGuard 有何不同?

R8 — ProGuard 的继任者,由 Google 开发。R8 一次通过完成混淆、压缩和优化,工作速度比 ProGuard 快 2-3 倍,并直接集成到 Android Gradle Plugin 中。ProGuard 使用四个独立阶段并需要外部启动。从 AGP 7.0 开始,ProGuard 不再使用——默认情况下 R8 工作。

使用 R8 时是否需要编写 ProGuard rules?

是的,R8 使用相同的 ProGuard rules(.pro 文件)。-keep、-keepclassmembers、-keepattributes、-assumenosideeffects 指令的工作方式相同。基本规则来自 Android SDK 的 proguard-android-optimize.txt,而特定于库(Retrofit、Room、Gson)的规则添加到项目的 proguard-rules.pro 中。没有这些规则,R8 可能会删除通过反射运行库所需的类。

如何在 Android 项目中启用 R8?

R8 从 AGP 3.4 开始在 Android Gradle Plugin 中默认启用。要启用压缩,在 build.gradle.kts 文件的 release buildType 块中设置 isMinifyEnabled = true。附加标志 isShrinkResources = true 启用删除未使用的资源。在 gradle.properties 中可以通过 android.enableR8=false 强制禁用 R8,但不推荐——R8 更快更稳定。

Android 中的代码混淆是什么?

混淆 — 将类、方法和字段重命名为简短的无意义名称(a、b、c)。类 com.example.app.auth.LoginManager 变为 a.a.a,方法 authenticateUser 变为 a。这增加了应用的逆向工程难度,但不影响执行逻辑。ProGuard 和 R8 只重命名未被 -keep 规则保护的元素。映射文件保留原始名称和混淆名称的对应关系,用于解码崩溃日志。

如何调试来自混淆应用的崩溃日志?

解码堆栈跟踪使用 retrace 工具(属于 ProGuard/R8 SDK 的一部分)。命令:retrace mapping.txt crash-stacktrace.txt。映射文件位于 build/outputs/mapping/release/mapping.txt。Google Play Console 也在发布 AAB 时支持上传 mapping.txt——崩溃日志在控制台中自动解码。没有映射文件,堆栈跟踪将只包含混淆的名称 a.b.c(),这对调试毫无用处。

总结

  • ProGuard — 经典的 Java 字节码混淆和优化工具,由四个连续阶段组成
  • R8 — Google 的现代继任者,内置于 AGP,一次通过完成所有阶段,性能高 2-3 倍
  • 混淆 将类、方法和字段重命名为简短名称,增加逆向工程难度并保护应用的商业逻辑
  • 压缩 删除未使用的代码和资源,在典型项目中将 APK 大小减少 20-50%
  • ProGuard rules(.pro 文件)控制混淆行为——-keep、-keepclassmembers、-assumenosideeffects 指令指定哪些元素被保留、删除或重命名
  • 映射文件(mapping.txt)——通过 retrace 解码 release 构建的崩溃日志的关键构建产物
  • 测试 release 构建在真实设备上是强制性的——混淆问题只在运行时出现,需要添加缺失的 -keep 规则

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读