2、配置语法基础:-injars、-outjars、-libraryjars、默认配置文件

好了,咱们正式开始接触 ProGuard 的核心配置。说实话,我第一次配置的时候,看到 -injars、-outjars、-libraryjars 这三个参数,觉得不就是输入输出嘛,能有多复杂?但后来在实际项目中踩了好几个坑,才发现细节里全是魔鬼。今天我就把这块掰开揉碎了讲清楚。

先放一张整体流程脑图,帮你建立直观印象:

ProGuard 配置语法核心流程 -injars 应用代码 / 依赖jar -libraryjars Android SDK / 第三方库 ProGuard 混淆引擎 代码压缩 | 混淆 | 优化 | 预校验 -outjars 混淆后的输出 (jar/aar) 默认配置文件 proguard-android*.txt 💡 关键点 -injars 和 -libraryjars 都是输入 区别:libraryjars不会被混淆 默认配置提供基础规则,可叠加

2.1 -injars:告诉 ProGuard 你要处理哪些代码

-injars 是最基本的一个参数。它指定了「待混淆的输入文件」——可以是 jar、aar、zip,甚至整个目录。ProGuard 会把这些文件里的所有类读进来,然后执行压缩、混淆、优化这些操作。

我个人习惯把应用的主模块 jar 和所有需要混淆的第三方依赖都列在 -injars 里。但你得注意,如果某个依赖你已经提前混淆过,再传进 -injars 可能会出问题。我记得有一次项目依赖了一个混淆后的 jar,结果 ProGuard 跑去二次混淆,方法签名全乱了,引发了一大堆运行时崩溃。

-injars  build/intermediates/classes/release/classes.jar
-injars  libs/some_third_party.jar
# 也可以直接写目录
-injars  build/exploded-aar/.../classes.jar
小经验:使用通配符 *.jar 可以一次性包含多个 jar,但要注意目录层级。我用 :/*.jar 匹配子目录下的所有 jar,效果很稳定。

2.2 -outjars:输出混淆后的产物

-outjars 指定混淆后生成的文件。通常是一个 jar 或 aar。当然,你也能指定多个输出文件,或者把输出内容映射到不同的目标。

这里有个坑——-outjars 必须跟 -injars 的数量匹配,否则 ProGuard 会报错。更精确地说,如果你写了多个 -injars,但没有分别对应多个 -outjars,ProGuard 默认把它们合并到一个输出里。我想让每个模块保持分离时,会这样写:

-injars module1.jar
-outjars module1_obfuscated.jar

-injars module2.jar
-outjars module2_obfuscated.jar
特别注意:输出文件不能写成空,比如只写一个目录名不带文件名,否则 ProGuard 可能会把结果输到标准输出流或直接报错。我曾经因为少写了一个 .jar,排查了半天,最后发现是输出路径没写全。

2.3 -libraryjars:引用但不混淆的库

这个参数最容易和新手产生误解。很多人以为 -libraryjars 跟 -injars 没区别,但实际上它们有一个关键不同:-libraryjars 里指定的 jar 只用来「引用」——ProGuard 会读取里面的类定义,用于解析依赖关系,但不会对它们进行混淆或压缩。说白了,Android SDK 的 android.jar 就必须放在 -libraryjars 里,否则 ProGuard 找不到系统类,会爆一大堆找不到符号的错误。

嗯,这里我还要强调一点:-libraryjars 不需要加上 -dontwarn 也能正常引用,但有些库内部写了一些不能被 ProGuard 解析的引用,这时候你才需要配合 -dontwarn 来忽略警告。我个人习惯把常见的第三方库都在 -libraryjars 里列一遍,可读性更好。

-libraryjars  /path/to/android-sdk/platforms/android-30/android.jar
-libraryjars  libs/support-annotations.jar  # 只引用,不处理
三种参数对比
参数作用是否被混淆典型用法
-injars输入待处理的代码是应用代码、需混淆的第三方jar
-outjars输出混淆后的结果—生成 release jar / aar
-libraryjars引用依赖,不处理否Android SDK、support库、gson等

2.4 默认配置文件:站在巨人的肩膀上

Android 工程里其实自带了一套默认混淆规则。在 android-sdk/tools/proguard/ 下你能找到 proguard-android.txt、proguard-android-optimize.txt 等文件。Gradle 构建时会自动将它们合并进来。你想想看,如果每次都要自己写规则来保留四大组件、R 文件、注解这些,那得多累?

不过默认配置并非万无一失。我记得有一次使用 ViewBinding,默认配置没有保留 binding 类,结果混淆后所有 binding 字段都出了问题。所以我的建议是:

  • 先采用默认配置,保证基础规则到位。
  • 用 -printconfiguration 输出最终生效的配置,方便查看。
  • 再在自定义 proguard-rules.pro 中补充项目特有的保留规则。
# 在 build.gradle 中配置
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                          'proguard-rules.pro'
        }
    }
}
避坑指南:如果你用 getDefaultProguardFile() 时文件名拼错(比如写成 proguard-android.txt 但实际下载了 proguard-android-optimize.txt),Gradle 不会报错,但优化和预校验的规则就没了。我曾经因为这个,打出来的 release 包在低版本 Android 上跑出奇怪的异常。

2.5 配置顺序与覆盖规则

最后提一下配置文件的读取顺序。ProGuard 会按照你在 proguardFiles 里写的顺序依次加载。后面的规则如果和前面冲突,后面的会覆盖前面的。你要是写了多个 -keep 或者 -dontwarn,后出现的获胜。这一点在处理模块化的项目时特别重要。

举个例子,如果默认配置里保留了 R$*,而你在自定义规则里写了个 -dontshrink,那压缩就直接被关闭了——不管默认配置怎么设。

实战技巧:我习惯在自定义 proguard-rules.pro 的开头加一行 -printconfiguration build/outputs/mapping/conf.txt,编译后去看看最终融合了什么规则,一目了然,排查问题快很多。

好了,配置语法基础就讲这么多。三种输入输出的参数一定要分清楚,默认配置能帮你省掉80%的工作,剩下的20%靠你根据项目来定制。熟练之后你会发现,ProGuard 其实没那么可怕。