写点什么

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

评论

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

拓展测试领域,不要不断重复相同的测试

测吧(北京)科技有限公司

测试

龙蜥白皮书精选:CentOS 迁移场景的平滑迁移方案

OpenAnolis小助手

Linux 开源 操作系统 CentOS迁移 龙蜥白皮书

iOS代码覆盖率(一)-全量覆盖率自动化实践

京东科技开发者

swift pod 脚本自动化 企业号 4 月 PK 榜 全量覆盖率

SpringCloud 微信小程序 获取用户信息一键登录

Java你猿哥

Java 小程序 spring ssm spring chloud

架构师日记-为什么数据一致性那么难

京东科技开发者

数据库 MQ 分布式系统 数据一致性 企业号 4 月 PK 榜

JAVA多线程并发编程-避坑指南

京东科技开发者

并发编程 线程 多线程并发 编码规范 企业号 4 月 PK 榜

“阿里爸爸”最新产出:Java面试突击核心讲(1658页),转载40W+

Java你猿哥

Java 面试 面经 八股文 Java八股文

使用NSIS打包超大型软件的几个注意事项

大伟

Alibaba最新“618,双十一”亿级并发系统设计(2023版小册开源)

架构师之道

Java 编程 高并发

浅谈 Spring Bean 的生命周期

做梦都在改BUG

Java spring 生命周期 bean

关注测试原因(针对风险测试)的基于问题的测试手段

测吧(北京)科技有限公司

测试

借降本增效之名,探索开闭原则架构设计

京东科技开发者

低代码 软件架构 开闭原则 企业号 4 月 PK 榜

NFTScan 与 Adot 达成合作伙伴,双方在多链 NFT 数据方面展开合作

NFT Research

NFT NFTScan

真香!阿里最新出品Java面试核心讲(终极版),Github已星标50K

三十而立

Java IT java面试 面试资料

编解码持续升级,「硬」实力铸就视频云最优解

阿里云CloudImagine

云计算 芯片 视频云

Github最新霸榜!“精华版”Java八股文10天帮你彻底解决大厂难题

Java你猿哥

Java 面试 面经 Java工程师 八股文

迄今为止把Mybatis讲解的最详细的PDF,图文并茂,通俗易懂

Java你猿哥

Java 面试 mybatis ssm Mybatis框架

GitHub置顶半个月!78w字百亿级并发设计(全彩PDF)

做梦都在改BUG

Java 架构 系统设计 高并发

阿里为双十一整理的JVM性能优化文档,竟被GitHub“抢开”

做梦都在改BUG

Java 性能优化 JVM 性能调优

GitHub爆火疯传“Java面试手册2023”还原面试现场,通过率90%

三十而立

Java 程序员 IT java面试 大厂面试

IotLink版本更新V1.10.0

山东云则信息科技

Redis事务

京茶吉鹿

nosql redis

C# 世界的《Effective C++》,来自.Net之父的核心揭秘

博文视点Broadview

和ChatGPT pair整理的测试类型清单

BY林子

软件测试 测试类型 ChatGPT

浅谈 Spring Bean 的生命周期

Java你猿哥

Java spring ssm Spring Bean

想成为 Spring 大牛?先了解这 6 种添加 Bean 的方式

Java你猿哥

spring ssm Spring Bean

基于Label studio实现UIE信息抽取智能标注方案,提升标注效率!

汀丶人工智能

人工智能 自然语言处理 信息抽取 labelstudio 智能标注

多层嵌套 if-else 代码优化重构的几种方案

Java你猿哥

Java ssm Java工程师 if-else

集成技术,大山里的金子

阿呆

Flutter 异步编程指南

京东科技开发者

flutter dart 异步任务编程 APP开发 企业号 4 月 PK 榜

阿里资深架构师整理出来的一份Java核心知识点分享给大家.pdf

Java你猿哥

Java 面试 面经 八股文 java八股文呢

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