我们一般写 json 解析,一般都是使用比较成熟的第三方库gsonfastjsonjackson等。

但是你知道吗?

这些库在使用的时候,bean对象不能混淆,而且底层是通过反射来对每个属性就行赋值,那么在性能损耗上就会大大增加。

1. 反序列化

让我们来看看实际的例子:

1.1 定义 json

定义一个 json,就是普通的一个对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const val json = """
{
    "id": 912345678902,
    "text": "@android_newb just use android.util.JsonReader!",
    "geo": [
      50.454722,
      -104.606667
    ],
    "user": {
      "name": "jesse",
      "followers_count": 2
    }
}"""

1.2 创建模型对象类

1
2
3
4
5
6
7
8
data class ModelUser(var name: String? = "", var followers_count: Int? = 0)

data class Model(
    var id: Long? = 0,
    var text: String? = "",
    var geo: List<Double>? = null,
    var user: ModelUser? = null
)

1.3 gson 解析

我们使用 gson 来解析对象,代码如下

1
2
3
4
5
6
7
fun testGson() {
    val beginTime = System.nanoTime()
    val gson = Gson()
    val models = gson.fromJson<Model>(json, Model::class.java)
    Log.d("zyh", "gson消耗:" + (System.nanoTime() - beginTime) / 1000 + "微秒")
    Log.d("zyh", models.toString())
}

结果如下:

1
2
gson消耗28643微秒
Model(id=912345678902, text=@android_newb just use android.util.JsonReader!, geo=[50.454722, -104.606667], user=ModelUser(name=jesse, followers_count=2))

我们记住这个数字 28643微秒。

因为没有对比就没有伤害。

1.4 JSONObject 解析

使用原生的 jsonObject 解析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fun testJsonObject() {
    val beginTime = System.nanoTime()
    val jsonObject = JSONObject(json)
    val model = Model()
    model.id = jsonObject.optLong("id")
    model.text = jsonObject.optString("text")
    val array = jsonObject.optJSONArray("geo")
    val arraylist = arrayListOf<Double>()
    for (item in 0 until array.length()) {
        arraylist.add(array[item] as Double)
    }
    model.geo = arraylist
    val user = ModelUser()
    val userObject = jsonObject.optJSONObject("user")
    user.name = userObject.optString("name")
    user.followers_count = userObject.optInt("followers_count")
    model.user = user
    Log.d("zyh", "testJsonObject消耗:" + (System.nanoTime() - beginTime) / 1000 + "微秒")
    Log.d("zyh", model.toString())
}

结果如下:

1
2
testJsonObject消耗865微秒
Model(id=912345678902, text=@android_newb just use android.util.JsonReader!, geo=[50.454722, -104.606667], user=ModelUser(name=jesse, followers_count=2))

和使用 gson 解析相比,结果差了 33 倍。那么现在你觉得使用第三方开源库还快吗?

1.6 JsonReader 解析

如果你的数据量 json 特别的大的时候,由于 JSONObject 是把所有的 json 全加载到内存中的,会造成内存的暴涨,这时候可以使用进阶类 JsonReader 类,通过流的方式,读取一段解析一段。这样内存就不会暴涨,保证运行的稳定性。

使用 JsonReader 解析的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
fun testJsonReader() {
    val beginTime = System.nanoTime()
    val inputStream = ByteArrayInputStream(json.toByteArray())
    //通过流来完成 jsonReader 的创建
    val jsonReader = JsonReader(InputStreamReader(inputStream, "UTF-8"))
    jsonReader.beginObject()
    val model = Model()
    while (jsonReader.hasNext()) {
        when (jsonReader.nextName()) {
            "id" -> {
                model.id = jsonReader.nextLong()
            }
            "text" -> {
                model.text = jsonReader.nextString()
            }
            "geo" -> {
                if (jsonReader.peek() != JsonToken.NULL) {
                    val doubles = arrayListOf<Double>()
                    jsonReader.beginArray()
                    while (jsonReader.hasNext()) {
                        doubles.add(jsonReader.nextDouble())
                    }
                    jsonReader.endArray()
                    model.geo = doubles
                } else {
                    jsonReader.skipValue()
                }
            }
            "user" -> {
                if (jsonReader.peek() != JsonToken.NULL) {
                    val modelUser = ModelUser()
                    jsonReader.beginObject()
                    while (jsonReader.hasNext()) {
                        when (jsonReader.nextName()) {
                            "name" -> {
                                modelUser.name = jsonReader.nextString()
                            }
                            "followers_count" -> {
                                modelUser.followers_count = jsonReader.nextInt()
                            }
                            else -> {
                                jsonReader.skipValue()
                            }
                        }
                    }
                    jsonReader.endObject()
                    model.user = modelUser
                } else {
                    jsonReader.skipValue()
                }
            }
            else -> {
                jsonReader.skipValue()
            }
        }
    }
    jsonReader.endObject()
    Log.d("zyh", "testJsonReader消耗:" + (System.nanoTime() - beginTime) / 1000 + "微秒")
    //打印输出
    Log.d("zyh", model.toString())
}

