retrofit2 源码初步分析

参考:http://bxbxbai.github.io/2015/12/13/retrofit2-analysis/

以前使用的是android-async-http作为网络访问库,最近改用了Retrofit2,本文简单的分析下Retrofit2的源代码。

什么是Retrofit2

来自Retrofit官网的介绍:

A type-safe HTTP client for Android and Java

Retrofit 可以说是目前最流行的类型安全网络库,是Square公司的一个开源项目,通过注解来创建restful API接口。目前2.0版本已经稳定,下文如无特殊说明,均指2.0版本。

Retrofit2使用步骤

Retrofit2的使用很简单,主要分为以下几个步骤:

  1. 创建Interface接口,通过注解标识客户端与后台约定的API接口

    1
    2
    3
    4
    public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
    }
  2. 创建Retrofit实例,生成Interface的实现类(动态代理实现对象)

    1
    2
    3
    4
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    GitHubService service = retrofit.create(GitHubService.class);
  3. 调用上面创建的实现类中的API方法,取得Call对象

    1
    Call<List<Repo>> repos = service.listRepos("octocat");
  4. 根据调用场景异步或同步,执行Call对象的异步或同步方法,异步方法需要传入Callback回调函数

    • 异步:call.enqueue(Callback callback)
    • 同步:call.execute()

Retrofit2内部原理

  • 第一步是根据跟服务器的接口协议文档写注解接口。(Retrofit非常巧妙的用注解来描述一个HTTP请求)
  • 第二步根据接口创建相关执行代理对象。

    1
    2
    3
    4
    5
    // The Retrofit class generates an implementation of the GitHubService interface.
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .build();
    GitHubService service = retrofit.create(GitHubService.class);

    给Retrofit对象传了一个GitHubService接口的Class对象,怎么返回一个GitHubService的对象呢?我们来看看create方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // Single-interface proxy creation guarded by parameter safety.
    public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
    eagerlyValidateMethods(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)
    throws Throwable {
    // If the method is a method from Object then defer to normal invocation.
    if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
    }
    if (platform.isDefaultMethod(method)) {
    return platform.invokeDefaultMethod(method, service, proxy, args);
    }
    return loadMethodHandler(method).invoke(args);
    }
    });
    }

create方法重要就是返回了一个动态代理对象。

动态代理是个什么东西?
Java 动态代理就是Java开发给了开发人员一种可能:当你要调用某个类的方法前,插入你想要执行的代码。
比如你要执行某个操作前,你必须要判断这个用户是否登录。

为什么要使用动态代理
你看上面的代码,获取数据的代码就是这句:

1
2
// Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.
Call<List<Repo>> repos = service.listRepos("octocat");

上面的service对象其实是一个动态代理对象,并不是一个真正的GitHubService接口的implements对象,当service对象调用listRepos方法时,执行的是动态代理方法,此时,动态代理发挥了它的作用,你看上去是调用了listRepos方法,其实此时Retrofit把GitHubService接口翻译成一个HTTP请求,也就是Retrofit中的MethodHandler对象,这个对象包含了:

1) OkHttpClient:发送网络请求的工具
2) RequestFactory:包含了HTTP请求的URL、Header信息,MediaType、Method以及RequestAction数组
3) CallAdapter:HTTP请求返回数据的类型
4) Converter:数据转换器
  • 第三步Retrofit在你调用Call<List<Repo>> repos = service.listRepos("octocat");后为你生成了一个Http请求,
  • 第四步你调用call.enqueue方法时就发送了这个请求,然后你就可以处理Response的数据了
    从原来上讲,就是这样的。

Retrofit2源码分析

想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:

  1. 一个retrofit2.http包,里面全部是定义HTTP请求的注解,比如GETPOSTPUTDELETEHeadersPathQuery等等
  2. 余下的retrofit2包中十几个类和接口就是全部retrofit的代码了,代码不是很多,retrofit把网络请求这部分功能全部交给了OkHttp了

Retrofit2接口

下面几个接口需要了解一下

