Android json 解析,你不知道的事

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

但是你知道吗?

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

1. 反序列化

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

1.1 定义 json

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

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 创建模型对象类

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 来解析对象,代码如下

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())
}

结果如下:

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 解析

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())
}

结果如下:

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 解析的代码如下:

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())
}

结果:

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
Gson28643微秒8227微秒
JsonObject865微秒154微秒
JsonReader1871微秒431微秒

2. 序列化

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

2.1 gson 序列化

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())
}

结果如下:

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 序列化

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())
}

结果如下

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
Gson16874微秒3248微秒
JsonObject160微秒29微秒

3. 解决方案

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

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

3.1 MSON

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

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

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

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

3.2 JsonLube

源码地址

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

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

@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

生成解析类

@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;
        }
    }
}

生成序列化的类

@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) 。

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

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

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 住模型类,所有代码都可以混淆。不过手写解析工作量太大,且容易出错。

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);
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()来获取这个类的所有方法。

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);
    }
}

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

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

现在我还没有想到具体的解决方案,如果你有好的解决方案,可以留言告诉我~

如果你喜欢我的文章,可以关注我的掘金、公众号、博客、简书或者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

欢迎关注微信公众号

image

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值