Moshi原理分析

基本使用

Kotlin 复制代码
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val xxx = moshi.adapter(XXXX::class.java).fromJson(jsonstring)
  • @Json(name="xxx") 设置别名
  • @Json(ignore = true) 忽略字段
  • @JsonClass(generateAdapter=true)编译生成JsonAdapter

初始化

Java 复制代码
Moshi(Builder builder) {
  List<JsonAdapter.Factory> factories =
      new ArrayList<>(builder.factories.size() + BUILT_IN_FACTORIES.size());
  factories.addAll(builder.factories);
  factories.addAll(BUILT_IN_FACTORIES);
  this.factories = Collections.unmodifiableList(factories);
  this.lastOffset = builder.lastOffset;
}

// 内置 Factory 初始化
static {
  BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
  BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(RecordJsonAdapter.FACTORY);
  BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
}
  1. 添加 Builder 中配置的 Factory
  2. 添加内置的 Factory

JsonClass 注解

  1. 编译期间为类型生成默认的 JsonAdapter
  2. 只能用于 Kotlin class,如果有父类也必须是 Kotlin class

JsonAdapter查找

Moshi.adapter(type),查找处理指定类型的 JsonAdapter

Java 复制代码
public <T> JsonAdapter<T> adapter(
    Type type, Set<? extends Annotation> annotations, @Nullable String fieldName) {

  // 缓存
  Object cacheKey = cacheKey(type, annotations);
  synchronized (adapterCache) {
    JsonAdapter<?> result = adapterCache.get(cacheKey);
    if (result != null) return (JsonAdapter<T>) result;
  }

  LookupChain lookupChain = lookupChainThreadLocal.get();
  if (lookupChain == null) {
    lookupChain = new LookupChain();
    lookupChainThreadLocal.set(lookupChain);
  }

  boolean success = false;
  JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
  try {
    // Lookup 没获取到,新的查询
    if (adapterFromCall != null) return adapterFromCall;

    // 遍历factory创建JsonAdapter
    for (int i = 0, size = factories.size(); i < size; i++) {
      JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
      if (result == null) continue;

      // 查找成功,修改 Lookup 的 adapter
      lookupChain.adapterFound(result);
      success = true;
      return result;
    }
  }
}

<T> JsonAdapter<T> push(Type type, @Nullable String fieldName, Object cacheKey) {
  for (int i = 0, size = callLookups.size(); i < size; i++) {
    Lookup<?> lookup = callLookups.get(i);
    if (lookup.cacheKey.equals(cacheKey)) {
      Lookup<T> hit = (Lookup<T>) lookup;
      stack.add(hit);
      // 存在查找,返回具体 JsonAdapter 或者代理 Lookup
      return hit.adapter != null ? hit.adapter : hit;
    }
  }

  // 首次查找,创建 Lookup 并保存
  Lookup<Object> lookup = new Lookup<>(type, fieldName, cacheKey);
  callLookups.add(lookup);
  stack.add(lookup);
  return null;
}
  1. 查找缓存;
  2. 获取 LookupChain;
  3. 调用 LookupChain.push 提交查询;
  4. 如果当前没有相同类型的查询则创建 LookUp 类型,保存并返回;
  5. LookUp相当于JsonAdapter的代理对象,本身继承了JsonAdapter,内部持有一个 JsonAdapter 成员,次成员是最终查找到的实际 Adapter,序列化及反序列化委托给次成员实现;
  6. 如果当前存在相同类型的查询,获取其对应的 Lookup 对象,如果其中的 adpter 不为空,返回代理的JsonAdapter,否则返回 Lookup 自身;
  7. 如果 LookUpChain.push 返回不为 null,表明查找到了,否则遍历初始化 Moshi 是配置的 factories 列表,调用其 create 方法并传递类型信息,如果 create 返回非空,表示查找到了;
  8. 通知 LookupChain 找到 adppter了,对 LookUp 设置 adapter;
  9. 总结:本身逻辑应该比较简单,缓存查不到直接通过 factory 创建就好了,但是这边加了一个 LookUp 的代理逻辑,这个主要是为了解决循环依赖的问题,比如有 A 和 B 两个类型,A中包含一个B类型的成员,B中包含一个A类型的成员,这时候查找A的JsonAdapter的过程中会去查找B的JsonAdapter,那在查找B的JsonAdapter同时又依赖了A的JsonAdapter,这样就循环依赖了。通过 LookUp 代理,在查找A的时候插入了一个LookUp 代理,没找到A走创建,创建过程中查找B,B也没有同样走创建,创建B的时候查找A返回了A的Lookup对象,这样B创建完成,返回继续A的创建,A创建完了把Lookup中的adapter替换。

