Retrofit之Converter解析

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

前言

Retrofit源码系列文章:

在前几篇文章里对Retrofit的源码以及CallAdapter进行了详细的分析,本文将继续扒一扒Converter。

Converter

先来看看网络请求接口的常规写法:

interface TestApi {
    @POST("/.../...")
    fun requestUserInfo(@Body request: RequestUserInfoBean): Call<UserInfo>
}
复制代码

其中RequestUserInfoBean和UserInfo是自定义的数据类,熟悉Retrofit源码的朋友应该知道API方法的返回类型是由CallAdapter负责适配的,在完成网络请求之后将方法定义的数据类型UserInfo返回来,但是Retrofit又是怎么认识自定义的RequestUserInfoBean和UserInfo呢?这就是数据转换器Converter的工作了,负责对请求和服务器返回的数据进行解析成我们定义的类型。

下面是Converter接口的源码:

public interface Converter<F, T> {
  @Nullable T convert(F value) throws IOException;

  abstract class Factory {
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
        Annotation[] annotations, Retrofit retrofit) {
      return null;
    }

    public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}
复制代码

接口的代码很简单,只有convert方法和一个工厂类,Converter<F,T>中的泛型F是原始数据,泛型T是解析后需要返回的数据类型,这个类型转换的工作由convert方法来做,工厂类里定义了五个方法:

  • responseBodyConverter方法负责将OkHttp的ResponseBody型的响应数据转换成需要的结构类型
  • requestBodyConverter方法负责将请求数据转换成OkHttp的RequestBody
  • stringConverter方法负责将API方法中被注解@Field,@FieldMap,@Header,@HeaderMap,@Path,@Query,@QueryMap标记的入参转换成String型
  • getParameterUpperBound方法用于获取第index个参数的最上层的类
  • getRawType方法用于获取原始类型

从接口代码里可以看出来,作为数据转换器Converter能够对请求数据和响应数据进行转换,具体的转换逻辑是由Converter.Factory的实现类去完成的。Retrofit2.8.0提供了以下的ConverterFactory:

image.png

在初始化Retrofit的时候会默认添加BuiltInConverters和平台的默认工厂(即OptionalConverterFactory):

List<Converter.Factory> converterFactories = new ArrayList<>(
          1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());

converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
复制代码

接下来就看看BuiltInConverters是怎么实现数据转换的。

BuiltInConverters

final class BuiltInConverters extends Converter.Factory {
  private boolean checkForKotlinUnit = true;

  @Override public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
      Type type, Annotation[] annotations, Retrofit retrofit) {
    if (type == ResponseBody.class) {
      return Utils.isAnnotationPresent(annotations, Streaming.class)
          ? StreamingResponseBodyConverter.INSTANCE
          : BufferingResponseBodyConverter.INSTANCE;
    }
    if (type == Void.class) {
      return VoidResponseBodyConverter.INSTANCE;
    }
    if (checkForKotlinUnit) {
      try {
        if (type == Unit.class) {
          return UnitResponseBodyConverter.INSTANCE;
        }
      } catch (NoClassDefFoundError ignored) {
        checkForKotlinUnit = false;
      }
    }
    return null;
  }

  @Override public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    if (RequestBody.class.isAssignableFrom(Utils.getRawType(type))) {
      return RequestBodyConverter.INSTANCE;
    }
    return null;
  }

  ......
}
复制代码

在responseBodyConverter方法中可以看到,只有返回类型为OkHttp的ResponseBody、Void、Kotlin的Unit中之一的接口方法才会用到BuiltInConverters,对返回类型进行检测后返回对应的Converter。

  • 当返回类型是ResponseBody的时候会对方法标记的注解进行检测,如果用了@Streaming则使用StreamingResponseBodyConverter,直接返回ResponseBody,否则使用BufferingResponseBodyConverter,把数据读取到内存最后关闭数据流,这样处理是为了避免下载大文件的时候数据一次性读取到内存而引起OOM的问题:
//StreamingResponseBodyConverter的convert方法
@Override public ResponseBody convert(ResponseBody value) {
  return value;
}

