写点什么

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

  • 2019-09-27
  • 本文字数:3609 字

    阅读完需:约 12 分钟

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

1 背景


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


new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())


这样做可以满足绝大部分的数据解析,但是会遇到两个问题:


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

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


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


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

2 前置知识

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

//第一步:初始一个可用的解析gson的retrofitnew 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 方法进行注册。这两个方法的区别如下:


headerregisterTypeAdapterregisterTypeHierarchyAdapter
支持泛型
支持继承


方法三:


在要解析的类上添加注释 @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 的比较


TypeAdapterJsonSerializer、JsonDeserializer
引入版本2.01.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


2019-09-27 10:232551

评论

发布
暂无评论
发现更多内容

淘宝商品评论API返回值中的评分与星级解读

技术冰糖葫芦

API Explorer API 编排 api 货币化 API 文档

红圈CRM携手金蝶ERP,彻底告别数据孤岛,效率飙升的数字化转型之旅!

聚道云软件连接器

案例分享

入选2024 分布式算力十佳“星耀”案例,天翼云为算力实践提供样板参考

极客天地

江苏徐州正规等保测评机构有吗?在哪里?

行云管家

等保 等保测评 徐州

一文了解云真机的使用

优测云服务平台

手机 测试 兼容性测试 移动设备识别

创新引领高质量发展,中微公司庆祝科创板上市五周年

新消费日报

开启SRE培训之旅:美高梅(MGM) 启动服务稳定性建设

雅菲奥朗

SRE SRE培训 SRE考试 SRE认证 SRE Foundation

神州鲲泰“电网变电站边缘计算解决方案

神州数码

GPU云服务器:计算型与渲染型的最佳应用场景

Ogcloud

gpu 云服务器 GPU云服务器 云服务器租用

有人泼冷水:为什么 AI 基础设施创业如此艰难?

小猿姐

创业 AI AI 基础设施

Web前端浅谈ArkTS组件开发

OpenTiny社区

typescript 前端 OpenTiny

PPT插入的图片去哪找?3款自带图库的在线PPT软件推荐!

彭宏豪95

效率工具 职场 PPT 办公软件 AI生成PPT

畜牧业也需要堡垒机吗?为什么?

行云管家

网络安全 等保 堡垒机 畜牧业

为什么我不建议用Excel做进销存系统?

源字节1号

小程序 开源 前端 后端

实力强劲!云起无垠上榜《数字安全护航技术能力全景图》

云起无垠

数业智能心大陆用AI解锁心灵健康

心大陆多智能体

智能体 AI大模型 心理健康 数字心理

1688 商品详情的 API 接口获取全解析

Noah

凤凰项目(Phoenix Project)精要 - 概念

Anliven

读书笔记 团队管理 DevOps 运维 团队效能

2024青岛软博会,招展工作顺利进行,诸多知名软件企业踊跃报名

AIOTE智博会

MES系统助力制造业数字化转型

万界星空科技

数字化 数字化运维 生产管理系统 mes 万界星空科技

IPIDEA分享:匿名代理的定义及其工作原理

IPIDEA全球HTTP

代理IP 匿名代理

Android使用Retrofit+Gson的数据解析研究_文化 & 方法_推敲_InfoQ精选文章