结果:

1
2
testJsonReader消耗1871微秒
Model(id=912345678902, text=@android_newb just use android.util.JsonReader!, geo=[50.454722, -104.606667], user=ModelUser(name=jesse, followers_count=2))

消耗的时间和使用 JSONObject 对比来说,大了 2.1倍,但是还是比使用第三库小太多了。

1.7 总结对比

解析方式 消耗时间(一加 3t)821 消耗时间(一加 7pro)855
Gson 28643微秒 8227微秒
JsonObject 865微秒 154微秒
JsonReader 1871微秒 431微秒

2. 序列化

让我们来看一下 序列化 的时间对比

2.1 gson 序列化

1
2
3
4
5
6
fun testCreateGson(model: Model) {
    val beginTime = System.nanoTime()
    val models = Gson().toJson(model)
    Log.d("zyh", "gson消耗:" + (System.nanoTime() - beginTime) / 1000 + "微秒")
    Log.d("zyh", models.toString())
}

结果如下:

1
2
16873微秒
{"geo":[50.454722,-104.606667],"id":912345678902,"text":"@android_newb just use android.util.JsonReader!","user":{"followers_count":2,"name":"jesse"}}

2.2 JSONObject 序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fun testCreateJson(model: Model) {
    val beginTime = System.nanoTime()
    val jsonObject = JSONObject()
    jsonObject.put("id", model.id)
    jsonObject.put("text", model.text)

    val jsonArray = JSONArray()
    for (item in model.geo!!.indices) {
        jsonArray.put(model.geo!![item])
    }
    jsonObject.put("geo", jsonArray)

    val jsonUser = JSONObject()
    jsonUser.put("name", model.user?.name)
    jsonUser.put("followers_count", model.user?.followers_count)
    jsonObject.put("user", jsonUser)

    Log.d("zyh", "testJsonObject消耗:" + (System.nanoTime() - beginTime) / 1000 + "微秒")
    Log.d("zyh", jsonObject.toString())
}

结果如下

1
2
160微秒
{"id":912345678902,"text":"@android_newb just use android.util.JsonReader!","geo":[50.454722,-104.606667],"user":{"name":"jesse","followers_count":2}}

JSONObject 比 gson 快 105 倍以上。

2.3 总结对比

序列化方式 消耗时间(一加 3t)821 消耗时间(一加 7pro)855
Gson 16874微秒 3248微秒
JsonObject 160微秒 29微秒

3. 解决方案

那么在开发中应该如何来加速 json 的解析,减少 json 解析对代码的影响呢?

  • MSON,让JSON序列化更快–美团
  • 阿里的JsonLube
  • moshi 的Codegen

3.1 MSON

官方介绍,但是此方案经过这么长时间了,也没有开源。

根据描述的知,此方案是通过 APT 的方式,在编译的时候动态的生成具体的JSONObject 的序列化和反序列话的代码。

在使用的时候,通过 MSON 类,来解析。

1
2
MSON.fromJson(json, clazz); // 反序列化
MSON.toJson(bean); // 序列化

3.2 JsonLube

源码地址

此方案是阿里开源的方案,也是通过 APT 的方式,在编译的时候动态的生成具体的JSONObject 的序列化和反序列话的代码。

