Bug 系列之 proguard 的坑

我们正在开发中的一个 Android 应用,最近发布 v2.6.7 版本时碰到几个跟 proguard 混淆相关的问题,记录下备忘。

问题一

测试同事反馈某个赠送礼物的功能不正常。发现 debug 包中该功能完全正常,而 release 包里面则有问题。这种问题是不是很眼熟?

想起之前 Android Studio 中检查相关代码时,提示礼物的 model 类某些字段未被用到,可删除。所以第一反应是:会不会 proguard 也认为这个字段未用到,所以给删除了。立马检查 proguard 的输出文件usage.txt中是否存在删除该 model 类的记录,发现并没有。不过,mapping.txt中有如下记录:

1
2
3
4
5
com.tc.xx.api.GiftsService$InnerGift -> com.tc.xx.c.f$a:
int num -> a
java.lang.String content -> b
java.lang.String receiver -> c
void <init>(java.lang.String) -> <init>

礼物的 model 类被混淆了!混淆导致 GSON 序列化生成的 json 数据格式不正确,跟后台的通信失败,引起功能不可用。

总结:之前赶进度偷懒将 model 类放在不正确的 package 下, 结果被混淆了,最终引发问题。 这是个人为错误。

我们的 proguard 配置已经指定 model 所在的 package 不被混淆,将相关的类放到该 package 即可解决问题。

1
2
# Application classes that will be serialized/deserialized over Gson
-keep class com.tc.xx.model.** { *; }

问题二

测试同事反馈更换头像或首次发送语音消息时 app 会 crash,必现。

跟前一个问题类似,debug 包中该功能完全正常,而release 包里面有问题。崩溃日志比较奇怪,看起来并不是我们自己的代码 crash 了:

1
2
3
4
5
6
7
8
9
java.lang.NoSuchFieldError: no "J" field "mNativeContext" in class "Lcom/tc/upload/network/base/ConnectionImpl;" or its superclasses
com.tc.upload.network.base.ConnectionImpl.native_init(Native Method)
com.tc.upload.network.base.ConnectionImpl.<clinit>(Unknown Source)
com.tc.upload.network.base.a.<init>(Unknown Source)
com.tc.upload.network.base.f.<init>(Unknown Source)
com.tc.upload.network.b.g.a(Unknown Source)
com.tc.upload.network.b.g.a(Unknown Source)
com.tc.upload.network.b.c.a(Unknown Source)
com.tc.upload.network.b.c.d(Unknown Source)

推测是某个第三方库代码被不正确地混淆了,导致上传时发生 crash。我想起来更换头像或首次发送语音消息两个功能都涉及到一个上传库 uploadlib.jar。

分析后以上错误堆栈对应的类和方法确实来自 uploadlib.jar 。我们不清楚 uploadlib.jar 具体的实现代码,还是不混淆为妙! 添加以下配置后重新打包,问题解决。

1
2
# uploadlib
-keep class com.tc.upload.** {*;}

总结:无论来自哪里的第三方库,一定要注明来源(最好是官网地址),并添加按官方文档添加 proguard 配置

注:在这里,我找不到 uploadlib.jar 的具体来源,只好简单地配置为完整不混淆。这个做法有些偷懒,但非常安全。


为什么这么多的混淆问题之前没有发现呢?不得不说一下腾讯云 IM SDK 包结构非常坑,很多类放在com.tc包下,而它的官网给出的混淆配置是这样:

1
2
-keep class com.tc.** {*;}
...

这明摆着是要让用了云 IM SDK 的、使用标准包名来命名的腾讯app没法好好混淆吗?(我们的 App 很不幸,历史原因导致包名是 com.tc 开头)

由于包名问题,我们 App 之前的版本一直不做代码混淆,proguard 配置是 -keep class com.tc.**{*;}。直到某个版本中优化了这个不合理的配置,让com.tc包下大量原本可被混淆的代码能正确地混淆。

回来来看,这个优化工作做得不彻底。之前一些错误或遗漏的混淆问题也暴露出来了,如上面提到的这两个例子。