写点什么

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

评论

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

编写Spring MVC控制器的技巧

编程江湖

Spring MVC

Java开发之测试框架知识分享

@零度

Java

架构营模块八作业

GTiger

架构实战营

大数据开发之Hive SQL的优化分享

@零度

大数据 Hive SQL

Flink类型系统的根及相关接口

编程江湖

flink

科技驱动经济发展的时代全面到来

CECBC

「自我检验」熬夜总结50个Vue知识点,全都会你就是神!!!

Sunshine_Lin

面试 Vue 前端 进阶 ES6

一文了解区块链如何帮助打击虚假信息

CECBC

【Golang】浅谈协程并发竞争资源问题

恒生LIGHT云社区

golang 后端 协程 并发 Go 语言

什么是Log4Shell?Log4j漏洞解读

龙智—DevSecOps解决方案

log4j Log4j 2 Log4Shell

一文整理区块链技术为企业带来的九大好处

CECBC

书单 | 学习数据可视化?看这些书就够了!

博文视点Broadview

作业5

施正威

数据库批量插入这么讲究的么?

秦怀杂货店

Java 数据库 批量插入

10个问题让你快速避开java中的jdbc常见坑

华为云开发者联盟

Java 数据库 JDBC fetchSize Prepared Statement

Kafka原理——Kafka为何如此之快?

Kafka中文社区

前端开发Vue中的v-指令的使用

@零度

Vue 前端开发

测试阻碍交付,如何破解这一难题?

飞算JavaAI开发助手

潘娟:Keep open,Stay tuned 开源为我打开的全新世界 | TiDB Hackathon 2021 评委访谈

PingCAP

nodejs 异步I/O和事件驱动

编程江湖

nodejs

EMQ 映云科技入围 Venture50 行业榜单,数字科技企业风向标!

EMQ映云科技

物联网 Venture50

ReactNative进阶(一):ReactNative 学习资料汇总

No Silver Bullet

React Native 1月月更

Hive on Spark和Spark sql on Hive,你能分的清楚么

华为云开发者联盟

sql 分布式计算 Sparksql hive on spark 数据源

作业4

施正威

网络安全好学吗?基础入门篇,NMAP高级使用技巧和漏洞扫描发现

学神来啦

网络安全 渗透测试 kali基础 nmap kali Linux

面试官:为什么不同返回类型不算方法重载?

王磊

netty系列之:选byte还是选message?这是一个问题

程序那些事

Java Netty 程序那些事 UDT 1月月更

【量化】量化交易入门系列3:经典的量化交易策略(中)

恒生LIGHT云社区

量化投资 量化交易 量化

今天你的静态变量和静态代码块执行了吗?

华为云开发者联盟

Java 类加载 静态 静态变量 静态代码块

带你认识7种云化测试武器

华为云开发者联盟

测试 接口测试 华为云DevCloud 云化测试 Mock 服务

SphereEx 完成近千万美元 Pre-A 轮融资,加速构建新一代数据库生态引擎

SphereEx

开源 融资 ShardingSphere SphereEx 嘉御资本

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