Gson大家一定不陌生,在很多项目中都大规模使用。
例如常见的:
网络请求
->返回Json数据
->Gson解析为对象
->渲染页面
很多时候,历史项目包含很多Gson解析对象在UI线程的操作,或者说即使在子线程其实也会影响页面展现速度。
大家都了解Gson对于对象的解析,如果不单独的配置TypeAdapter,那么其实内部是充满反射的。
问题来了:
有没有什么低侵入的方案可以尽可能去除反射操作,从而提升运行效率?描述思路即可。
更多问答 >>
-
每日一问 UndeclaredThrowableException 是什么异常?
2021-12-02 00:50 -
每日一问 | android hidden api 不是禁用反射,以及如何突破,「元反射」不行了?
2022-02-08 23:51 -
每日一问 | 如何构造一个 hide interface 的实现类?
2022-02-08 23:51 -
每日一问 | 脱糖对于Android 打包期间插桩的有什么影响?
2022-03-07 21:26 -
每日一问 | 好奇ActivityThread中为什么会有一个 Application的集合?
2021-08-30 21:36 -
每日一问 | 关于 Activity 重建,值得探究的几个问题
2021-08-30 21:37 -
每日一问 | ViewModel 在什么情况下的「销毁重建」能够对数据进行无缝恢复?
2021-08-25 18:11 -
每日一问 | 我们经常说到的 Android 脱糖指的是什么?
2021-07-11 22:06 -
2021-07-11 22:06
通过GsonBuilder的
我在想,应该可以弄一个TypeAdapterFactory,集合所有需要优化的实体类对应的TypeAdapter,然后通过ASM,在Gson的构造函数中把这个TypeAdapterFactory添加到registerTypeAdapter
方法设置的TypeAdapter,会包装成TypeAdapterFactory放在一个叫factories
的List<TypeAdapterFactory>里面,当我们调用create
方法创建Gson对象实例,这些TypeAdapterFactory最终会添加到Gson里面的同名成员变量factories
。factories
里,这样的话,就不需要动原来的代码,就算原来的代码是直接new Gson()
,也能享受到自定义TypeAdapter的效果。我等下回去试试。Gson这个类有两个构造函数(我的版本是2.8.8,目前最新):
这个无参构造函数最终也是会调用另一个有参数的:
这个构造函数,大部分代码都是在给本地变量
factories
添加各种TypeAdapterFactory,最后通过Collections.unmodifiableList
包装了一下,赋值给了同名成员变量。顺便提一下,通过GsonBuilder.
create
创建的Gson对象,也是调用Gson有参数的那个构造函数:也就是说,使用常规方式创建的Gson对象,始终都会走到这个有参的构造函数里面,这可以作为我们代码的注入点。
好。我们的思路是,在Gson有参构造函数调用Collections.unmodifiableList
之前,添加上我们自己的TypeAdapterFactory,即调用本地变量factories
的add
方法。看下代码怎么写:通常我们使用Gson都是直接引用的jar包,所以这里只对jar文件作处理。
看看processGsonJar
方法是怎么写的:我们会对每一个jar包的内容进行检查,目标是
【下一个环节】,就是借助ClassReader和ClassVisitor对这个class文件进行访问,我们需要找的是构造函数,所以重写了ClassVisitor的com/google/gson
路径下的Gson.class
,一旦找到这个文件,则进入【下一个环节】,其他文件无需修改。visitMethod
方法,并在里面进一步判断(是有参数的那个),找到这个有参的构造函数之后,会返回一个GsonMethodVisitor,等下会说。可以看到在ClassReader的accept
方法调用完之后,还把新的Gson.class写出到一个单独的文件里,这是方便后面查看修改结果的。来想一下,我们注入的代码,应该是怎么样的呢?
其实只需要知道Factory的完整类名就行了。可以用一行代码搞定:转换成ASM的操作代码:
注意!
第一行的ALOAD 1
,后面的索引1,在实际应用中可能并不是这个数,因为我们刚刚写的是模拟代码,没有其他多余的局部变量所以通过插件转换时这个数会小很多,但是当真正放在Gson构造函数中就不一样了。这怎么办呢?可以先看一次Gson.class的字节码,确定好factories
这个局部变量的索引是多少。慢着,直接看字节码很会头疼,不如先看一遍源码,确定好factories
的大致位置:嗯,就在成员变量
那现在来builderHierarchyFactories
赋值豫剧的下面。javap -v
看一下字节码:看highlight部分,刚好是在
重点来了,看最后一句builderHierarchyFactories
下面创建的ArrayList实例,证明这就是factories
引用的对象了。astore 18
,把栈顶的值保存到局部变量表中,索引是18。好,得到真正的索引值之后,可以继续写代码了:
好,执行assembleDebug这个Task,会在刚刚指定的路径下看到Gson.class:
把它拖到AS里面:
看到了没有!!!已经成功注入代码了!
现在把那个TypeAdapterFactory定义好:我们就拿现成的Rect来作为实体类测试。
可以看到现在重写的create
方法是直接返回一个匿名的TypeAdapter对象,实际应用当然不建议那么做,这里只是为了代码更加简洁。在自定义的TypeAdapter中,重写的write
方法把Rect的四个属性:left
、top
、right
、bottom
按顺序写入,这个逻辑是没有问题的;我们主要是想验证反序列化,也就是read
的效果,所以在重写的read
方法里面,故意把读取出来的四个属性值都翻倍了一下,比如本来是10的,反序列化后会变成20。噢,这里有同学可能会有疑问:刚刚注入的代码,不是通过反射获取到目标类中一个叫
哈哈哈,快往上翻看一下RectAdapterFactory用的什么关键字。我们在声明RectAdapterFactory这个类的时候,用object代替class,就已经是单例模式了,在编译的时候会自动生成一个叫INSTANCE
的静态变量嘛?怎么这里没有声明?INSTANCE
的静态变量,不信你反编译看一下代码。好,现在把刚刚GsonMethodVisitor里面的那个类名,替换成我们新写的这个RectAdapterFactory完整类名。
再到MainActivity里加入测试的代码:注意这里在创建Gson对象时是完全没有进行过配置的,是最直接的创建方式。
按照预期的输出结果,应该是:对吧?
好,编译运行:咦?怎么没有效果?也没有报错啊!
难道是我们注入的代码有问题?再次看看输出的Gson.class:好像也没有问题啊,类名都填对了的。
噢!!!快看上面一句的Factory叫什么?!ReflectiveTypeAdapterFactory!!Reflective!!!反射啊!这不就是默认的对象类处理方式嘛,这个factories
是一个ArrayList,估计在反序列化的时候,也是按顺序从里面去找合适的TypeAdapter,怪不得它放在最后才添加。这样的话,就不能把我们的Factory放在最后了,可以把它放到最前面。改一下GsonMethodVisitor.visitMethodInsn
里面的代码:注意看注释,主要改动的地方有2个:
在
factories
入栈后再入栈了一个0(iconst_0
);把List.
add
方法的签名由(Ljava/lang/Object;)Z
改成了(ILjava/lang/Object;)V
;还有个地方要注意的是!这次是没有弹栈的,没有了原来
mv.visitInsn(POP)
这一句,因为这次调用的add(int index, E element)
方法返回值是void,不需要弹栈!好,执行一下assembleDebug,会发现Gson.class变成这样了:
安装到手机上运行:
可以了!
现在,我们已经成功地在不修改源代码的前提下,去掉了Gson反序列化时的反射操作。噢对了,上面demo的代码在这里:GsonOptimizer.zip ,感兴趣的同学可以下载下来运行看看效果,或者自己尝试改一下其他地方。
不知不觉又写了340多行,冲凉睡觉。
牛批+1
这就是我想到的办法,哈哈
更新啦
哈哈哈
请教一下,很好奇,自己定义了TypeAdapterFactory,为啥不直接通过 @JsonAdapter 就可以了。 而是还需要通过ASM去往Gson构造函数中插入。 ...查看更多
请教一下,很好奇,自己定义了TypeAdapterFactory,为啥不直接通过 @JsonAdapter 就可以了。 而是还需要通过ASM去往Gson构造函数中插入。
原来还有这个注解啊,哈哈哈哈,我不知道啊,学到了👍🏻
高产似母猪!
基于以上三点,可以考虑书写TypeAdapter来进行优化。但是书写TypeAdapter十分麻烦,故可以考虑生成代码来解决。
另外,如果生成的TypeAdapter太多,需要一个一个的注入到Gson实例中,也十分麻烦,故可以考虑再生成一个TypeAdapterFactory来解决。下班时间写了一个库,使用kapt生成TypeAdapter + TypeAdapterFactory,可以参考一下~
GsonBooster感谢感谢,最近在尝试优化json解析速度,试了下你的库,确实有效果。不过有些小问题,参考你的改一改
`
从开始设计的时候就应该考虑到了,同一功能有很多开源库可供选择的时候都应该使用中间层,调用中间层来实现功能。比如json解析,图片加载,网络请求,数据库这些,实现都在中间层,有修改就改那一个类。
每个类里都夸嚓一下new Gson怕是就废废了`
特意注册账号来给你点赞,太对了!!!
简单实现了下,如下:
但是目前的实现,跟原本的比起来耗时并没有减少。。。。所以还需要优化
然后就是自动化的问题,目前有两个想法,第一,apt生成如上代码,第二,asm来实现。牛批
搞了个例子来实现,持续完善中 https://github.com/xingxingxiaoyu/GsonOptimize
本来也想用小缘的方案,但是实际测试了一下,json转成bean,log打印 systemclock 线程时间,主线程耗时不超过1ms。。pass了 ,是我统计有问题?
哈哈哈
如果项目是kotlin的话, 移除gson库, 换moshi, 根据gson库的api 写一套 用moshi实现的库,感觉这样貌似更简单=.=
如果你原先就有中间层, 那更完美了