我们一般写 json 解析,一般都是使用比较成熟的第三方库gson
、fastjson
、jackson
等。
但是你知道吗?
这些库在使用的时候,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 |
---|---|---|
Gson | 28643微秒 | 8227微秒 |
JsonObject | 865微秒 | 154微秒 |
JsonReader | 1871微秒 | 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 |
---|---|---|
Gson | 16874微秒 | 3248微秒 |
JsonObject | 160微秒 | 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;
}
这样就可以在编译的时候生成下面的类。
生成解析类
@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. 进阶
参考上面的解决方案,我们可以得知,现在有两种方式来解决
两种解决方案:
- 新项目直接在原有的bean 中创建tojson 和 fromjson 方法,继承 IJSON 接口这样就可以通过 JSON解析类来统一进行转换,这种情况下不需要混淆。
- 老项目中有很多 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
优快云: https://blog.youkuaiyun.com/zxloveooo
欢迎关注微信公众号