proguard如何会导致json解析失效?

1. 楔子

json数据的解析是这个星球上99%的Android程序猿都会遇到的问题,而其中的大部分,都会使用Gson,故事就是从这里发生……

2. 一开始

通常json数据是从服务端取的,典型的一段json字符串如下:

  { "name": "老张","age": 18}

一般程序猿们会在本地写一个实体类与之对应,比如数据类Person:

public class Person { 
    private String name;
    private int age;
    //……省略中间get/set
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}

然后就可以愉快地使用Gson来进行解析啦,像这样:

Person someone = new Gson().fromJson(jsonString, Person.class);
Log.d("TAG", "someone: " + someone.toString());

一切顺利,程序猿们得到输出:

Person{ name=’老张’,age=18}

然后他们就愉快地下班去玩耍了。

3. 后来,他们开启了proguard

意外就是从这里开始的,开启了proguard之后,debug运行的程序一切如常,但是proguard之后输入的apk包,在安装之后的输出变成了这样:

Person{ name=’null’,age=0}

——解析出错了。
(算了,我编不下去了,从现在开始使用第一人称……)

4. 问题所在

经过反复排查,发现问题就是由proguard引起的。
我们知道,proguard的过程会执行压缩、混淆、去除无用代码等操作。而其中的混淆、去除无用代码的过程,对于使用了反射技术的代码来说,都是有可能受到影响的。
不幸的是:Gson.fromJson()就是基于反射来实现的。

通过反编译apk包,发现我们写的Person类,在没有被保护的情况下,会被proguard成类似这个样子的:
反编译代码1
可以看到,类名、字段名都改成了a/b这样的名字。
这样在使用Gson的时候,它尝试去序列化的字段就会变成了a/b,而不是之前的name/age了。
——那还反射个毛啊!

5. 解决

知道问题了,自然也就很好解决了。
方案是在proguard规则中,把Person类给保护起来

5.1 proguard文件编辑

proguard-rules.pro文件中添加规则(也可能是其他文件,具体见gradle文件中的定义),把数据类保护起来即可。
具体来说,可以直接保护当前类:

-keep class com.xxx.beans.Person {*;}

也可以保护整个包:

-keep class com.xxx.beans.* {*;}

还可以给Person添加一个对Serializable的实现,然后保护所有的Serializable

-keep class * implements java.io.Serializable {*; }

5.2 @Keep注解

另外,项目如果引用了注解支持库,那么给Person类加上@Keep的注解也可以防止它被混淆。

@Keep
public class Person {/*省略*/}

OK,加上保护之后,重新打包。
反编译这时的apk,可以看到,字段Person类没有再被混淆了:
反编译代码2
然后安装、运行,果然一切正常了。

Person{ name=’老张’,age=18}

完美。

6. 一些链接

反编译工具:
压缩代码和资源:

proguard相关问题排查:

关于作者:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*