- 原文地址:Troubleshooting ProGuard issues on Android
- 原文做者:Wojtek Kaliciński
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:dieyidezui
- 校对者:corresponding
ProGuard 是一个压缩、优化、混淆代码的工具。尽管有不少其余工具供开发者们使用,可是 ProGuard 做为 Android Gradle 构建过程的一部分,已经打包在 SDK 中。html
当咱们构建应用时,使用 ProGuard 有不少好处。有的开发者更关心混淆这块功能,对我而言最大的用处是打包时移除 dex 中的无用代码。前端
一个 Android 示例应用的空间分布图,源码地址 Topeka sample app。java
减小包体积的好处有不少,好比增长用户黏性和满意度,提高下载速度,减小安装时间,以便在终端设备上链接用户,尤为是在新兴市场。固然,有时候您不得不限制您的应用的大小,好比 Instant App 限制大小 4 MB,此时 ProGuard 显得必不可少了。node
若是以上还不足以说服您使用 ProGuard,其实移除无用代码和混淆全部名称还有其余更多的优化效果:react
a.A
和 a.a.B
。这个过程就是混淆。混淆经过两种方式来减小代码:让表示名称的字符串更短;在这些方法或者属性有相同的签名状况,下这些字符串更容易被复用,最终减小了字符串池的数目。每一个 Android 应用都应该使用代码压缩吗?我认为是的!android
可是在您激动的跳起来以前,请先继续阅读下去。当您开启 ProGuard 时,在某些很是微妙的状况下会让您的应用崩溃。虽然有些错误会在构建应用时发生,您能及时发现,可是也有些错误您只能在运行时发现,因此请确保您的应用通过完全的测试。ios
在您的项目中开启 ProGuard 只需简单到添加以下几行代码在您的主应用模块的 build.gradle
文件中:git
buildTypes {
/* you will normally want to enable ProGuard only for your release
builds, as it’s an additional step that makes the build slower and can make debugging more difficult */
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
}
复制代码
ProGuard 自身的配置已经在另一个单独的配置文件中完成了。上面的代码中,我给出了 Android Gradle 打包插件中的默认配置¹,接下去我会在 proguard-rules.pro
中加入其余的配置。github
在 ProGuard 官网您能够找到一个 使用手册。 在您深刻研究这些配置以前,最好先大概理解 ProGuard 是如何工做的和咱们为何要指定一些额外的选项。后端
您也能够去观看 part of this Google I/O session Shai Barack 的教学视频。
简单来讲,ProGuard 将您项目中的 .class 文件作为输入,而后寻找代码中全部的调用点,计算出代码中全部可达的调用关系图,而后移除剩余的部分(即不可达的代码和那些不会被调用的代码)。
在您读 ProGuard 手册时,您不必看那些 输入 / 输出的部分,由于这些 Android Gradle 打包插件会替您指定输入源(您和第三方库的代码) 和 Android jar 库(您构建应用时用到的 Android 框架类)。
想要正确配置 ProGuard,最重要的就是让它知道运行时您的哪些代码不该该被移除(若是开启混淆的话,固然也要保持他们的名称不变)。当一些类和方法会被动态访问到时(如使用反射),在某些状况下,ProGuard 在构建调用图时不能正确的决定他们的「生死」,致使这些代码被错误的移除掉。当您只从 XML 资源引用您的代码会时(一般使用底层的反射),这个状况也会发生。
在一次 Android 典型的构建过程当中,AAPT(处理资源的工具)会生成一个额外的 ProGuard 规则文件。它会为 Android 应用添加一些特别的 keep 规则,因此您在 Android Manifest.xml 中记录的 Activities、Services、BroadcastReceivers 和 ContentProviders 会保持不动. 这就是为何在上面动图中 MyActivity
类没有被被移除或者重命名.
AAPT 也会 keep 住全部在 XML 布局文件使用到的 View 类(和它们的构造函数)和其余一些类,如在过渡动画资源中引用到的过渡类。 您能够在构建后直接看这个 AAPT 生成的配置文件,位置是:<your_project>/<app_module>/build/intermediates/proguard-rules/<variant>/aapt_rules.txt
。
在构建时 AAPT 生成的一个示例 ProGuard 配置文件
我会在本文后面章节中讨论更多关于 keep 规则,可是在那以前咱们最好先学一下在如下状况时应该怎么作:
在您能够测试是否开启 ProGuard 后全部代码在运行时都能正常工做前,您须要先构建您的应用。不幸的是,ProGuard 可能会发现一些引用的类缺失,并给予告警,致使您的构建失败。
修复这个问题的关键是仔细观察构建时输出的消息,理解这些警告的内容并定位他们。一般的途径是修正您的依赖或者在您的 ProGuard 配置中添加 -dontwarn 规则。
这些警告的一个缘由就是,您的构建路径中没有加入须要依赖的 JARs,如使用了 provided (仅编译时)依赖。而有时候,在 Android 上这些代码的依赖在运行时并不会被真正的调用。让咱们看一个真实的例子。
一个项目依赖 OkHttp 3.8.0 构建时的消息。
OkHttp 库在 3.8.0 版本的类中添加了新的注解(javax.annotation.Nullable
)。可是由于它们使用了编译时的依赖,因此这些注解在最终构建时不会被打包进去(哪怕应用显式的依赖了 com.google.code.findbugs:jsr305
),所以 ProGuard 会抱怨 缺失了这些类.
由于咱们知道这些注解类在运行时不会被使用,咱们能够经过在 ProGuard 配置中添加 -dontwarn 规则来安全地忽略掉这些警告,如 在 OkHttp 文档中加入这些规则:
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
复制代码
您应该经历过相似的过程,在输出消息中看到这些警告,而后从新构建直到构建经过。重要的是去理解为何您会收到这些警告以及您在构建时是否真的缺乏这些类。
如今您可能会尝试使用 -ignorewarnings 选项直接忽略全部的警告,但这一般不是个好注意。在某些状况下,ProGuard 的警告确实有助于您发现闪退的罪魁祸首和关于您配置上的其余问题。
您可能须要了解一下 Progard的 notes (优先级低于警告的消息),它能够帮您发现一些反射相关的问题。虽然它不会打断您的构建,可是在运行时可能会闪退。这会在下面的场景中发生:
在某些状况下,ProGuard 并不知道一个类或者方法被使用了,例如这个类仅在反射时被使用或者仅在 XML 中被引用。为了阻止这样的代码被移除或混淆,您应当在 ProGuard 配置中指定额外 keep 规则。这取决于做为应用开发者的你,须要去发现哪些部分代码有问题并提供必要的规则。
当运行时发生了 ClassNotFoundException
或 MethodNotFoundException
异常意味着您确定缺失了某些类或者方法,也许是 ProGuard 移除了他们,又或者是由于错误配置依赖而致使没法找到他们。因此生产环境的构建(开启 ProGuard 时)必定要注重完全的测试并正视这些错误。
您有不少选项来配置您的 ProGuard:
我建议您从 ProGuard 的这篇 class specification syntax 开始熟悉,此文讨论了上述全部的 keep 规则和前一段讨论到的 -dontwarn 选项。另外这三个 keep 规则也各有一个不一样的版本支持仅保留混淆(重命名),不保留压缩。您能够在 ProGuard 官网的表格看一下概览。
做为一个可选的方案来写 ProGuard 规则,您能够直接在某个不想被混淆和移除的类、方法、属性上添加 @Keep 注解。注意,若是这样作的话,您须要把 Android 默认的 ProGuard 配置加入到您的构建中。
Android Studio 集成的 APK Analyzer 能够帮您看到哪些类被 ProGuard 移除了并支持为它们生成 keep 规则。当您构建 APK 时开启了 ProGuard,那么会额外输出一些文件在 <app_module>/build/outputs/mapping/
目录下。这些文件包含了移除代码的信息、混淆的映射关系。
加载 ProGuard 映射文件到 APK Analyzer 能够看到 DEX 视图中更多的信息
当您加载了映射文件到 APK Analyzer时(点击 “Load Proguard mappings… “ 按钮), 您能够在 DEX 视图树中看到一些额外功能:
全部应用均可以使用 Android 内置的 ProGuard 的一些安全的默认规则,如保留 View
的 getter 和 setter 方法,由于他们一般会被反射来访问,以及其余一些普通的方法和类都不会被移除。 这在许多状况下能够时您的应用避免崩溃的发生,可是这些配置并非 100% 适合您的应用。您能够移除掉默认的 ProGuard 文件而使用您本身的。
若是您但愿 ProGuard 移除全部未使用的代码,您应当避免 keep 规则写的太宽泛,如加入通配符匹配整个包,而是使用类相关的匹配规则或者使用上面说起的 @Keep
注解。
使用 -whyareyoukeeping <class-specification>
选项来观察为何这些类没有被移除。
若是您实在不肯定为何 ProGuard 没有移除您指望它移除的代码,,您能够添加 -whyareyoukeeping 选项至 ProGuard 配置文件中,而后从新构建您的应用。在构建输出中,您会看到是什么调用链决定了 ProGuard 保留这些代码。
在 APK Analyzer 中追踪是什么在 DEX 中 keep 住了这些类和方法
另外一种方法不那么精准,但在任何应用都不须要从新构建和额外的工做量。那就是在 APK Analyzer 中打开 DEX 文件,而后右击您关注的类、方法。选择 “Find usages” 您将看到引用链,这也许会引导您了解哪部分代码使用指定的类、方法从而阻止了它被移除。
我以前说起到,在构建过程当中 ProGuard 会在处理类文件时输出映射关系和日志文件。当您须要保留构建产物时,您应当保存好这些文件和 APK 在一块儿。这些映射文件不能被其余的构建所使用,而只会在与它们一块儿生成的 APK 配合使用时才能确保正确。有了这些映射关系,您才能有效地 debug 用户设备的发生的崩溃。不然太难去定位问题了,由于名字都混淆过了。
上传 APK 对应的 ProGuard 映射文件至 Google Play 控制台,从而得到混淆前的堆栈信息。
您在 Google Play 控制台发布混淆后的生产 APK时,记得为每一个版本上传对应的映射文件。这样的话当您看 ANRs & crashes 页面时,上报的堆栈都会现实真实的类名、方法名和行号而不是缩短的混淆后的那些。
就像您有责任为您本身的代码提供 keep 规则同样,那些第三方库的做者们也有义务向您提供必要的混淆规则配置来避免开启 Proguard 致使的构建失败或者应用崩溃。
有些项目简单地在他们的文档或者 README 上说起了必要的混淆规则,因此您须要复制粘贴这些规则到您的主 ProGuard 配置文件中。不过有个更好的方法,第三方库的维护者们若是发布的库是 AAR ,那么能够指定规则打包在 AAR 中并会在应用构建时自动暴露给构建系统,经过添加下面几行代码到库模块的 build.gradle
文件中:
release { //or your own build type
consumerProguardFiles ‘consumer-proguard.txt’
}
复制代码
您写入在 consumer-proguard.txt
文件中的规则将会在应用构建时附加到应用主 ProGuard 配置并被使用。
若是想了解更多关于代码和资源压缩的信息,请参考咱们的文档页面
开启 ProGuard 可能一开始会比较困难,可是我我的认为这些代价是值得的。只要投入一点点时间,您将会得到一个轻量、优化后的应用。此外,如今花费时间去配置您的应用意味着当实验性的 ProGuard 替代者 R8 就绪时,您已经准备好了。由于 R8 也是用现有的 ProGuard 规则文件来工做的。
除了让您的代码更小巧以外, ProGuard 和 R8 能够选择优化您的代码让它运行得更快,固然这又是另外一篇文章的话题了……
¹ proguard-android.txt 文件以前是在 SDK tools 目录下(SDK/tools/proguard/proguard-android.txt
),但在新版的 SDK Tools 和 Android Gradle 插件版本2.2.0+上,能够在构建时从 Android 插件的 jar 中解压出来。在构建您的项目后,您能够在 <your_project>/build/intermediates/proguard-files/
目录下找到这个配置文件。
感谢 Daniel Galpin。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。