QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

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

评论

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

SpringBoot自动配置原理详解

做梦都在改BUG

Java Spring Boot

SpringBoot 项目解决跨域的几种方案

做梦都在改BUG

Java Spring Boot

OceanBase 4.0(小鱼)入选2023数字中国建设峰会“十大硬核科技”!

OceanBase 数据库

数据库 oceanbase

153个!PCB板上的字母符号都代表啥?一图带你搞懂!

华秋PCB

物理 电路 元器件 PCB PCB设计

Spring Security 中的基本认证过滤器链

Java架构历程

Java spring security 三周年连更

病假单|病假条|体检报告|诊断证明书|病历证明|医院化验单|ct报告|b超单|怀孕检查

病假条病假单

硬核!阿里自爆虐心万字面试手册,Github上获赞89.7K

做梦都在改BUG

Java 程序员

NFTScan 推出「nftonchain」Telegram channel,实时推送链上 NFT 热点数据

NFT Research

NFT 智能推送 #Web3

京东物流常态化压测实践 | 京东云技术团队

京东科技开发者

测试 压测 常态化压测 企业号 5 月 PK 榜

从0到100:小区物业报修小程序开发笔记

CC同学

第四范式开源强化学习研究通用框架,支持单智能体、多智能体训练,还可训练自然语言任务!训练速度提升17%

Geek_32eb82

八股MQ001——为什么需要使用MQ?

Codyida

后端

八股MQ005——聊聊Broker

Codyida

后端

八股MQ002——说说Rebalance?

Codyida

后端

干货满满的技术盛宴!OpenHarmony开发者大会技术分论坛成功举办

最新动态

面试官:如何保证 RabbitMQ 的消息可靠性

做梦都在改BUG

Java 面试 RabbitMQ 消息队列 消息中间件

八股MQ004——聊聊Producer

Codyida

后端

小红书如何应对万亿级社交网络关系挑战?图存储系统 REDtao 来了!

小红书技术REDtech

云原生 存储 图数据库 跨云多活

openEuler之上的K3s ARM64集群管理

openEuler

Linux 云原生 k8s AWS Kubernetes Serverless

升级企业数智化底座 用友iuap拉满长期主义

用友BIP

SAPUI5 本地工程中的键值对 sapux - true 的作用

汪子熙

前端开发 SAP Fiori SAP UI5 三周年连更

太强了!阿里人用138个案例讲明白了Spring全家桶+Docker+MQ

做梦都在改BUG

Java spring 微服务 Spring Cloud Spring Boot

全球首个开发者村“开村”!数字之光在何处点亮?

白洞计划

叹服!阿里自述SpringCloud微服务:入门+实战+案例

做梦都在改BUG

Java 架构 微服务 Spring Cloud

膜拜!阿里人用10W字面经把Java面试官拿下了

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

简洁好用的思维导图软件:simplemind 中文版

真大的脸盆

Mac 思维导图 Mac 软件 思维导图软件

Netty服务端开发及性能优化 | 京东云技术团队

京东科技开发者

Netty 高性能 netty内存管理 企业号 5 月 PK 榜

chatGPT是割韭菜的镰刀还是创业的新风口? | 社区征文

迷彩

AIGC 生成式人工智能 三周年征文 三周年连更

GPIO实验-主芯片GPIO输出实验

鸿蒙之旅

OpenHarmony 三周年连更

面试被Spring Cloud拿捏?莫慌,阿里人用五个模块讲明白了SpringCloud微服务架构

做梦都在改BUG

Java 架构 微服务 Spring Cloud

即时通讯技术文集(第14期):WebSocket精华文章合集 [共15篇]

JackJiang

网络编程 即时通讯 IM

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