通过注解的方式,在想要生成的类的地方,加上 @FromJson 和 @ToJson 的注解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@FromJson
@ToJson
public class Teacher {
	private String name;
  private int age;
  public List<Student> students;  //支持bean的嵌套
  //...get/set 方法
}

public class Student {
	public String name;
	public int age;
	public int sex;
}

这样就可以在编译的时候生成下面的类。

image-20200304174641133

生成解析类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@ProguardKeep
public final class Teacher_JsonLubeParser implements Serializable {
    public Teacher_JsonLubeParser() {
    }

    public static Teacher parse(JSONObject data) {
        if (data == null) {
            return null;
        } else {
            Teacher bean = new Teacher();
            bean.name = data.optString("name", bean.name);
            bean.age = data.optInt("age", bean.age);
            JSONArray studentsJsonArray = data.optJSONArray("students");
            if (studentsJsonArray != null) {
                int len = studentsJsonArray.length();
                ArrayList<Student> studentsList = new ArrayList(len);

                for(int i = 0; i < len; ++i) {
                    Student item = Student_JsonLubeParser.parse(studentsJsonArray.optJSONObject(i));
                    studentsList.add(item);
                }

                bean.students = studentsList;
            }

            bean.bestStudent = Student_JsonLubeParser.parse(data.optJSONObject("bestStudent"));
            bean.setSex(data.optInt("sex", bean.getSex()));
            return bean;
        }
    }
}

生成序列化的类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@ProguardKeep
public final class Teacher_JsonLubeSerializer implements Serializable {
    public Teacher_JsonLubeSerializer() {
    }

    public static JSONObject serialize(Teacher bean) throws JSONException {
        if (bean == null) {
            return null;
        } else {
            JSONObject data = new JSONObject();
            data.put("name", bean.name);
            data.put("age", bean.age);
            if (bean.students != null) {
                JSONArray studentsJsonArray = new JSONArray();
                Iterator var3 = bean.students.iterator();

                while(var3.hasNext()) {
                    Student item = (Student)var3.next();
                    if (item != null) {
                        studentsJsonArray.put(Student_JsonLubeSerializer.serialize(item));
                    }
                }

                data.put("students", studentsJsonArray);
            }

            data.put("bestStudent", Student_JsonLubeSerializer.serialize(bean.bestStudent));
            data.put("sex", bean.getSex());
            return data;
        }
    }
}

3.3 moshi

源码地址

moshi 支持 kotlin 的 Codegen ,仅支持 kotlin 。

需要在使用的地方加上注解 @JsonClass(generateAdapter = true) 。

1
2
@JsonClass(generateAdapter = true)
data class ConfigBean(var isGood: Boolean ,var title: String ,var type: CustomType)

通过编译,自动生成的 kotlin 的 fromJson 和 toJson.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
override fun fromJson(reader: JsonReader): ConfigBean {
    var isGood: Boolean? = null
    var title: String? = null
    var type: CustomType? = null
    reader.beginObject()
    while (reader.hasNext()) {
        when (reader.selectName(options)) {
            0 -> isGood = booleanAdapter.fromJson(reader) 
            1 -> title = stringAdapter.fromJson(reader) 
            2 -> type = customTypeAdapter.fromJson(reader)
            -1 -> {
                reader.skipName()
                reader.skipValue()
            }
        }
    }
    reader.endObject()
    var result = ConfigBean(isGood = isGood ,title = title ,type = type
    return result
}

override fun toJson(writer: JsonWriter, value: ConfigBean?) {
    writer.beginObject()
    writer.name("isGood")
    booleanAdapter.toJson(writer, value.isGood)
    writer.name("title")
    stringAdapter.toJson(writer, value.title)
    writer.name("type")
    customTypeAdapter.toJson(writer, value.type)
    writer.endObject()
}

4. 进阶

参考上面的解决方案,我们可以得知,现在有两种方式来解决

两种解决方案:

  1. 新项目直接在原有的bean 中创建tojson 和 fromjson 方法,继承 IJSON 接口这样就可以通过 JSON解析类来统一进行转换,这种情况下不需要混淆。
  2. 老项目中有很多 bean ,这样的情况下,我们可以通过JsonLube或者moshi来解决,然后增加混淆。

4.1 新项目

编写 idea 或者 Android studio 的插件,使用插件来自动生成 toJson 和 fromJson 方法,或者手写toJson和fromJson方法,这样的话在混淆的时候,不需要 keep 住模型类,所有代码都可以混淆。不过手写解析工作量太大,且容易出错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public interface ITestJson {
    Object fromJson(String json) throws Exception;
    String toJson() throws Exception;
}
public class MOSN {
    public static <T extends ITestJson> T fromJson(String json, Class<T> clazz) throws Exception {
        ITestJson iTestJson = clazz.newInstance();
        return (T) iTestJson.fromJson(json);
    }
    public static String toJson(ITestJson iTestJson) throws Exception {
        return iTestJson.toJson();
    }
}
//具体使用
TestPerson testPerson = MOSN.fromJson("", TestPerson.class);
String json = MOSN.toJson(testPerson);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestPerson implements ITestJson {

    @JsonName("name")
    private String name;
    @JsonName("age")
    private int age;

    public TestPerson fromJson(String json) throws Exception {
        JSONObject jsonObject = new JSONObject(json);
        name = jsonObject.optString("name");
        age = jsonObject.optInt("age");
        return this;
    }

    public String toJson() throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", name);
        jsonObject.put("age", age);
        return jsonObject.toString();
    }
}

