Android 序列化框架 Gson 原理分析,可以优化吗?
theme: jzman
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。
前言
大家好,我是小彭。
Gson 是 Google 推出的 Java Json 解析库,具有接入成本低、使用便捷、功能扩展性良好等优点,想必大家都很熟悉了。在这篇文章里,我们将讨论 Gson 的基本用法和以及主要流程的源码分析。
学习路线图:
1. Gson 的基本使用
Gradle 依赖
groovy
dependencies {
implementation 'com.google.code.gson:gson:2.10'
}
1.1 GsonBuilder 配置项
Gson 类是整个库的核心 API,在进行任何序列化或反序列化之前,我们都需要获得一个 Gson 对象。可以直接 new 创建默认配置的 Gson 对象,也可以使用 GsonBuilder 构造者配置 Gson 对象。
事实上,一个 Gson 对象代表一个 Gson 工作环境,不同 Gson 对象之间的配置和缓存都不会复用。 因此,在项目中有必要在 common 层提供一个全局的 Gson 对象,既有利于统一序列化配置,也是 Gson 性能优化的基本保障。
GsonBuilder 使用示例
groovy
Gson gson = new GsonBuilder()
// 设置自定义解析(不支持协变)
.registerTypeAdapter(Id.class, new IdTypeAdapter())
// 设置自定义解析(支持协变)
registerTypeHierarchyAdapter(List.class, new MyListTypeAdapter())
// 设置自定义解析(以工厂方式)
.registerTypeAdapterFactory(new IdTypeAdapterFactory())
// 设置日期格式
.setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
// 设置自动切换命名风格规则(默认不切换命名风格)
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
// 设置过滤指定字段标识符(默认只过滤 transient 和 static 字段)
.excludeFieldsWithModifiers(Modifier.TRANSIENT | Modifier.STATIC)
// 设置类或字段过滤规则
.setExclusionStrategies(new MyExclusionStrategy1())
// 设置过滤规则(只适用于序列化)
.addSerializationExclusionStrategy(new MyExclusionStrategy2())
// 设置过滤规则(只适用于反序列化)
.addDeserializationExclusionStrategy(new MyExclusionStrategy3())
// 设置序列化版本号
.setVersion(1.0)
// 启用非基础类型 Map Key
.enableComplexMapKeySerialization()
// 启用不过滤空值(默认会过滤空值)
.serializeNulls()
// 启用 Json 格式化
.setPrettyPrinting()
.create();
1.2 注解配置
Gson 没有编译时处理,所以注解均是运行时注解。
- @SerializedName 字段别名: 支持设置多个别名,
value
变量在序列化和反序列化时都有效,而alternate
变量只是在反序列化时做兼容而已; - @Expose 字段暴露:
- 默认情况下,一个类中所有字段都会暴露,但使用 @Expose 注解后,只有声明注解的字段才会暴露;
- 注解的
serialize
变量或deserialize
变量可以声明字段只参与序列化或反序列化,默认都参与。
- @JsonAdapter 注解: 声明在具体类或字段上,用于更细粒度地设置 TypeAdapter,优先级比 registerTypeAdapter 高;
- @Since 注解: 声明在具体类或字段上,声明字段的起始序列化版本;
- @Until 注解: 声明在具体类或字段上,声明字段的终止序列化版本。当字段的序列化版本满足 since ≥ GsonBuilder#setVersion 且 GsonBuilder#setVersion ≤ until 时,才会参与序列化;
1.3 *JsonSerializer 和 JsonDeserializer* 自定义解析
JsonSerializer
和 JsonDeserializer
是 Gson 1.x 版本提供的自定义解析 API,是基于树型结构的解析 API。在解析数据时,它们会将 Json 数据一次性解析为 JsonElement
树型结构。 JsonElement 代表 Json 树上的一个节点,有 4 种具体类型:
| JsonElement | 描述 | | --- | --- | | JsonObject | {} 对象 | | JsonArray | [] 数组 | | JsonPrimitive | 基本类型 | | JsonNull | null 值 |
1.4 TypeAdapter 自定义解析
TypeAdapter
是 Gson 2.0 新增的自定义解析 API,是基于流式结构的 API。事实上, JsonSerializer 和 JsonDeserializer 最终也会被构造为 TreeTypeAdapter
;
相较之下,JsonSerializer & JsonDeserializer 相对方便,但更费内存。而 TypeAdapter 更节省内存,但不方便。不过,如果需要用到完整数据结构(例如根据 type 字段按照不同类型解析 data),也可以手动解析为树型结构。因此 TypeAdapter 这个 API 的优先级更高。
| | TypeAdapter | JsonSerializer、JsonDeserializer | | --- | --- | --- | | 引入版本 | 2.0 | 1.x | | Stream API | 支持 | 不支持 | | Tree API | 支持,可以手动转换 | 支持 | | 内存占用 | 小 | 比 TypeAdapter 大 | | 效率 | 高 | 比 TypeAdapter 低 | | 作用范围 | 序列化 + 反序列化 | 序列化 / 反序列化 |
1.5 registerTypeAdapter 和 registerTypeHierarchyAdapter 的区别
- registerTypeAdapter 是不变型的: 只会对注册的类型生效。例如注册
<List.class,TypeAdapter>
,则只会影响List
类型的字段,但不会影响 ArrayList 类型的字段; - registerTypeHierarchyAdapter 是协变型的: 会对注册的类型及其子类生效。例如注册
<List.class,TypeAdapter>
,则只会影响List
、ArrayList
类型的字段;
| | registerTypeAdapter | registerTypeHierarchyAdapter | | --- | --- | --- | | 支持泛型 | 是 | 否 | | 支持继承 | 否 | 是 |
2. Gson 源码分析
这一节,我们来分析 Gson 核心流程的工作原理和源码。
2.1 说一下 Gson 解析的工作过程
“TypeAdapter” 是 Gson 解析的重要角色,Gson 每次解析一种对象类型,首先需要创建一个 TypeAdapter 对象,之后所有的解析工作都会交给其中的 TypeAdapter#write 和 TypeAdapter#read 方法;
Java Bean 类型的 TypeAdapter 对象是交给 “ReflectiveTypeAdapterFactory” 创建的。每创建一种类型的 TypeAdapter,都需要递归地使用 “反射” 遍历所有字段,并解析字段上的注解,生成一个 <serializeName - BoundFiled>
的映射表。
- 在序列化时,首先使用反射获取字段值,再使用字段的
BoundFiled
序列化; - 在反序列化时,首先创建对象实例(下文会讨论如何创建),再使用依次使用字段的
BoundField
反序列为字段类型的值,再通过反射为字段赋值。
由于字段值的写入和读取是通过 Field
元数据反射操作的,所以 private 字段也可以操作。
在构造 Gson 对象时,已经初始化了一系列 TypeAdapter 创建工厂,开发者可以注册自定义的 TypeAdapter:
Gson.java
```java
Gson(final Excluder excluder, ...) {
List
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// 过滤规则
factories.add(excluder);
// 自定义 TypeAdapter
factories.addAll(factoriesToBeAdded);
// 1. 基础类型
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
...
// 2. 复合类型
// 2.1 列表类型
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
// 2.2 集合类型
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
// 2.3 枚举类型
factories.add(TypeAdapters.ENUM_FACTORY);
// 2.4 Java Bean 类型
factories.add(new ReflectiveTypeAdapterFactory(constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
} ```
通过 Gson#getAdapter 查找匹配 TypeAdapter 的方法:
Gson.java
```java
// TypeAdapter 缓存映射表
public
使用 ReflectiveTypeAdapterFactory 工厂为每种类型创建 TypeAdapter 对象:
ReflectiveTypeAdapterFactory.java
```java
// 1. 创建 TypeAdapter 对象
@Override
public
public static abstract class Adapter
// 2. 反序列化过程
@Override
public T read(JsonReader in) {
// 2.1 创建对象
T instance = constructor.construct();
// 2.2 消费 {
in.beginObject();
// 2.3 递归反序列化每个字段
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
// 读取流并设置到 instance 对象中
readIntoField(in, instance);
}
}
// 2.4 消费 }
in.endObject();
return instance;
}
// 3. 序列化过程
@Override
public void write(JsonWriter out, T value) {
// 3.1 写入 {
out.beginObject();
// 3.2 递归序列化每个字段
for (BoundField boundField : boundFields.values()) {
// 将对象的每个字段写入流中
boundField.write(out, value);
}
// 3.3 写入 }
out.endObject();
}
}
// -> 1.2 getBoundFields:解析每个字段的适配器
private Map
// -> 1.2.2.3 为所有字段别名创建 BoundField 对象 private ReflectiveTypeAdapterFactory.BoundField createBoundField( final Gson context, final Field field, final String name, final TypeToken<?> fieldType, boolean serialize, boolean deserialize) { // 基本类型 final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); // @JsonAdapter 注解 JsonAdapter annotation = field.getAnnotation(JsonAdapter.class); TypeAdapter<?> mapped = null; if (annotation != null) { mapped = jsonAdapterFactory.getTypeAdapter(constructorConstructor, context, fieldType, annotation); } final boolean jsonAdapterPresent = mapped != null; if (mapped == null) mapped = context.getAdapter(fieldType); final TypeAdapter<?> typeAdapter = mapped; return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
@Override
void write(JsonWriter writer, Object value) {
if (!serialized) return;
// 通过反射读取字段值
Object fieldValue = field.get(value);
TypeAdapter t = jsonAdapterPresent ? typeAdapter : new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
// 写出到流
t.write(writer, fieldValue);
}
@Override
void readIntoField(JsonReader reader, Object target) {
// 从流读取
Object fieldValue = typeAdapter.read(reader);
// 通过反射写入字段值
field.set(target, fieldValue);
}
};
} ```
2.2 List & Set & Map 等容器类型是如何解析的?
- 1、在预置的容器 TypAdapter 中,会先通过容器类型的
RawType
获取容器构造器,再根据泛型实参elementType
获取元素类型的 TypeAdapter; - 2、在序列化时,先写入 [ 左中括号,再用元素类型的 TypeAdapter 依次序列化元素对象,再写入 ] 右中括号;
- 3、在反序列化时,先创建集合对象,再用元素类型的 TypeAdapter 依次反序列化元素对象;
- 4、Map 类型需要维护 Key 和 Value 两个 TypeAdapter。
CollectionTypeAdapterFactory.java
```java
// 1. 创建 TypeAdapter
public
private static final class Adapter
// 3. 序列化过程
@Override
public void write(JsonWriter out, Collection<E> collection) {
// 3.1 写入 [
out.beginArray();
// 3.2 使用 1.2 步骤的 TypeAdapter 序列化每个元素
for (E element : collection) {
elementTypeAdapter.write(out, element);
}
// 3.3 写入 ]
out.endArray();
}
} ```
2.3 枚举类型是如何解析的?
- 1、在预置的 EnumTypeAdapter 适配器中,会先获取枚举类型的整个枚举列表,并生成 2 个映射表。
=""> 映射表 - <枚举 - name> 映射表
- 2、在序列化时,会写入枚举的 name。在反序列化时,会根据 name 查询枚举对象。
TypeAdapters.java
```java
private static final class EnumTypeAdapter
public EnumTypeAdapter(Class<T> classOfT) {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name()
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
}
@Override
public T read(JsonReader in) {
return nameToConstant.get(in.nextString());
}
@Override
public void write(JsonWriter out, T value) {
out.value(constantToName.get(value));
}
} ```
2.4 同类型嵌套会不会无限递归?
ReflectiveTypeAdapterFactory
在创建对象的 TypeAdapter 适配器时,需要递归的创建每个字段的 TypeAdapter。如果字段的类型正好与类的类型相同,那么又会触发创建一个相同的 TypeAdapter,造成无限递归。例如:
无限递归的例子
java
public class Article {
public Article linkedArticle;
}
分析发现,无限递归只发生在第一次创建某个 Java Bean 类型的 TypeAdapter 时,而下一次会从缓存获取,不会发生无限递归。因此,Gson 的做法是:
- 1、在每次新创建 TypeAdapter 前,先在临时映射表中创建一个
FutureTypeAdapter
代理对象。在创建真实的 TypeAdapter 后,将其注入到代理对象中。这样在递归获取字段的 TypeAdapter 时,就会拿到代理对象,而不是重新创建 TypeAdapter,因此解决递归问题; - 2、另外,考虑到多线程环境下,临时映射表的新增和移除会有并发问题,因此 Gson 的策略是使用
ThreadLocal
隔离各个线程的临时映射表。
Gson.java
```java // 线程隔离的映射表 private final ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls = new ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>>(); // 线程共享的映射表(基于 ConrurrentHashMap) private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();
public
// 2. 初始化当前线程的临时映射表
Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
calls.set(threadCalls);
requiresThreadLocalCleanup = true;
}
// 3. 尝试从临时映射表获取(递归调用时,会从这里获取到代理 TypeAdapter,而不会走到下面的 factory.create
FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
try {
// 4.1 创建代理 TypeAdapter
FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
threadCalls.put(type, call);
for (TypeAdapterFactory factory : factories) {
// 4.2 创建 TypeAdapter
TypeAdapter<T> candidate = factory.create(this, type);
// 4.3 将真实的 TypeAdapter 注入到代理 TypeAdapter 中
if (candidate != null) {
call.setDelegate(candidate);
// 4.4 将 TypeAdapter 写入缓存
typeTokenCache.put(type, candidate);
return candidate;
}
}
} finally {
// 5. 清除临时映射表
threadCalls.remove(type);
if (requiresThreadLocalCleanup) {
calls.remove();
}
}
} ```
2.5 Gson 是如何创建对象的?
- 1、基础类型:Integer、Calendar 等基础类型由固定的 TypeAdapter,会通过 new 关键字创建对象;
- 2、枚举:枚举的序列化和反序列化只是在枚举名 name 和枚举对象之间切换,不会创建新的枚举对象;
- 3、List & Set & Map:容器类型会通过预置的对象创建工厂,调用 new 关键字创建对象;
- 4、Java Bean:Java Bean 的创建分为多种可能:
- 情况 1:自定义了对象创建工厂
InstanceCreator
,则优先通过自定义工厂创建; - 情况 2:存在默认的无参构造函数,则通过反射构造函数创建;
- 情况 3:使用 Unsafe API 兜底创建对象。
- 情况 1:自定义了对象创建工厂
ConstructorConstructor.java
```java
public
// InstanceCreator API
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
if (typeCreator != null) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return typeCreator.createInstance(type);
}
};
}
final InstanceCreator<T> rawTypeCreator = (InstanceCreator<T>) instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor<T>() {
@Override
public T construct() {
return rawTypeCreator.createInstance(type);
}
};
}
// 无参构造函数
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
}
// 容器类型
ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
if (defaultImplementation != null) {
return defaultImplementation;
}
// Unsafe API
return newUnsafeAllocator(type, rawType);
} ```
2.6 Gson 隐藏的坑
当 Class 未提供默认的无参构造函数时,Gson 会使用 Unsafe API 兜底来创建对象。Unsafe API 主要提供一些用于执行低级别、不安全操作的方法,也提供了一个非常规实例化对象的 allocateInstance
方法。
这个 API 不会调用构造函数
- 1、构造函数参数的默认值丢失;
- 2、字段的默认值丢失;
- 3、Kotlin 非空类型失效;
- 4、初始化块未执行;
- 5、by 属性代理(没有创建代理对象)
3. Gson 如何解析泛型类型?
由于 Java 有泛型擦除,无法直接在 .class
语法上声明泛型信息,Gson 的方法是要求程序员创建匿名内部类,由 Gson 在运行时通过反射获取类声明上的泛型信息。
示例代码
java
// 非法:
Response<User> obj = Gson().fromJson<Response<User>>(jsonStr, Response<User>.class)
// 合法;
TypeToken token = object : TypeToken<Response<User>>() {}
Response<User> obj = Gson().fromJson<Response<User>>(jsonStr, token.type)
为什么反序列化泛型类要使用匿名内部类呢?
原理是 Class 文件中的 Signature
属性会保持类签名信息,而 TypeToken
只是一个工具类,内部通过反射获取类签名中泛型信息并返回 Type
类型。
TypeToken.java
```java protected TypeToken() { this.type = getSuperclassTypeParameter(getClass()); this.rawType = (Class<? super T>) $Gson$Types.getRawType(type); this.hashCode = type.hashCode(); }
// 返回 Response
public final Type getType() { return type; } ```
既然 TypeToken 只是一个获取 Type 类型的工具类,我们也可以跳过它直接提供 Type,方法是定义 ParameterizedType
参数化类型的子类:
ParameterizedTypeAdapter.java
```java private static class ParameterizedTypeAdapter implements ParameterizedType {
private final Class<?> rawType;
private final Type[] types;
private ParameterizedTypeAdapter(Class<?> rawType, Type... types) {
this.rawType = rawType;
this.types = types;
}
@Override
public Type[] getActualTypeArguments() {
return types;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return null;
}
} ```
示例代码
java
Response<User> obj = new Gson().fromJson<Response<User>>(jsonStr, ParameterizedTypeAdapter(Response.class, User.class))
在 Kotlin 中,还可以使用 reified 实化类型参数简化:
Utils.kt
```kotlin
inline fun
inline fun
示例代码
kotlin
List<User> obj = toList<User>(jsonStr)
4. 总结
今天,我们讨论了 Gson 的基本用法和以及主要流程的源码分析。
在 Gson 的反序列化中,首次反序列化一个类型的对象时,Gson 需要使用大量反射调用解析一个 TypeAdapter 适配器对象。随着 Model 的复杂程度增加,首次解析的耗时会不断膨胀。
这个问题在抖音的技术博客中提到一个解决方案,这个问题我们在下篇文章讨论,请关注。
版权声明
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
参考资料
- Java Google Json (Gson) Introduction —— Mithil Shah 著
- Gson — Getting Started with Java-JSON —— Norman Peitek 著
- Javadoc · Gson —— Gson 官方文档
- Gson 源码解析和它的设计模式 —— 拉丁吴 著
- 还在被数据类的序列化折磨?是时候丢弃 Gson 了—— bennyhuo 著
- 抖音 Android 性能优化系列:启动优化实践(反序列化优化) —— 字节跳动技术团队 著
- JSON —— Wikipedia
- LeetCode 周赛 336,多少人直接 CV?
- LeetCode 周赛 335,纯纯手速场!
- LeetCode 双周赛 98,脑筋急转弯转不过来!
- Android IO 框架 Okio 的实现原理,到底哪里 OK?
- 12 张图看懂 CPU 缓存一致性与 MESI 协议,真的一致吗?
- Android 序列化框架 Gson 原理分析,可以优化吗?
- 为什么计算机中的负数要用补码表示?
- 什么是二叉树?
- 我把 CPU 三级缓存的秘密,藏在这 8 张图里
- 全网最全的 ThreadLocal 原理详细解析 —— 原理篇
- 程序员学习 CPU 有什么用?
- WeakHashMap 和 HashMap 的区别是什么,何时使用?
- 万字 HashMap 详解,基础(优雅)永不过时 —— 原理篇
- Java 面试题:说一下 ArrayDeque 和 LinkedList 的区别?
- Java 面试题:说一下 ArrayList 和 LinkedList 的区别?
- Java 面试题:ArrayList 可以完全替代数组吗?
- 已经有 MESI 协议,为什么还需要 volatile 关键字?
- JVM 系列(6)吊打面试官:为什么 finalize() 方法只会执行一次?
- 使用前缀和数组解决"区间和查询"问题
- NDK 系列(5):JNI 从入门到实践,万字爆肝详解!