自己写的类、接口、方法到底要不要混淆?

2 阅读4分钟

核心总原则

  1. 能混淆就尽量混淆:缩小包体积、增加逆向难度、删除无效代码;
  2. 只要被「外部反射 / 系统反射 / 跨进程 / JS/JNI/ 序列化」调用的代码,一律不能混淆,必须 -keep
  3. 纯内部逻辑、仅自己代码直接调用、无反射、无跨外部调用 → 放心混淆。

一、可以正常混淆(不用 keep)的代码

满足全部条件:

  • 只在自己 Java/Kotlin 代码里直接硬编码调用,不存在反射;
  • 不在 AndroidManifest、XML、JS、JNI、路由框架、序列化工具中被间接加载;
  • 没有被第三方框架通过注解反射扫描。

举例:

  1. 普通工具类、内部业务逻辑类、私有方法、私有接口实现;
  2. 仅内部 new、直接调用的普通方法;
  3. 仅本类内部使用的私有内部类;
  4. 不参与 JSON 序列化、不做 Parcelable/Serializable 传递的临时数据类。

好处:R8/ProGuard 会自动改名、内联、删除无用分支,减小安装包。

示例场景:

kotlin

// 纯内部工具,全可混淆
class CalcUtil {
    fun add(a:Int,b:Int) = a+b
}

不需要任何 keep 规则。

二、绝对不能混淆,必须 keep 的代码(高频踩坑区)

1. 系统反射创建的类(四大组件、自定义 View)

系统通过字符串全类名反射实例化,混淆改名后直接 ClassNotFound 闪退

  • Activity、Service、BroadcastReceiver、ContentProvider
  • 自定义 View(xml 通过 class/ViewGroup 反射加载)
  • Application

2. 序列化 / JSON 实体类(Gson/ Moshi/FastJson/Parcelable)

序列化框架通过反射 get/set 字段,字段名混淆后解析失败、数据全空

  • 网络返回 Bean、本地存储实体
  • Parcelable、Serializable 序列化对象(页面传参)

3. 反射相关(自己手写反射、框架反射)

  1. 手写 Class.forNamegetMethodgetField 操作的类 / 方法 / 字段;
  2. 事件总线 EventBus、LiveDataBus、路由框架(ARouter、WMRouter);
  3. 注解驱动框架(APT、动态代理、Retrofit 接口)。

4. JNI 交互类与 native 方法

C/C++ 层写死 Java 方法名,混淆后找不到方法崩溃

java

运行

// native 方法不能改名
native void test();

5. WebView JS 交互接口

JS 用字符串调用 Java 方法,混淆后方法找不到

java

运行

@JavascriptInterface
public void callAndroid(){}

6. 动态下发配置、热修复、插件化相关类

插件 / 热更通过字符串加载类,混淆会找不到类。

7. 回调、接口被第三方 SDK 反射调用

第三方 SDK 通过反射加载你的实现类,必须 keep。

三、接口单独说明:接口要不要混淆?

分两种情况:

情况 1:接口只在内部代码直接实现、直接调用 → 可混淆

kotlin

interface ICalc
class CalcImpl : ICalc
// 代码里直接 CalcImpl() as ICalc 调用,无反射,随便混淆

情况 2:接口被反射 / 框架扫描实例化 → 接口 + 实现类都要 keep

示例:

  • Retrofit 接口;
  • 框架 Class.newInstance() 反射获取接口实现;
  • 注解框架扫描所有实现类。

proguard

# 保留接口及所有实现类
-keep interface com.xxx.api.IMyApi
-keep class * implements com.xxx.api.IMyApi {*;}

四、方法单独说明:哪些方法不能混淆

  1. XML 中 android:onClick="xxx" 点击方法;
  2. JS 交互 @JavascriptInterface 方法;
  3. Native 对应的 Java native 方法;
  4. 反射工具调用的 set/get、自定义回调方法;
  5. EventBus @Subscribe 订阅方法;
  6. Parcelable 的 CREATOR、枚举 values ()/valueOf ()。

普通私有 / 公共业务方法,代码直接调用,无反射 → 随便混淆。

五、两种保守方案(项目常用)

方案 1:精准 keep(推荐,混淆最大化,包最小)

只对必须不混淆的类加 -keep,其余全部混淆示例:

proguard

# 只保留网络实体
-keep class com.xxx.bean.** { *; }
# 只保留四大组件
-keep public class * extends android.app.Activity
# 只保留JS交互类
-keep class com.xxx.JsBridge {<methods>;}

优点:逆向难度最高,包体积最优;缺点:需要自己梳理所有反射类,漏写就崩溃。

方案 2:全包不混淆(简单懒人方案,不推荐上线)

proguard

-keep class com.xxx.** {*;}

适用:快速调试、临时打包测试;缺点:所有类名都明文暴露,极易被逆向,包体积更大。

六、快速判断口诀

  1. 代码直接 new / 直接点方法调用 → 随便混淆;
  2. 通过字符串名字找到类 / 方法(反射、XML、Manifest、JS、JNI、路由、JSON 解析)→ 必须 keep;
  3. 第三方框架、系统服务自动实例化的类 → 必须 keep;
  4. 纯内部工具、私有逻辑、无外部反射依赖 → 放心混淆。

七、常见误区纠正

  1. 误区:自己写的业务类全部要 keep,不然会报错纠正:只要没有反射调用,混淆完全没问题,R8 会自动处理调用关系。
  2. 误区:接口必须 keep纠正:仅反射使用的接口才需要,内部直接调用的接口可混淆。
  3. 误区:public 方法都要 keep纠正:public 只是访问权限,只要不是反射调用,照样可以混淆改名。