Retrofit2 探索

Retrofit2 是 Square 公司推出的支持 Java 和 Android 平台的 Http 开源框架。该框架使用注解的方式构建 Http 请求,想法非常巧妙,API 也设计的非常优雅。

本文基于 Retrofit2.0.2

写在前面

发送请求的一般过程,以官网代码为例。

1
2
3
4
5
6
//接口定义
public interface GitHubService {
//接口定义方法称之为 Service Method
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
//创建接口代理对象
GitHubService service = retrofit.create(GitHubService.class);
1
2
3
4
5
6
7
8
9
10
11
12
13
//发送异步请求
Call<List<Repo>> call = service.listRepos("octocat");
call.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
//正确回调
}

@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
//错误回调
}
});

开始之前,有些概念需要提前说明一下。

  • Service Method 指什么?
    接口定义称之为 Service Method。之所以这么说,是因为接口的定义信息都被解析为了一个叫 ServiceMethod 类型的对象。后面会详细解释。
  • Return Type 与 Response Type?
    responseType 是指接口定义中的泛型部分,即之前例子中的 List<Repo> 。而 Return type 是指 Call<List<Repo>>
    先来看 Retrofit.create(class<T> service) 方法,这就是魔法发生的地方。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public <T> T create(final Class<T> service) {

    return (T) Proxy.newProxyInstance(
    service.getClassLoader(),
    new Class<?>[] { service },
    new InvocationHandler() {
    private final Platform platform = Platform.get();

    @Override
    public Object invoke(Object proxy, Method method, Object... args){
    // 一些必要的判断
    ServiceMethod serviceMethod = loadServiceMethod(method);
    OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    return serviceMethod.callAdapter.adapt(okHttpCall);
    }
    });
    }

Platform 用于支持多个平台,包括 Java Android 和 iOS(需要额外的支持包)。同时,Platform 包含了默认的回调 Executor(之后会详细介绍) 。
可以看到,create(...)方法使用代理方式 Proxy.newProxyInstance 为每个 Service Method 创建代理对象。这也解释了为什么我们没有实现 Service Method,却可以发送请求。

下面详细介绍 Retrofit2 处理 HTTP 请求的过程的各个阶段。

解析接口定义,构造 ServiceMethod 对象

ServiceMethod 是 Retrofit2 中重要的对象,包含了构造 HTTP 请求的所有信息。

1
2
3
4
5
6
7
8
9
10
11
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}

loadServiceMethod 方法非常简单,只是根据接口定义构造 SeiviceMethod 并进行 缓存。下面我们着重看下如何构造 SeiviceMethod 对象。
SeiviceMethod 类使用Builder 模式进行创建。该类中只有两个 public 方法:SeiviceMethod.Builder 构造函数和 SeiviceMethod.Builder.build() 方法。
构造函数只是简单的赋值语句,下面详细介绍 build() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public ServiceMethod build() {
callAdapter = createCallAdapter();
responseType = callAdapter.responseType();

// responseType 错误判断
// responseType 不能是 retrofit2.Response 和 okhttp3.Response

responseConverter = createResponseConverter();

for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}

// httpMethod 错误判断
// httpMethod 为空,不指定请求方式情何以堪

// 没有请求体的情况下使用 @FormUrlEncoded 或者 @Multipart 注解。


int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
Type parameterType = parameterTypes[p];
if (Utils.hasUnresolvableType(parameterType)) {
throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
parameterType);
}

Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
if (parameterAnnotations == null) {
throw parameterError(p, "No Retrofit annotation found.");
}

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}
// 注解使用错误情况判断
// 1、缺少 URL
// 2、不支持请求体的请求方式带有 @Body 注解
// 3、form encoded 方式缺少请求体,即缺少 @Field 注解
// 4、multipart 编码,缺少 @Part 注解

return new ServiceMethod<>(this);
}

排除掉错误校验部分,该方法包含了四个部分:

  1. 处理 CallAdapter,顺便解析 responseType
  2. 处理 responseConverter,用于将返回数据解析为 responseType 类型。
  3. 处理方法注解,解析请求方式、编码格式等方法注解。
  4. 处理参数注解,根据参数类型和注解为每一个参数创建相应的 ParameterHandler

为什么是这四个部分?
我们再看一下接口的定义。

1
2
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);

接口定义一共包含:方法注解、返回值Call、泛型类型 List>、参数列表(参数注解和参数类型)。ServeiceMethod 就是为了解析接口方法,也就对应了接口的四个部分。

下面依次探索这四个部分都是如何处理的。

处理 CallAdapter

什么是 CallAdapter

CallAdapter 包含两个方法:

  • Type responseType() 返回该 CallAdapter 处理的 response 类型,该类型是指 Call<List<Repo>> 中的 List
  • <R> T adapt(Call<R> call)Call 类型转换为 T 类型,不能处理的类型返回 null 。