//BufferingResponseBodyConverter的convert方法
@Override public ResponseBody convert(ResponseBody value) throws IOException {
  try {
    return Utils.buffer(value);
  } finally {
    value.close();
  }
}
复制代码
  • 对于Void和Kotlin的Unit返回类型的方法,对应的Converter主要是做了关闭数据流的处理,最后处理Void返回类型的Converter返回null,处理Kotlin的Unit返回类型的Converter返回一个UnitResponseBodyConverter实例:
//VoidResponseBodyConverter的convert方法
@Override public Void convert(ResponseBody value) {
  value.close();
  return null;
}

//UnitResponseBodyConverter的convert方法
@Override public Unit convert(ResponseBody value) {
  value.close();
  return Unit.INSTANCE;
}
复制代码

requestBodyConverter方法逻辑更加简单,只能对请求参数为RequestBody型的接口方法做处理,并且是将RequestBody型的数据通过RequestBodyConverter直接返回去,实际上并没有对数据做任何转换的工作:

//RequestBodyConverter的convert方法
@Override public RequestBody convert(RequestBody value) {
  return value;
}
复制代码

BuiltInConverters里还定义了一个用于把数据转换成字符串型的ToStringConverter:

static final class ToStringConverter implements Converter<Object, String> {
    static final ToStringConverter INSTANCE = new ToStringConverter();

    @Override public String convert(Object value) {
      return value.toString();
    }
  }
复制代码

这个ToStringConverter只在Retrofit的stringConverter方法中被调用到,当在配置的所有Converter中都找不到合适的字符串转换器的时候就会返回ToStringConverter,用于对API方法中标记了特定注解的请求参数转换成String:

//Retrofit类的stringConverter方法
public <T> Converter<T, String> stringConverter(Type type, Annotation[] annotations) {
    Objects.requireNonNull(type, "type == null");
    Objects.requireNonNull(annotations, "annotations == null");

    for (int i = 0, count = converterFactories.size(); i < count; i++) {
      Converter<?, String> converter =
          converterFactories.get(i).stringConverter(type, annotations, this);
      if (converter != null) {
        return (Converter<T, String>) converter;
      }
    }

    return (Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE;
  }
复制代码

看到这块的时候有个疑惑:在Converter.Factory类中定义的stringConverter方法跟BuiltInConverters中的ToStringConverter作用基本上一样,都是用于将标记了@Path等注解的请求参数转换成String,不同的是工厂类中的stringConverter方法内部是没有对入参的依赖的,因此实际上工厂类中的stringConverter方法并不会被调用到,那么BuiltInConverters为什么不重写父类Converter.Factory的stringConverter方法而选择另外定义具有相同功能的ToStringConverter呢?如果不打算用stringConverter方法又为什么要在工厂类里定义呢?

OptionalConverterFactory

@IgnoreJRERequirement
final class OptionalConverterFactory extends Converter.Factory {
  static final Converter.Factory INSTANCE = new OptionalConverterFactory();

  @Override public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
      Type type, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(type) != Optional.class) {
      return null;
    }

    Type innerType = getParameterUpperBound(0, (ParameterizedType) type);
    Converter<ResponseBody, Object> delegate =
        retrofit.responseBodyConverter(innerType, annotations);
    return new OptionalConverter<>(delegate);
  }
  ......
}
复制代码

OptionalConverterFactory中只重写了父类的responseBodyConverter方法,只对返回类型为Optional的接口方法进行处理,获取方法的返回类型Optional包着的类型innerType,并调用Retrofit类的responseBodyConverter方法根据根据innerType检索出合适的Converter,responseBodyConverter方法会返回一个nextResponseBodyConverter:

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
    Objects.requireNonNull(type, "type == null");
    Objects.requireNonNull(annotations, "annotations == null");

    int start = converterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter<ResponseBody, ?> converter =
          converterFactories.get(i).responseBodyConverter(type, annotations, this);
      if (converter != null) {
        return (Converter<ResponseBody, T>) converter;
      }
    }

    ......//省略无关代码
  }
复制代码

检索出合适的Converter之后将其封装成OptionalConverter对象返回,而实际上在OptionalConverter的convert方法中是通过其他的Converter去转换类型的,也就是说OptionalConverterFactory本身并不能对数据进行类型转换,仅仅是拆下Optional的包装拿到里面的类型然后依赖其他的Converter去解析数据:

static final class OptionalConverter<T> implements Converter<ResponseBody, Optional<T>> {
    final Converter<ResponseBody, T> delegate;