4.2 老项目

通过JsonLube或者moshi来解决,但是存在一些问题。

  • 生成的类不能混淆,因为要通过模型类找到具体的生成类。
  • 模型类也不能混淆,因为要使用 get/set 方法。

由于代码中使用了反射,所以不能混淆序列化和反序列话的方法名和类名。

解决不能混淆方法名的问题,因为这个类中只有一个方法,所以我们可以通过getDeclaredMethods()来获取这个类的所有方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static <T> T fromJson(JSONObject json, Class<T> clazz) throws JsonLubeParseException {
    String parserClassName = getParserClassName(clazz);
    try {
        //因为生成的 bean 对象中 只有一个方法
        Class<?> parserClass = Class.forName(parserClassName);
        Method method = parserClass.getDeclaredMethods()[0];//获取类的方法,不包括父类的方法
        return (T) method.invoke(null, json);
//            Method parseMethod = parserClass.getMethod("parse", JSONObject.class);
//            return (T) parseMethod.invoke(null, json);
    } catch (Exception e) {
        throw new JsonLubeParseException(e);
    }
}

更近一步的方法,我们可以让自动生成的类实现接口,这样我们就可以不用反射来调用方法,而使用接口中的方法就可以,这样可以节省效率,因为反射效率很低,速度很慢。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public interface ITestJson {
    Object fromJson(String json) throws Exception;
    String toJson() throws Exception;
}
public static <T extend ITestJson> T fromJson(JSONObject json, Class<T> clazz) throws JsonLubeParseException {
    String parserClassName = getParserClassName(clazz);
    try {
        //因为生成的 bean 对象中 只有一个方法
        Class<?> parserClass = Class.forName(parserClassName);
      	ITestJson iTestJson = parserClass.newInstance();
        return (T) iTestJson.fromJson(json);
    } catch (Exception e) {
        throw new JsonLubeParseException(e);
    }
}

解决不能混淆类的名称,因为是通过拼接的方式得到具体的类名,所有混淆之后,就不能通过这种方式来找到具体的类。那么怎么解决这个问题呢?

1
2
3
4
private static String getParserClassName(Class<?> beanClass) {
    String name = beanClass.getCanonicalName();
    return name + "_JsonLubeParser";
}

那么如果解决这个问题呢?

我们可以通过ASM来动态的修改原来的bean,而不是通过APT来动态的生成其他类,这样的话,我们就可以完美解决所有问题。但是通过ASM在编译阶段生成方法是比较复杂的,我们需要时间来研究~~

如果你有更好的解决方案,可以留言告诉我~

如果你喜欢我的文章,可以关注我的掘金、公众号、博客、简书或者Github!

简书: https://www.jianshu.com/u/a2591ab8eed2

GitHub: https://github.com/bugyun

Blog: https://ruoyun.vip

掘金: https://juejin.im/user/56cbef3b816dfa0059e330a8/posts

CSDN: https://blog.csdn.net/zxloveooo

欢迎关注微信公众号