#现象

测试同学反映编译出来的release版本的包无法正常运行,debug倒是正常的。
一查日志,发现是类文件saveInSP找不到。如下图:
crash.img
这种现象一比较,直觉是proguard混淆出错了。来验证直觉吧。

#验证
反编译release apk,发现确实找不着saveInSP这个类文件。

saveInSP是个Java Annotation注解文件,用来对类文件中的某些成员变量进行修饰。其代码如下:

1
2
3
4
5
6
7
8
@Target(ElementType.FIELD)//仅为属性名的修饰有效
@Retention(RetentionPolicy.RUNTIME)//存在RetentionPolicy.RUNTIME、RetentionPolicy.CLASS、RetentionPolicy.SOURCE
@Documented//javadoc可生成相应文档
public @interface saveInSP {
boolean isServer() default false;
boolean save() default false;
String defValue() default "";
}

可见只有简单的三个方法。
查一下编译日志,发现问题,提示“name already added:string{“a”}”,如下图:

cmd.err.hint
是不是混淆之后存在多个方法名是相同名称“a”呢?
拿出proguard混淆后的mapping文件一查,果然isServer()和defValue()这两个不同的方法都被混淆成相同的方法名”a”了,如下图:
mapping.01
由此,确定了crash的原因是由于混淆时将saveInSP的方法混淆后存在同名称方法名且这两个方法都没有参数,唯一的区别只是返回类型不同而已,根据java规则是无法区别对待的,导致添加saveInSP类文件到classes.dex时不成功,自然在运行时就会发现找不到该类而抛出异常了。
但奇葩的是dx命令添加失败的时候应该中断掉呀,为毛仍会继续往下执行呢?

为了确定proguard出错的规则,继续往saveInSP中添加方法,如下:

1
2
3
4
5
6
7
public @interface saveInSP {
boolean isServer() default false;
boolean save() default false;
String defValue() default "";
String defValue01() default "";
int getInt() default 0;
}

进行混淆后,mapping文件如下图:

mapping.02
可见proguard对Java注解类文件的方法进行混淆时,存在一个bug:
对于方法参数个数相同只是返回参数不同的多个方法混淆后,混淆后,名称会相同,参数个数相同,只是返回类型不同。这样的混淆结果必然无法正确识别方法名呀。

#解决
1.如果为了保留混淆效果,则设置方法的参数个数不一致就可以解决。
2.拒绝混淆该注解文件,毕竟注解文件一般不涉及核心代码及逻辑,不混淆也不存在什么大问题。可在proguard.config文件中添加如下代码即可:

1
-keep public class com.t*****download.xmldata.saveInSP{*;}