Android 使用 Retrofit+Gson 的数据解析研究

阅读数:181 2019 年 9 月 27 日 10:23

Android使用Retrofit+Gson的数据解析研究

1 背景

复制代码
Android 客户端解析网络请求,目前比较常用的做法是 Retrofit+Gson,最小集配置的做法如下:

new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
这样做可以满足绝大部分的数据解析,但是会遇到两个问题:

  • 服务端下发字段和客户端字段类型定义不一致,造成整个数据解析失败;

  • 异构列表类型数据的解析方式。

这里异构列表是指:列表中包含的每一项数据的 java 实体类可以不同。

本文将探讨这两个问题产生的原因及解法。

2 前置知识

1、gson 与 retrofit 的结合做了什么?

复制代码
// 第一步:初始一个可用的解析 gson 的 retrofit
new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
// 第二步:加入 TypeAdapter 的列表
public static GsonConverterFactory create() {
return create(new Gson());
}
//Gson() 的构造函数中加入了 TypeAdapter 的列表
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
factories.add(TypeAdapters.STRING_FACTORY);
...
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
...
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
// 第三步:当数据从网络回来后被 GsonConverterFactory.java 拦截:
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
// 第四步:使用对应的 TypeAdapter 完成数据解析
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
...
try {
result = adapter.read(jsonReader);
}

以上四个代码片段,可以看到 Retrofit+Gson 的解析,是将 Json 串用 JsonReader 读入,根据类型用对应的 TypeAdapter 完成解析。

2、我们常用的 Gson 解析方法,将一个 json 串转换成对应的 java 实体类,是怎么做到的?

将一个 json 串,转换成对应的 java 实体类常用的方法有三个:

方法一:

复制代码
new Gson().fromGson()
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
...
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
...
}

方法二:

Gson gson =

new GsonBuilder().registerTypeAdapter().create();

registerTypeAdapter 需要传入 TypeAdapter, JsonSerializer 或 JsonDeserializer ,在 TypeAdapter、JsonDeserializer 中重写对应的 read 方法完成解析。此外还可以使用 registerTypeHierarchyAdapter 方法进行注册。这两个方法的区别如下:

header registerTypeAdapter registerTypeHierarchyAdapter
支持泛型
支持继承

方法三:

在要解析的类上添加注释 @JsonAdapter,将 TypeAdpater,TypeAdapterFactory,JsonSerializer 或 JsonDeserializer 其中之一作为传入参数。之后的解析方法同方法二。

注意:@JsonAdapter 的优先级比 GsonBuilder.registerTypeAdapter 的优先级更高。

从三种方法中可以看出,最终都是使用 TypeAdpater 或 JsonDeserializer 进行解析。

从 json 转成 Object 的数据解析,只要重写 TypeAdapter 的 read() 或者 JsonDeserializer 的 deserialize() 方法即可:

复制代码
public abstract class TypeAdapter<T> {
public abstract T read(JsonReader in) throws IOException;
}
public interface JsonDeserializer<T> {
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException;
}

TypeAdapter 和 JsonDeserializer 的解析效率不同,如下表,所以优先选用 TypeAdapter。Retrofit+Gson 也是优先选用的 TypeAdapter。

TypeAdapter 和 JsonDeserializer 的比较

TypeAdapter JsonSerializer、JsonDeserializer
引入版本 2.0 1.x
Stream API 支持 不支持 *,需要提前生成 JsonElement
内存占用 比 TypeAdapter 大
效率 比 TypeAdapter 低

3 前服务端下发字段和客户端字段类型不兼容的处理

根据前两节的分析,Gson 利用各种 TypeAdapter 完成基本数据类型和自定义类型的数据解析。默认情况下,解析失败时,向上抛出异常,如果没有特别处理此异常,则整个解析失败。所以,我们据此,在异常发生时,跳过这个字段的解析,继续向下解析,从而完成字段类型不兼容的处理。

前文讲到,Retrofit+Gson 初始化时,添加了一系列 TypeAdapter,其中包括 ReflectiveTypeAdapterFactory.java。这个类根据反射拿到要解析的 java 实体类的类型,完成解析。read() 的关键代码如下:

复制代码
public T read(JsonReader in) throws IOException {
...
try {
in.beginObject();
while(in.hasNext()) {
String name = in.nextName();
ReflectiveTypeAdapterFactory.BoundField field = (ReflectiveTypeAdapterFactory.BoundField)this.boundFields.get(name);
if (field != null && field.deserialized) {
field.read(in, instance);
} else {
in.skipValue();
}
}
} catch (IllegalStateException var5) {
throw new JsonSyntaxException(var5);
} catch (IllegalAccessException var6) {
throw new AssertionError(var6);
}
in.endObject();
return instance;
}
}

从上述代码可见,当 field.read(in, instance) 发成异常时,会将异常继续向上抛出, 如果没有处理这个异常,则会造成解析失败。据此我们修改代码如下:

复制代码
public T read(JsonReader in) throws IOException {
...
try {
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
try {
field.read(in, instance);
} catch (Exception e) {
in.skipValue();
}
}
}
}
catch (Exception e) {
//throw new AssertionError(e);
in.skipValue();
}
in.endObject();
return instance;
}

当 field.read(in, instance) 发生异常时,跳过字段,继续向下解析。

4 异构列表解析

json 数据结构定义如下,增加 "type" 字段用于标识对应的的 java 实体类:

复制代码
{
"list": [
{
"type": "a",
...
},
{
"type": "b",
...
},
{
"type": "c",
...
}
]
}

抽象出 BaseCard 基类

复制代码
public class BaseCard {
@SerializedName("type")
private String cardType;
}

其他自定义卡片类型 ACard, BCard, CCard 都是 BaseCard 的子类。

复制代码
public class ACard extends BaseCard {
}

自定义 TypeAdapter,建立不同 type 和 java 实体类的映射关系,并将这种映射关系保存在 mMap 中。

复制代码
protected <T extends BaseTypeT> void addSubTypeAdapter(TypeAdapterFactory factory,
Gson gson, String typeName, Class<T> subTypeClass) {
mMap.put(typeName, new SubTypeReadWriteAdapter<>(
gson.getDelegateAdapter(factory, TypeToken.get(subTypeClass))));
}

重写 TypeAdapter 的 read() 方法,根据 type 的值从 Map 中取出对应的 java 实体类的 TypeAdapter, 从而完成数据解析。

定义 DemoTypeAdapterFactory,利用工厂模式 create() 的回调,生成 TypeAdapter 列表。

复制代码
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (TypeAdapter<T>) createTypeAdapter(gson);
}
public TypeAdapter<Card> createTypeAdapter(final Gson gson) {
final TypeAdapterFactory factory = this;
return new BaseTypeAdapter<Card>(gson, "type") {
{
addSubTypeAdapter(factory, gson, "a",
ACard.class);
...
}
}

在 BaseCard.java 上添加注释:

@JsonAdapter(DemoTypeAdapterFactory.class)
从而实现了完成异构列表数据类型的解析。

作者介绍:
推敲(企业代号名),目前负责贝壳新网销平台的相关移动端工作。

本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。

原文链接:

https://mp.weixin.qq.com/s/Lqt55t66lE_4WTUVeqzo-A

评论

发布