反序列化Java对象

由内置的 ClassJsonAdapter 完成

ini 复制代码
public T fromJson(JsonReader reader) throws IOException {
  T result;
  try {
    result = classFactory.newInstance();
  }
  // 身略 catch

  try {
    reader.beginObject();
    while (reader.hasNext()) {
      int index = reader.selectName(options);
      if (index == -1) {
        reader.skipName();
        reader.skipValue();
        continue;
      }
      fieldsArray[index].read(reader, result);
    }
    reader.endObject();
    return result;
  } catch (IllegalAccessException e) {
    throw new AssertionError();
  }
}

内部通过 ClassFactory 创建出对象,在读取属性赋值

Java 复制代码
// 1、获取无参构造方法,反射创建对象
try {
  final Constructor<?> constructor = rawType.getDeclaredConstructor();
  constructor.setAccessible(true);
  return new ClassFactory<T>() {
    @Override
    public T newInstance()
        throws IllegalAccessException, InvocationTargetException, InstantiationException {
      Object[] args = null;
      return (T) constructor.newInstance(args);
    }

    @Override
    public String toString() {
      return rawType.getName();
    }
  };
} catch (NoSuchMethodException ignored) {
}

// 2、通过 Unsafe 的 allocateInstance 创建对象
try {
  Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
  Field f = unsafeClass.getDeclaredField("theUnsafe");
  f.setAccessible(true);
  final Object unsafe = f.get(null);
  final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
  return new ClassFactory<T>() {
    public T newInstance() throws InvocationTargetException, IllegalAccessException {
      return (T) allocateInstance.invoke(unsafe, rawType);
    }

    @Override
    public String toString() {
      return rawType.getName();
    }
  };
}
  1. 反射获取类的无参构造方法,通过无参构造方法创建对象;
  2. 如果没有无参数的构造方法,则反射获取 Unsafe 实例,调用其 allocateInstance 创建对象;
  3. 整体过Gson比较类似;

反序列化Kotlin对象

由 moshi-kotlin 模块的 KotlinJsonAdapter 完成,通常需要在初始化 Moshing 的时候手动添加 KotlinJsonAdapterFactory

尝试从@JsonClass注解生成的JsonAdapter中查找匹配的

  1. 判断是否含有 JsonClass 注解,并且设置了 generateAdapter = true;
  2. 根据类型名拼JsonAdapter字符串得到此类型生成的 JsonAdapter 类型名称,eg:UserInfoJsonAdapter;
  3. Class.formName加载对应的JsonAdapter,反射构造方法创建对象并返回;
