写点什么

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:232818

评论

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

【实践篇】手把手教你落地DDD | 京东云技术团队

京东科技开发者

DDD Archetype 企业号 5 月 PK 榜 三层架构

Java中synchronized锁的深入理解

Java synchronized

深度学习进阶篇-国内预训练模型[6]:ERNIE-Doc、THU-ERNIE、K-Encoder融合文本信息和KG知识;原理和模型结构详解。

汀丶人工智能

人工智能 自然语言处理 深度学习 预训练模型 Transformer

NFTScan | 05.22~05.28 NFT 市场热点汇总

NFT Research

NFT 热点

从源码全面解析 dubbo 服务注册的来龙去脉

Java 源码 dubbo

校园共享电动车发展现状及未来趋势

共享电单车厂家

共享电动车厂家 校园共享电单车 校内共享电动车

最佳实践:基于vite3的monorepo前端工程搭建 | 京东云技术团队

京东科技开发者

前端 vite Monorepo lodash vue3 vite 企业号 5 月 PK 榜

ERP已死,秒杀系统称王!阿里巴巴内部「10亿级并发设计文档」

Java你猿哥

数据库 缓存 分布式 消息队列 秒杀系统

阿里逆天级调优方案,内部这套Java性能调优实战宝典,堪称教科书

Java 性能优化 性能调优

盘点一款好用的运维团队协同软件,用过真香!

行云管家

运维 IT运维 协同合作

腾讯高工内产,Github都没的SpringBoot源码手册

Java spring Spring Boot 框架

惊艳!京东T8纯手码的Redis核心原理手册,基础与源码齐下

Java 数据库 redis 缓存

如何设计一个自动化测试平台

老张

自动化测试 测试开发 测试平台

阿里技术大佬限产的Netty核心原理剖析手册,看完你不心动?

Netty

Tomcat处理http请求之源码分析 | 京东云技术团队

京东科技开发者

tomcat container HTTP 企业号 5 月 PK 榜

防lombok实现一个Getter注解,AbstractProcessor实例

Java你猿哥

Java ssm lombok

听听飞桨框架硬核贡献者如何玩转开源!

飞桨PaddlePaddle

开源社区 百度飞桨 PaddlePaddle

CST如何查看哪些 GPU 在线?

思茂信息

cst cst使用教程 cst操作 cst电磁仿真 cst仿真软件

CISA零信任成熟度模型(译文)

权说安全

首届百度商业AI技术创新大赛启动 点燃AIGC革新“星火”

百度Geek说

人工智能 百度 AIGC 企业号 5 月 PK 榜

堪称一绝!阿里技术人都用的Nginx笔记手册,应用到架构齐全

nginx

5G和led显示屏有什么关系

Dylan

技术 5G LED显示屏

卧薪尝胆30天!啃透京东大牛的高并发设计进阶手册,终获P7意向书

Java 系统设计 高并发

2023年天津等级测评机构有哪些?具体位置在哪里?

行云管家

等保 等保测评 等级 天津

500代码行代码手写docker-设置网络命名空间

蓝胖子的编程梦

k8s 容器网络 ,docker 容器网络方案 容器网络平台

敏捷项目管理中缺陷bug的跟踪和管理

顿顿顿

Scrum 敏捷开发 缺陷管理 敏捷项目管理 敏捷开发管理工具

CIO视角|平台工程带来的优势与机遇

SEAL安全

IdP 平台工程 企业号 5 月 PK 榜 内部开发平台

GitHub星标126K的京东「微服务进阶笔记」首次开源!好评如潮

Java你猿哥

Java 架构 微服务 微服务架构 架构师

英特尔黑科技加持,腾讯应用宝登陆电脑:安卓应用完美移植PC 更有神器辅助

E科讯

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