    OptionalConverter(Converter<ResponseBody, T> delegate) {
      this.delegate = delegate;
    }

    @Override public Optional<T> convert(ResponseBody value) throws IOException {
      return Optional.ofNullable(delegate.convert(value));
    }
  }
复制代码

分析到这里可以知道如果不额外添加其他的ConverterFactory,那么API方法的入参只能是RequestBody类型,返回类型则只能是ResponseBody、Void或者Unit,在这种情况下Retrofit是不认识前面接口中的自定义Data类RequestUserInfoBean和UserInfo的,也就是在实际使用中Retrofit默认添加的两个ConverterFactory基本上不能满足实际需求。在实际项目里要想Retrofit能正常工作往往需要另外添加数据转换器,下面以比较常用的GsonConverterFactory为例进行分析。

GsonConverterFactory

public final class GsonConverterFactory extends Converter.Factory {
  public static GsonConverterFactory create() {
    return create(new Gson());
  }

  @SuppressWarnings("ConstantConditions")
  public static GsonConverterFactory create(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    return new GsonConverterFactory(gson);
  }

  private final Gson gson;
  private GsonConverterFactory(Gson gson) {
    this.gson = gson;
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
}
复制代码

在给Retrofit配置GsonConverterFactory的时候会调用其create方法构建出一个Gson对象,然后分别根据方法的请求参数类型和返回类型生成对应的Gson数据转换器,构建出GsonResponseBodyConverter和GsonRequestBodyConverter。可以看到GsonConverterFactory内部并没有对转换类型进行检测,也就是说无论API方法的请求参数和返回类型如何定义,GsonConverterFactory都是可以处理的,来者不拒,下面分别看看请求体Converter和响应体Converter转换数据的具体逻辑:

//GsonResponseBodyConverter的convert方法
@Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      T result = adapter.read(jsonReader);
      if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
        throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
    } finally {
      value.close();
    }
  }

//GsonRequestBodyConverter的convert方法
@Override public RequestBody convert(T value) throws IOException {
    Buffer buffer = new Buffer();
    Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
    JsonWriter jsonWriter = gson.newJsonWriter(writer);
    adapter.write(jsonWriter, value);
    jsonWriter.close();
    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
  }
复制代码
  • 在GsonResponseBodyConverter的convert方法中先是读取数据流构建一个JsonReader对象,然后通过Gson数据转换器将原始返回的ResponseBody型数据转换成所需类型并返回。
  • 在GsonRequestBodyConverter的convert方法中将请求参数的原始类型通过Gson数据转换器转换成一个JsonWriter对象并写入缓存,最终返回RequestBody型的数据。

Converter何时起作用

从Converter接口的代码开始追踪,可以发现requestBodyConverter和responseBodyConverter方法是在Retrofit类中被调用到,用于根据类型在Retrofit持有的数据转换器工厂集合里去检索Converter。在创建API方法的ServiceMethod对象时,会调用RequestFactory.parseAnnotations去解析方法的注解:

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    ......//省略对方法返回类型的校验

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }
复制代码

这时requestBodyConverter则在RequestFactory中被用于对API方法根据注解和请求参数类型解析出RequestBody,然后在HttpServiceMethod.parseAnnotations中responseConverter会根据从CallAdapter拿到的方法返回类型responseType构建出合适的Converter对象:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
    Type responseType = callAdapter.responseType();
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    ......//省略无关代码
  }
复制代码

在发起请求之后,OkHttpCall的parseResponse方法会对服务器返回的数据进行解析,此时会调用到responseConverter的convert方法将响应数据转换成所需的类型并返回:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ......
    try {
      T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      ......
    }
  }
复制代码

下面是Converter的简单调用流程:

sequenceDiagram
调用者->>Retrofit:发起请求
Retrofit->>ServiceMethod:构建
ServiceMethod->>Converter:请求参数类型
Converter->>OkHttpCall:RequestBody
OkHttpCall->>Converter:ResponseBody
Converter->>ServiceMethod:返回类型
ServiceMethod->>Retrofit:返回结果
Retrofit->>调用者:返回结果

PS:本文仅代表个人见解,如有理解偏差的地方欢迎各位朋友指出

分类:
Android
标签: