引言
你是否也到过自己的Android项目,在debug时项目是正常运行的,但在release时,项目却出现了找不到对应的类的问题?
导致这个问题的原因有很多,其中最最主要原因是debug 模式为了方便调试,默认关闭代码压缩、混淆、资源优化;而 release 模式为了减小包体积、提升性能,会启用代码压缩、混淆、资源压缩 ,这些优化操作若配置不当,会直接导致类被 "误删除" 或 "混淆后找不到"。其中,代码压缩、混淆、资源压缩 这些工作均由 R8 来实现。
什么是 R8
R8 是 Google 推出的 Android 代码压缩和混淆工具,自 AGP 3.4.0 起默认的代码优化工具。它在构建 release 版本时执行以下核心功能:
- 代码混淆(Obfuscation) :将类名、方法名、字段名等有意义的标识符重命名为极短的无意义名称(如 
a,b),增加反向工程难度。 - 代码压缩(Shrinking) :分析代码的可达性,移除项目中未被使用的类、方法、字段等,减小 APK 体积。
 - 代码优化(Optimization) :进行内联、移除无用代码块、简化控制流等操作,提升应用运行效率。
 
R8 直接将 Java 字节码(.class 文件)转换为优化后的 DEX 字节码,通常具有更快的构建速度和更好的优化效果。
使用与关闭 R8 相关功能
R8 相关功能的启用与关闭可以在 build.gradle 中进行配置
            
            
              kotlin
              
              
            
          
          android {
    buildTypes {
        release {
            minifyEnabled true  // 启用 R8 代码压缩和混淆
            shrinkResources true // 启用资源压缩
            // 下面是R8规则的配置文件
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
        R8 原理与相关功能介绍
混淆代码
混淆代码(Obfuscation)是指将源代码中的类名、方法名、字段名等 有意义的标识符替换为无意义的短名称(如 a, b, c 等),从而增加逆向工程的难度,保护应用逻辑不被轻易窃取。
作用:
- 提高安全性:防止他人通过反编译轻松理解你的代码逻辑。
 - 减小 DEX 文件体积:较短的名称占用更少空间。
 - 避免暴露敏感信息:例如内部 API 名称、业务逻辑命名等。
 
当 minifyEnabled true 启用后,R8 会自动执行混淆操作。默认情况下,R8 使用启发式算法分析代码入口点(如 Activity、Application 类等),仅对未被引用或可安全处理的部分进行重命名。
配置方式
Android Studio在创建项目时会创建proguard-rules.pro文件,可以在这个文件中添加混淆规则:
            
            
              ini
              
              
            
          
          -keep class com.example.UserManager { *; }
-keepclassmembers class * implements java.io.Serializable {
    <fields>;
    <methods>;
}
        压缩代码与资源
1. 代码压缩
定义:代码压缩是指:移除应用中未被使用的类、方法、字段和指令,只保留运行时真正需要的代码。
工作原理 : R8 从入口点(如 Android 四大组件、自定义 Application 类等)开始追踪所有可达代码路径。任何无法从入口点访问到的代码都会被视为"死代码"并被删除 (可达性分析)
启用方式: 同代码混淆
2. 资源压缩(Resource Shrinking)
定义 :
资源压缩是指移除打包进 APK 中但实际未被代码引用的资源文件(如图片、布局 XML、字符串等)。
工作机制:
- R8 分析代码中对资源的引用(包括 
R.*和Resources.getIdentifier())。 - 构建一个"引用图",标记所有被使用的资源。
 - 未出现在图中的资源将在打包时被剔除。
 
启用方式:
            
            
              kotlin
              
              
            
          
          shrinkResources true  // 通常配合 minifyEnabled 使用
        代码优化
定义
对字节码进行深层次的结构优化,在不改变程序行为的前提下提升性能和效率。
典型优化策略
| 优化类型 | 说明 | 
|---|---|
| 内联方法(Method Inlining) | 将小方法体直接插入调用处,减少方法调用开销。 | 
| 常量传播(Constant Propagation) | 将常量值传播到使用位置,消除变量访问。 | 
| 无用代码消除(Dead Code Elimination) | 移除永远无法执行的代码块(如 if (false))。 | 
| 类合并与简化 | 合并仅被单处使用的类,或简化继承结构。 | 
| 字符串优化 | 合并重复字符串,移除冗余拼接。 | 
R8 的优化是在字节码层面完成的,开发者无需手动干预。
总结对比
| 功能 | 是否默认启用 | 主要收益 | 
|---|---|---|
| 混淆 | minifyEnabled true 时启用 | 
安全性、体积减小 | 
| 代码压缩 | minifyEnabled true | 
移除无用代码 | 
| 资源压缩 | shrinkResources true | 
移除无用资源 | 
| 优化 | minifyEnabled true 时自动启用 | 
性能提升、体积进一步压缩 | 
注意事项
那些类不应该被 R8 处理
为了确保应用正常运行,一些类、方法或字段必须保留原样,不可被 R8 压缩或混淆:
反射相关的类和成员
使用 Java 反射(Class.forName(), getDeclaredMethod() 等)调用的类、方法、字段必须保留原名。
            
            
              kotlin
              
              
            
          
          # 保留特定类及其所有成员不被混淆
-keep class com.example.MyReflectionTarget { *; }
# 或者更精确地保留特定方法
-keepclassmembers class com.example.MyClass {
    public void reflectiveMethod();
}
        JNI 方法
所有通过 native 关键字声明并在 C/C++ 代码中实现的方法。
            
            
              kotlin
              
              
            
          
          # 保留 native 方法的签名
-keepclasseswithmembers class * {
    native <methods>;
}
# 或者针对特定类
-keepclassmembers class com.example.NativeBridge {
    public native *;
}
        序列化
Serializable: 实现java.io.Serializable的类,其字段名在反序列化时需匹配。Parcelable: 通常由框架生成代码处理,一般无需额外规则,但复杂情况可能需要。JSON 序列化库 (Gson, Moshi, Jackson): 需要保留模型类的字段名和构造函数。
            
            
              kotlin
              
              
            
          
          # 保留所有实现 Serializable 的类
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    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();
}
# Gson: 保留所有被注解的成员和无参构造函数
-keepattributes Signature
-keepattributes *Annotation*
-keepclassmembers class ** {
    @com.google.gson.annotations.* <fields>;
}
-keepclassmembers class * {
    *** get*();
    *** is*();
    *** set*(***);
}
        四大组件
Android 系统通过类名反射创建这些组件,因此它们的类名必须保留。
            
            
              kotlin
              
              
            
          
          # 通常 AndroidX 或 Support Library 的规则已包含
-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.Application
        自定义 View
在布局 XML 中引用的自定义 View 类及其构造函数。
            
            
              kotlin
              
              
            
          
          -keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
        第三方库
许多第三方库都有特定的 ProGuard 规则。要查阅官方文档并添加相应规则。
使用注解处理器生成代码的类
Room 会根据注解自动生成代码,它们生成的代码依赖于原始类的结构和注解,通常需要保留原始类,涉及到Room的类应当被保留。
公共 API
如果应用是库模块,需要明确保留对外暴露的 API。
            
            
              kotlin
              
              
            
          
          -keep public class com.example.mylibrary.PublicApi { *; }