Callback<T>
这个接口是retrofit请求数据返回的接口,有下面两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Callback<T> {
/**
* Invoked for a received HTTP response.
* <p>
* Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
* Call {@link Response#isSuccess()} to determine if the response indicates success.
*/
void onResponse(Call<T> call, Response<T> response);

/**
* Invoked when a network exception occurred talking to the server or when an unexpected
* exception occurred creating the request or processing the response.
*/
void onFailure(Call<T> call, Throwable t);
}

Converter<F, T>
这个接口主要的作用就是讲HTTP返回的数据解析成Java对象,主要有Xml、Gson等等,我们可以在创建Retrofit对象时添加需要使用的Converter实现

1
2
3
4
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build();

Call<T>
这个接口主要的作用是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>,我们也可以根据实际情况实现自己的Call类。

CallAdater<T>
CallAdapter 中属性只有 responseType一个,还有一个<R> T adapter(Call<R> call)方法,这个接口的实现类也只有一个,DefaultCallAdapter。这个方法的主要作用就是将Call对象转换成另一个对象,可能是为了支持RxJava才设计的这个类的吧。

Retrofit2的运行

上面分析到GitHubService service = retrofit.create(GitHubService.class);代码返回了一个动态代理对象,而执行Call<List<Repo>> repos = service.listRepos("octocat");代码返回了一个OkHttpCall对象,拿到这个Call对象才能执行HTTP请求。
其中后一句代码执行了一个非常复杂的过程

当执行listRepos方法时,Retrofit其实是执行了动态代理的InvocationHandler对象,最后会创建一个MethodHandler对象,这个对象很重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static MethodHandler create(Retrofit retrofit, Method method) {
CallAdapter<?> callAdapter = createCallAdapter(method, retrofit);
Type responseType = callAdapter.responseType();
if (responseType == Response.class || responseType == okhttp3.Response.class) {
throw Utils.methodError(method, "'"
+ Types.getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?");
}
Converter<ResponseBody, ?> responseConverter =
createResponseConverter(method, retrofit, responseType);
RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);
return new MethodHandler(retrofit.callFactory(), requestFactory, callAdapter,
responseConverter);
}

上面的代码就是创建一个MethodHandler对象,一个MethodHandler对象中包含了4个对象

1. OkHttpClient

这个是Retrofit默认生成的

2. RequestFactory

通过RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);生成,主要作用就是解析整个Http请求的数据
主要原料是解析一个接口,比如上面的GitHubService接口,得到整个Http请求的全部信息,还会通过@Path注解拼接Url.

3. CallAdapter

获取CallAdapter的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static CallAdapter<?> createCallAdapter(Method method, Retrofit retrofit) {
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw Utils.methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}
if (returnType == void.class) {
throw Utils.methodError(method, "Service methods cannot return void.");
}
Annotation[] annotations = method.getAnnotations();
try {
return retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw Utils.methodError(e, method, "Unable to create call adapter for %s", returnType);
}
}

我们可以在创建Retrofit对象时,添加想要的CallAdapter,获取CallAdapter的方式也是从Retrofit对象中获取

4. Converter

获得Converter对象和上面的原来类似

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

创建这4个对象的目的就是为了执行下面这句代码

1
2
3
4
Object invoke(Object... args) {
return callAdapter.adapt(
new OkHttpCall<>(callFactory, requestFactory, args, responseConverter));
}

这个也就是Call<List<Repo>> repos = service.listRepos("octocat");返回的Call对象。
最后调用Call对象的executeenqueue(Callback<T> callback)方法,就能发送一个Http请求了。Retrofit提供了同步和异步两种HTTP请求方式。

如何在Retrofit中使用RxJava

RxJava是由Netflix开发的响应式扩展(Reactive Extensions)的Java实现。由于Retrofit设计的扩展性非常强,我们只需要改变一下CallAdapter就可以了

1
2
3
4
5
6
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

上面代码创建了一个Retrofit对象,支持Proto和Gson两种数据格式,并且还支持RxJava

最后

Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了动态代理方式,动态的将这个接口注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求。