本文来自于 Foursquare的团队技术博客 。Foursquare团队最近发现自家的Android app在使用过程中并没有传说当中的“如黄油般顺滑”,而且通过查看logcat发现app会频繁地发起 GC_FOR_ALLOC
调用,Activity和Fragment之间的跳转也没有想象当中那样快。于是好学的工程师们开始去挖掘这个背后的内容。
通过初步的性能测试,我们发现以上现象的很大部分原因来自于app在解析JSON的时候耗时过多。于是我们决定深入去看看app的JSON解析代码有些什么问题?
Foursquare的Android应用都是通过一个 JSON API 来同服务器进行交互的,JSON的解析则是使用Google的 Gson 库,也就是说使用Gson来反序列化JSON字符串生成相应的Java对象供Android开发者进行下一步处理。我们一般是这样使用Gson将一个描述venue(场地)的字符串转化成Java对象的:
Java
String venueJson = "{/"name/": /"Starbucks/" ,/"city/": /"New York/"}”; Gson gson = new Gson(); Venue venue = gson.fromJson(venueJson, Venue.class); // Do something with object. String name = venue.getName();
程序是这样没问题,可是实际上我们只需要venue的Name属性,但是上面的程序却解析了整个JSON字符串。是不是这里造成了一部分性能问题?如果我们只解析我们想要的属性,会不会降低JSON解析的时间?于是他们开始使用 Gson streaming API ,确保Android在解析JSON的时候每次只解析自己需要的属性,示例代码如下:
Java
InputStream in = ...; // Obtained from HTTP client. JsonReader reader = new JsonReader(in); Gson gson = new Gson(); Venue venue = gson.fromJson(reader, Venue.class); // Do something with object. String name = venue.getName();
Java
public interface JsonDeserializer{ T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException; }
Type typeOfT,那么
Gson 就会在系统中检查是否存在相应的自定义deserializer 处理这个 typeOfT type 。如果存在这样的自定义deserializer ,那么系统便会调用这个自定义 deserializer的 deserialize
方法。 我们在系统中为几种type自定义了deserializer ,其中一个就是最外层的 Response
type ,它封装了所有 Foursquare API 的响应: Java
class ResponseDeserializer implements JsonDeserializer{ @Override public Response deserialize(JsonElement json) { JsonObject object = json.getAsJsonObject(); String meta = object.get("meta"); String result = object.get(“response”); // Do custom parsing. return response; } }
虽然我们使用了 Gson 的 streaming API,但是我们自定义的 deserializer 会把任何我们想要反序列化的JSON stream 都读进了一个 JsonElement
对象树里面,然后这个JsonElement 再被传入 deserialize
方法 (然而这恰恰是我们要避免的情况)。更糟糕的是,我们从服务器接收到的每一个响应都会被包装成 response type ,这就阻碍了流式反序列化的进行( streaming deserialization)。
最后的实践证明我们可以采取 TypeAdapter s 和 TypeAdapterFactory s 方案来代替 JsonDeserializer。示例代码如下:
Java
abstract class TypeAdapter{ ResponseV2 read(JsonReader in); }
我们从上面的代码可以发现 JsonReader
stream 被直接传入 read()
方法而不是传入一个 JsonElement
树。通过上面的改进之后,即把我们自定义的 deserializer 改为继承 TypeAdapter
s 和 TypeAdapterFactory
s ,我们app在解析大的响应的时候解析时间减少了 50% 以上。更重要的是,整个 app 感觉比以前要快多了 , app的滑动也顺畅多了。
TypeAdapter
s 实现的deserializer 代码肯定要比使用 JsonDeserializer
s 实现的deserializer 代码要丑,因为 TypeAdapter
s 比起 JsonDeserializer
s 更偏向于底层了,或者说 JsonDeserializer
s 要比 TypeAdapter
s 更抽象一下,而越是底层的代码越具体。相应的 JsonDeserializer
s 肯定也要比 TypeAdapter
s 方案要灵活些。