CallAdapter.Factory 根据接口定义的返回值(Call>)创建 CallAdapter 实例。
自定义 CallAdapter 需要继承自 CallAdapter.Factory。一般情况下不需要自定义。

已支持的 CallAdapter

  • RxJava
  • Java8
  • Guava

CallAdapter 从哪来

1
2
3
4
5
6
// 删除部分校验代码
private CallAdapter<?> createCallAdapter() {
Type returnType = method.getGenericReturnType();
Annotation[] annotations = method.getAnnotations();
return retrofit.callAdapter(returnType, annotations);
}
1
2
3
public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations){
return nextCallAdapter(null, returnType, annotations);
}
1
2
3
4
5
6
7
8
9
10
public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
// 如果未找到可以处理该返回类型的 CallAdapter ,抛出异常。
}

以上代码可以看出,通过遍历 Retrofit 中的 adapterFactories 找到第一个可以处理该返回值的 CallAdapter

当接口返回 Call<?> 时,不需要设置 CallAdapter。此时,是如何处理的?
Retrofit.Builder.build() 方法中,adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)) 添加了默认的 CallAdapter。对于 Android 平台而言,默认的 Executor 是 Platform.Android.MainThreadExecutor,默认的 CallAdapter.Factory 是 ExecutorCallAdapterFactory

处理 Response Converter

注意,此处的 Converter 是指用于解析返回数据的 Response Converter。之后还有用于处理 Service Method 参数的 Converter。

什么是 Converter

Converter 只有一个方法 T convert(F value) throws IOException。用于将 Http 请求中的 请求参数返回数据 转换为相应的类型。

自定义Converter需要继承Converter.FactoryFactory 共有三个方法:

  • Converter<ResponseBody, ?> responseBodyConverter(...)
    用于将返回数据转换为相应类型,不能处理返回 null 。

  • Converter<?, RequestBody> requestBodyConverter(...)
    用于将请求数据中使用 Body,Part,PartMap 注解的参数转换为 RequestBody

  • Converter<?, String> stringConverter(...)
    用于将请求数据中使用 Field,FieldMap,Header,Path,Query,QueryMap注解的参数转换为Stirng

BuildInConverters

  • 返回数据转换
    仅支持 ResponseBodyVoid 类型。其中,转换类型为ResponseBody时,根据是否存在方法注解 Streaming,返回 非 Buffer 的 ResponseBody 和 Buffer 的 ResponseBody

  • Body,Part,PartMap 注解转换
    仅支持 RequestBody -> RequestBody 类型。

  • Field,FieldMap,Header,Path,Query,QueryMap 注解转换仅支持 String -> String 类型。

已支持的 Converter

  • 用于将 String,基本数据类型及其包装类转换为text/plainScalarsConverterFactory
  • 使用 GsonGsonConverterFactory
  • 使用 JacksonJacksonConverterFactory
  • 使用 MoshiMoshiConverterFactory
  • 使用 Protocol BufferProtoConverterFactory
  • 使用 Simple 解析 xml 的 SimpleXmlConverterFactory
  • 使用 Wire 解析 Protocol Buffer 的 WireConverterFactory

Response Converter 从哪来

Converter 处理与 CallAdapter 类似。遍历 Retrofit.converterFactories 寻找第一个能够处理该 response 类型的 Converter

为什么使用 json converter,如 Gson、Jackson 时,需要最后添加该 Converter?
对于 Json 数格式而言,GsonConverter 无法判断是否可以处理该 Response 。responseBodyConverter() 方法的返回值永远不为 null。如果 GsonConverter 不是最后添加的话,其他的 Converter 永远没有机会处理该 Response,哪怕 GsonConverter 不能处理该 Response 。

处理方法注解

方法注解包括 Http 请求方式、请求地址、请求头。

处理参数注解

按照参数顺序,根据参数注解,为每个参数构造相应的 ParameterHandler,保存在数组 parameterHandlers 当中。
ParameterHandler 用于将处理接口参数,并将参数添加到到请求相应部分当中。

参数注解有且只能有一个。

  • @Url
    注解使用 ParameterHandler.RelativeUrl 实例进行处理,且参数类型必须为 okhttp3.HttpUrlStringjava.net.URIandroid.net.Uri其中之一。
  • @Path
    注解使用 ParameterHandler.Path 实例进行处理。具体来说,首先在 Retrofit.converterFactories 中寻找能够处理该参数类型的 Converter ,即 Converter.Facyory.stringConverter(...) 方法返回值不为 null 。如果未找到相应的 Converter,就使用 BuiltInConverters.ToStringConverter,仅调用参数的 toString() 方法。
  • @Query
  • @QueryMap
  • @Header
  • @Field
  • @FieldMap
  • @Part
  • @PartMap
  • @Body

发送请求,解析数据,回调

ServiceMethod 对象包含了构造 Http 请求的所有信息,但是这些信息是如何使用的呢?让我们重新回到 Retrofit.create() 方法。

1
2
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

使用 ServiceMethod 和 Service Mehtod 参数构造 OkHttpCall。OkHttpCall 就是 Retrofit2 中发送网络请求的对象。但是,Retrofit2 的网络请求部分是由 okhttp3.Call 处理的。

构造 OkHttpCall,创建 okhttp3.Call

1
2
3
4
5
6
7
8
private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args);
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}

okhttp3.Call 的创建分为两步:构造 okhttp3.Request 、构造 okhttp3.Call 。
okhttp3.Request 转换为 okhttp3.Call 的过程由 okhttp3.Call.Factory ,即 OkHttpClient 完成,不再继续深究。下面详细看下如何生成 okhttp3.Request 。

1
2
3
4
5
6
7
8
9
10
11
12
13
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);

@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(requestBuilder, args[p]);
}

return requestBuilder.build();
}

ServiceMethod 使用 RequestBuilder 将之前已经解析过的接口信息组装为 Request。

对 OkHttpCall 进行适配

我们已经知道 CallAdapter 的作用是将 Call(OkHttpCall) 对象转换为其他类型。
Android 平台下,默认的转换后的类型为 ExecutorCallAdapterFactory.ExecutorCallbackCall
ExecutorCallbackCall 构造函数 ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) 。其内部方法除了 enqueue(...) 方法,其他全部直接调用 delegate 方法。着重看一下 enqueue(...) 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override 
public void enqueue(final Callback<T> callback) {
if (callback == null) throw new NullPointerException("callback == null");

delegate.enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
}
});
}

@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(new Runnable() {
@Override public void run() {
callback.onFailure(ExecutorCallbackCall.this, t);
}
});
}
});
}

ExecutorCallbackCall.enqueue 使用 callbackExecutor 执行 callback。所以说,callback 执行线程为 Android 主线程。

CallbackExecutor 只对返回值为 Call<?> 的 Service Method 有作用,对自定义返回值类型无效。

发送请求

根据以上分析可知,Call<List<Repo>> call = service.listRepos("octocat") 方法返回值实际为 ExecutorCallbackCall 对象。当执行 call.enqueue 时,调用 OkHttpCall.enqueue(...) 方法,进一步调用 okhttp.Call.enqueue(new okhttp3.Callback()) 发送网络请求。

解析返回数据

OkHttpCall.parseResponse() 方法负责返回数据的解析。此方法内部调用 ServiceMethod.toResponse(...) 解析返回数据。

总结

Retrofit2 网络请求处理分为四个阶段:

  1. 解析 ServiceMethod 构造 ServiceMethod 对象。这是 Retrofit2 最复杂的部分。
    ① 解析Http请求相关信息,如:请求方式、头信息、接口地址,请求体编码格式。
    ③ 寻找能够处理返回类型的 Converter。
    ② 寻找能够处理参数类型的 Converter。
    ④ 寻找能够处理 Service Method 返回值的 CallAdapter。
  2. 使用 ServiceMethod 构造 okhttp3.Call,并发送请求。
  3. 使用 ServiceMethod.responseConverter 解析返回数据
  4. 进行回调

收获

Bulider 模式与 Factory 模式的异同

  • 都用于简化对象创建过程,提高代码的可读性。
  • Builder 模式需要了解对象构建的细节,适用于可选参数过多的对象创建。
  • Factory 模式不需要了解对象的创建细节,仅需要对象创建的“特征描述”。
    RetrofitConverter 的创建为例。
    创建 Retrofit 对象,需要 url,CallAdapter 等多个可选对象。如果直接使用 new 方法创建的话,参数列表较多,而且可能出现多个参数为 null 的情况,大大影响代码的可读性。相反,使用 Builder 模式,可以采用链式调用,选择性地传递需要的参数,可以保持代码的简单和优雅。
    对于 Converter 对象,使用者不需要了解其创建细节,只需要告诉 Factory ,自己需要什么类型的 Converter 就行。如,Factory.requestBodyConverter() 表明一个可以将某种类型转换为 RequestBody 的转换器。

Java 类型处理

retrofit2.Utils 中有大量的工具方法用于处理类型信息。以根据 Method Service 返回类型获取 Response type 为例。

1
2
3
4
5
6
7
static Type getCallResponseType(Type returnType) {
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
return getParameterUpperBound(0, (ParameterizedType) returnType);
}

1
2
3
4
5
6
7
8
9
10
11
12
static Type getParameterUpperBound(int index, ParameterizedType type) {
Type[] types = type.getActualTypeArguments();
if (index < 0 || index >= types.length) {
throw new IllegalArgumentException(
"Index " + index + " not in range [0," + types.length + ") for " + type);
}
Type paramType = types[index];
if (paramType instanceof WildcardType) {
return ((WildcardType) paramType).getUpperBounds()[0];
}
return paramType;
}

代码写的很清楚,似乎也不需要解释,( >﹏< ),这些 API 倒是长见识。