Android应用打包完成后,APK里的dex文件随时可能被人用jadx、apktool这类工具打开。没有混淆的代码,类名、方法名、注释痕迹甚至字符串常量都清清楚楚,逆向成本极低。混淆的作用就是把有意义的命名替换成a、b、c这类无意义字符,同时进行压缩和优化,让反编译结果变得难以阅读。本文从混淆的基本原理讲起,详细说明规则文件的编写方法,以及实际项目中容易踩到的坑。

ProGuard和R8是什么关系
很多初学者会被这两个名字搞混。早期Android项目使用ProGuard作为混淆工具,从Android Studio 3.4开始,Google默认使用R8作为编译器的混淆和压缩引擎。R8兼容ProGuard的绝大部分规则语法,也就是说你写在proguard-rules.pro里的规则,在R8下依然生效,只是底层执行引擎换了,编译速度更快,还能直接生成DEX文件。
在build.gradle中开启混淆的配置如下,minifyEnabled控制代码混淆开关,shrinkResources控制资源压缩:
android {
buildTypes {
release {
minifyEnabled true // 开启代码混淆
shrinkResources true // 开启资源压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}
注意官方提供了两个基础规则文件:proguard-android.txt关闭了优化,proguard-android-optimize.txt包含优化选项。一般建议用optimize版本,混淆效果更彻底。debug版本默认不开启混淆,否则断点调试和崩溃堆栈都会变得难以定位。
keep规则的含义与常见坑点
混淆的核心问题不是开启,而是哪些东西不能混淆。keep规则就是告诉工具哪些类、方法、字段必须原样保留。下面是最常用的几类规则写法:
# 保留类本身及其成员
-keep class com.example.model.User { *; }
# 只保留类名,不保留成员
-keep class com.example.model.User
# 保留某个包下所有类
-keep class com.example.api.** { *; }
# 保留所有带特定注解的类
-keep @androidx.annotation.Keep class * { *; }
# 保留继承自某个类的子类
-keep public class * extends android.app.Activity
# 保留接口的实现类
-keep class * implements com.example.callback.OnResultListener {
void onResult(int code, java.lang.String msg);
}
这里有几个高频踩坑点值得展开说明。第一,泛型信息必须保留。Gson、Fastjson这类反射序列化库依赖类的泛型签名,如果被擦除,运行时会抛出ClassCastException或者解析结果全部为null。解决办法是加上-keepattributes Signature。第二,代码行号在排查线上崩溃时非常关键,需要保留LineNumberTable和SourceFile属性,再配合mapping.txt文件就能还原混淆后的堆栈。
第三,JNI回调不能混淆。Native层通过FindClass和GetMethodID按名字查找Java方法,方法名一旦被改成a,C++代码就找不到了,表现出来的现象是直接抛NoSuchMethodError。所有被native调用的方法都要显式keep住。第四,四大组件和Application系统会自动保留,但自定义View的构造方法、实体Bean、枚举的values和valueOf方法,这些都是反射重灾区,建议统一keep。
一份可直接套用的规则模板
下面这份模板整合了实际项目中最常见的场景,可以直接放在proguard-rules.pro里按需裁剪:
# 基础配置
-keepattributes Signature, InnerClasses, EnclosingMethod
-keepattributes SourceFile, LineNumberTable
-keepattributes *Annotation*, Exceptions
# 混淆后的映射文件输出
-printmapping build/outputs/mapping/release/mapping.txt
# 枚举不能被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保持自定义View的构造方法
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
}
# 实体Bean保留
-keep class com.example.model.** { *; }
# Gson泛型处理
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
# WebView的JS桥接
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
# 常见第三方SDK(按实际引入裁剪)
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class com.tencent.** { *; }
其中-printmapping输出的mapping.txt文件务必妥善归档,每次发版都对应一份。线上崩溃堆栈是混淆后的类名,需要用retrace工具配合mapping才能还原成真实代码位置。这份文件一旦丢失,那次版本的崩溃将几乎无法定位。另外,谷歌还提供了R8的mapping上传功能,配合Play Console可以自动符号化崩溃报告。
混淆之外还需注意的加固手段
混淆只能增加阅读难度,并不能阻止反编译本身。真正对抗破解需要多层防护配合。字符串加密是容易被忽视的一环,密钥、接口地址这类敏感字符串混淆后依然以明文形式存在于dex中,建议做加密存储,运行时解密,或者放到Native层。资源文件同样需要保护,微信开源的AndResGuard可以把res目录下的资源名混淆成短路径,同时减小APK体积。
更进一步的方案是使用第三方加固服务,原理是在dex外层套壳,运行时再动态解密加载,jadx直接打开只能看到壳代码。此外还可以做完整性校验、反调试检测、模拟器识别等对抗手段。需要提醒的是,安全永远是成本和收益的平衡,没有绝对不可破解的应用,混淆加多重防护的目标是让破解成本高于破解收益。
最后建议在每次修改混淆规则后,用正式包完整回归一遍核心流程,重点覆盖网络请求、支付、推送和序列化场景。混淆引发的问题往往只在release包出现,debug阶段完全正常,这也是它容易漏测的原因。把混淆配置纳入版本管理,规则变更走代码评审,才能长期维持配置的正确性。
Android混淆ProGuard规则R8配置修改时间:2026-09-13 02:12:30