Java 复制代码
public static @Nullable JsonAdapter<?> generatedAdapter(
    Moshi moshi, Type type, Class<?> rawType) {
  JsonClass jsonClass = rawType.getAnnotation(JsonClass.class);
  // 包含注解并且 generateAdapter 设置为 true
  if (jsonClass == null || !jsonClass.generateAdapter()) {
    return null;
  }
  // 生成类型的名称
  String adapterClassName = Types.generatedJsonAdapterName(rawType.getName());
  Class<? extends JsonAdapter<?>> adapterClass = null;
  try {
  // 反射获取类型
    adapterClass =
        (Class<? extends JsonAdapter<?>>)
            Class.forName(adapterClassName, true, rawType.getClassLoader());
    Constructor<? extends JsonAdapter<?>> constructor;
    Object[] args;
    if (type instanceof ParameterizedType) {
      Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
      try {
        // Common case first
        constructor = adapterClass.getDeclaredConstructor(Moshi.class, Type[].class);
        args = new Object[] {moshi, typeArgs};
      } catch (NoSuchMethodException e) {
        constructor = adapterClass.getDeclaredConstructor(Type[].class);
        args = new Object[] {typeArgs};
      }
    } else {
      try {
        // Common case first
        constructor = adapterClass.getDeclaredConstructor(Moshi.class);
        args = new Object[] {moshi};
      } catch (NoSuchMethodException e) {
        constructor = adapterClass.getDeclaredConstructor();
        args = new Object[0];
      }
    }
    constructor.setAccessible(true);
    return constructor.newInstance(args).nullSafe();
  }
  // catch 逻辑省略

如果没有对应的 JsonAdapter,则通过 Kotlin 的反射生成对象

  1. 获取 kotlin 主构造方法及其参数列表
  2. 遍历 class 的成员列表,查找每个成员的 JsonAdapter
  3. 记录每个成员的名称,对应的 JsonAdapter,是否是构造方法参数,index信息
  4. 根据收集的信息创建 KotlinJsonAdapter
  5. KotlinJsonAdapter 中读取 json 数据,根据字段是否有值,是否有默认值,是否可能等条件调用不同的构造方法,完成初始化,
  6. 存在默认参数的时候,kotlin内部会生成一个构造方法,在方法内部判断如果默认值参数没传递则使用默认值。
Java 复制代码
// KotlinJsonAdapter
override fun fromJson(reader: JsonReader): T {
  val constructorSize = constructor.parameters.size

  // Read each value into its slot in the array.
  val values = Array<Any?>(allBindings.size) { ABSENT_VALUE }
  reader.beginObject()
  while (reader.hasNext()) {
    val index = reader.selectName(options)
    if (index == -1) {
      reader.skipName()
      reader.skipValue()
      continue
    }
    val binding = nonIgnoredBindings[index]

    val propertyIndex = binding.propertyIndex
    if (values[propertyIndex] !== ABSENT_VALUE) {
      throw JsonDataException(
        "Multiple values for '${binding.property.name}' at ${reader.path}"
      )
    }
    // 保存从json中解析出来的属性值
    values[propertyIndex] = binding.adapter.fromJson(reader)

    reader.endObject()

  // Confirm all parameters are present, optional, or nullable.
  // Class 成员数量 == 构造方法参数数量
  var isFullInitialized = allBindings.size == constructorSize
  for (i in 0 until constructorSize) {
    // 构造函数参数成员没有从 json 中解析出值
    if (values[i] === ABSENT_VALUE) {
      when {
        // 可选的,存在默认值
        constructor.parameters[i].isOptional -> isFullInitialized = false
        // 可空的,直接设置为null
        constructor.parameters[i].type.isMarkedNullable -> values[i] = null
        // 异常
        else -> throw Util.missingProperty(
          constructor.parameters[i].name,
          allBindings[i]?.jsonName,
          reader
        )
      }
    }
  }

  val result = if (isFullInitialized) {
    // 调用默认构造方法
    constructor.call(*values)
  } else {
    // 调用生成的构造方法(带一个mask参数和一个mark参数,内部做了默认参数的赋值工作)
    constructor.callBy(IndexedParameterMap(constructor.parameters, values))
  }

  // Set remaining properties.
  for (i in constructorSize until allBindings.size) {
    val binding = allBindings[i]!!
    val value = values[i]
    binding.set(result, value)
  }

  return result
}

和Gson比较

  • Moshi 对 Kotlin 支持更好,data class、默认值、空安全
  • data class 没有默认的无参构造方法,反序列化的时候通过 Unsfae 的方式去创建的实例对象,导致默认值的初始化以及非空值的断言都没有执行
相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui