Retrofit之Converter详解

QuibblerQuibbler 2021-04-22 约 25 分钟 2170 次阅读

Retrofit之Converter详解

Retrofit具有很高的扩展性,得益于预留的丰富接口,开发者可以根据业务自行实现接口。以Retrofit中的结果转换器Converter为例,了解其实现流程,最后根据业务自定义一个Converter。

1、Converter

Converter接口中只定义了一个T convert(F value)接口方法,以及一个创建Converter实例的抽象工厂。Retrofit根据返回类型Type从各种Converter.Factory中获取对应的转换器。

public interface Converter<F, T> {
  @Nullable
  T convert(F value) throws IOException;
  
  /** Creates {@link Converter} instances based on a type and target usage. */
  abstract class Factory {...}
  	  
}

Converter接口中的两个泛型参数<F,T>,含义分别为from和to。也就是从HTTP请求“来”的结果ResponseBody,以及需要“去”转换成的类型T(可以是自定义的Json Bean类,还可以是原始的ResponseBody)。

1.1、Converter.Factory初始化

Retrofit中通过Converter.Factory获取Converter实例。先来看看这些工厂是如何初始化的。

首先,在构造Retrofit实例的时候,通过建造者的addConverterFactory(Converter.Factory factory)方法添加自定义的Converter.Factory:

/** Add converter factory for serialization and deserialization of objects. */
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(Objects.requireNonNull(factory, "factory == null"));
      return this;
    }

最后在构造Retrofit实例的时候:先将内置转换器工厂BuiltInConverters添加到集合中;再将自定义的转换器工厂拷贝到converterFactories;最后,从Platform获取平台默认的转换器工厂。

public Retrofit build() {
      ...
      // 构造1 + 自定义转换器 + 默认转换器 大小的集合
      List<Converter.Factory> converterFactories =
          new ArrayList<>(
              1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
              
      // 首先添加内置转换器工厂。
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);
      converterFactories.addAll(platform.defaultConverterFactories());
      ...
    }

从上面可以看出,一个Retrofit实例中的Converter.Factory数量为:1(内建) + 自定义数量 + Platform默认数量。

1.2、通过工厂获取Converter实例

那么何时通过Converter.Factory创建Converter呢?在Retrofit源码流程分析一文第2.2节中创建HttpServiceMethod的parseAnnotations(Retrofit, Method, RequestFactory)方法中:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    ...
    //获取Converter
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);
        
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } 
    ...
  }

通过createResponseConverter(retrofit, method, responseType)方法创建Converter<ResponseBody,ResponseT>转换器。注意这里第一个泛型参数F已经限定为ResponseBody,后面自定义Converter的时候一定要留意。

private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(
      Retrofit retrofit, Method method, Type responseType) {
    Annotation[] annotations = method.getAnnotations();
    try {
      return retrofit.responseBodyConverter(responseType, annotations);
    } catch (RuntimeException e) { // Wide exception range because factories are user code.
      throw methodError(method, e, "Unable to create converter for %s", responseType);
    }
  }

上面的方法内部再通过Retrofit实例的responseBodyConverter(Type , Annotation[] annotations)方法获取转换器:

public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }

再使用nextResponseBodyConverter()按顺序遍历1.1节中给Retrofit添加的所有Converter.Factory。如果有对应的Converter.Factory可以处理并返回Converter,则直接返回。并且后面的Converter.Factory就跳过,所以Converter.Factory的添加顺序也会影响到获取的Converter类型。

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
      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) {
        //noinspection unchecked
        return (Converter<ResponseBody, T>) converter;
      }
    }
    ...
    //如果没有对应的转换器,那么会抛出异常
    throw new IllegalArgumentException();
  }

如果没有能够处理指定返回类型Type的Converter,那么在最后抛出异常。

1.3、Converter转换请求响应

又在何时使用Converter呢?在Retrofit源码流程分析一文的第3.1节网络请求结果回调中看到了转换器的身影,Retrofit使用OkHttpCall封装okhttp3.Call,并且处理网络回调。

@Override
  public void enqueue(final Callback<T> callback) {
    okhttp3.Call call;
    ...
    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            response = parseResponse(rawResponse);
            ...
          }
          ...
        });
  }

parseResponse(rawResponse)方法显然是用来进行结果转换的。处理原始的ResponseBody,并转换成需要的类型返回(比如Json bean类或者其它数据结构)。

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ...
    //将OkHttp原始Response包装成ExceptionCatchingResponseBody 
    ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
    try {
      T body = responseConverter.convert(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {}
  }

这里用到的成员responseConverter就是1.2节中创建的Converter实例,还记得OkHttpCall是在哪里初始化的吗?

回到Retrofit源码流程分析一文的第2.3节中讲到的invoke(Object[] args)方法,将1.2节中创建的Converter实例作为参数构造OkHttpCall。

@Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

2、自定义Converter

Retrofit作为热门的网络请求库,附带很多转换器的实现,方便将网络请求直接转换成开发需要的类型。比如常用GsonConverterFactory内部使用Gson将网络请求结果转换成Json Bean类。

//Gson
    implementation 'com.squareup.retrofit2:converter-gson'
    
    //Jackson
    implementation 'com.squareup.retrofit2:converter-jackson'
    
    //Moshi
    implementation 'com.squareup.retrofit2:converter-moshi'
    
    //Protobuf
    implementation 'com.squareup.retrofit2:converter-protobuf'
    
    //Wire
    implementation 'com.squareup.retrofit2:converter-wire'
    
    //Simple Framework
    implementation 'com.squareup.retrofit2:converter-simplexml'
    
    //Scalars
    implementation 'com.squareup.retrofit2:converter-scalars'

这么多附加的转换器还是满足不了业务需求怎么办?自己动手丰衣足食,自定义Converter及Converter.Factory。前面已经对Converter的实现过程有一个全面的认识,接下来就是自定义一个转换器,将网络请求结果直接转换成Bitmap。

2.1、自定义Converter工厂

自定义转换器工厂很简单,只需要实现Converter.Factory抽象类,重写Converter<ResponseBody, ?> responseBodyConverter()方法:主要是根据返回类型Type判断能否处理,并且返回对应的Converter。如果无法处理直接返回null(父类默认返回null),Retrofit会继续遍历后面的Converter.Factory,直到找到一个能够处理的Converter。

class BitmapConverterFactory private constructor() : Converter.Factory() {
    
        companion object {
            private val INSTANCE = BitmapConverterFactory()
            fun getInstance(): BitmapConverterFactory {
                return INSTANCE
            }
        }
        override fun responseBodyConverter(
            type: Type,
            annotations: Array<out Annotation>,
            retrofit: Retrofit
        ): Converter<ResponseBody, *>? {
            //如果返回类型是Bitmap,那么就返回自定义的Bitmap转换器。否则返回null。
            return if (type.typeName == (Bitmap::class.java.name)) {
                BitmapResponseBodyConverter()
            } else null
        }
        
    }

Converter.Factory还有另外一个Converter<?, RequestBody> requestBodyConverter()方法可以重写,可以对网络请求前的RequestBody拦截进行一些操作。在RequestFactory的parseParameterAnnotation(...)方法中解析注解参数构造请求,会通过Retrofit获取合适的转换器以处理请求RequestBody,流程和ResponseBody转换器类似,就不再赘述。

2.2、实现Converter

从前面的1.3节中,我们知道,Retrofit将网络请求响应体ResponseBody包装成ExceptionCatchingResponseBody给Converter处理。而且OkHttpCall中的转换器类型定义如下,Converter<F,T>中的from类型参数必须是ResponseBody:

private final Converter<ResponseBody, T> responseConverter;

自定义Converter很简单,只需实现Converter接口。实现T convert(F value)方法,从而完成网络响应从F -> T的类型转换。

public interface Converter<F, T> {

      T convert(F value) throws IOException;
  	  
    }

以自定义BitmapResponseBodyConverter为例,返回类型是我们需要的Bitmap,在这个自定义转换器中,将网络响应转换成Bitmap并返回。

class BitmapResponseBodyConverter : Converter<ResponseBody, Bitmap?> {
    
        override fun convert(value: ResponseBody): Bitmap? {
            value.use {
                return try {
                    val data: ByteArray = value.bytes()
                    BitmapFactory.decodeByteArray(data, 0, data.size)
                } catch (ignore: Exception) {
                    null
                }
            }
        }
        
    }

后面在业务中拿到的网络请求结果就是可以直接用的Bitmap,而不是ResponseBody。是不是很方便!

2.3、使用自定义Converter

这里演示,定义了一个接口。Retrofit中有很多注解,用来自定义网络请求RESTful接口,详见Retrofit注解详细探究一文。

interface GetBitmapService {
    @GET("/upload/{path}")
    fun getBitmap(@Path("path") path: String): Call<Bitmap>
}

上面的接口返回参数类型是Bitmap,也就是直接从网络请求中获取Bitmap。

接下来构造Retrofit实例,在前面1.2节我们已经知道,Converter.Factory的添加顺序也会影响结果的转换。先添加处理范围小的转换器工厂,最后添加通用转换器工厂兜底。

val retrofit = Retrofit.Builder()
            .baseUrl("http://quibbler.cn/")
            .addConverterFactory(BitmapConverterFactory.getInstance())
            .addConverterFactory(GsonConverterFactory.create())
            .build()

这里演示直接获取服务器avatar/000/1.png路径中的图片,网络请求结果直接拿到Bitmap,而不用在多进行额外的解析操作。

val call: Call<Bitmap> = bitmapService.getBitmap("avatar/000/1.png")
    
    call.enqueue(object : Callback<Bitmap?> {
        override fun onResponse(call: Call<Bitmap?>, response: Response<Bitmap?>) {
            //返回响应body()类型就是Bitmap?
            val bitmap: Bitmap? = response.body()
            imageView.setImageBitmap(bitmap)
        }
        
        override fun onFailure(call: Call<Bitmap?>, t: Throwable) {
            //TODO
        }
    })

相关推荐

精选
Airbnb开源项目
开源

Airbnb开源项目

Airbnb Engineering & Data ScienceAirflow Use Apache Airflow (incubating) to author workflows as directed acyclic graphs (DAGs) of tasks12,263Airpal Web UI for PrestoDB2,502Aerosolve A machine learning

1.8k
OkHttp线程池和连接池
开源

OkHttp线程池和连接池

OkHttp线程池和连接池了解了OkHttp的网络请求流程以及拦截器实现原理,再关注OkHttp中两个重要的:OkHttp的线程池和连接池。1、OkHttp线程池在OkHttp网络请求流程一文中,我们分析了OkHttp异步和同步请求流程。请求最后都在Dispatcher中分发调度处理,最后被ExecutorService执行。1.1、DispatcherDispatcher中执行任务的执行器是ex

5.2k
优美的开源动效库:Lottie
开源

优美的开源动效库:Lottie

优美的开源动效库:Lottie1、强大的动效LottieLottie是一个适用于Android,iOS,Web和Windows的库,它可以使用Bodymovin解析以json格式导出的Adobe After Effects动画,并在移动设备和Web上原生渲染它们!GitHub:https://github.com/airbnb/lottie-androidLottie官网:http://airbn

4.3k