闲暇之时看了看Retrofit的源码(2.0.0版本),其内部是基于Okhttp来实现网络访问请求的,为自己以前写过Okhttp源码分析的文章,自己也对Okhttp在使用时进行了二次封装。所以没理由不分析分析Retrofit这么牛叉的框架是怎么对Okhttp进行再次设计的。
通过分析该源码,着实又学到了一点东西。故此写下供自己可大家参阅,共同学习(当然本文不会花费篇幅写Retrofit的使用,网上有大量的资料可参考)。如果想要了解Okhttp源码的实现原理,可参考博主的Okhttp源码分析系列博客。
先来简单回顾下Okhttp发起同步和异步请求的方式:
//同步请求
Response response = okhttpClient.newCall(request).execute();
//异步请求
Response response =okhttpClient.newCall(request).enqueue(callback);
从上面两行代码,我们可以直达Okhttp发起网络请求有三个步骤:
1、将所需参数构建成Request对象(至于怎么构建,参考博主okthhp系列博客)
2、根据Request对象创建Call对象
3、执行call.execute/enqueue来完成同步/异步请求
这三点很重要是okhttp发起网络整体的核心,了解上面三点后,就让我们进入Retrofit的内部世界,看看其怎么设计的吧。
Retrofit内部的Call
Retrofit在对okhttp封装的时候也设计了一个Call接口,不过这个接口是泛型接口,下面两幅图就很好的说明了Retrofit的Call和Okhttp 的Call的区别和联系:
Retrofit的call:
最大的区别是什么?很明显,就是Retrofit的okhttp的call接口进行了泛化处理,比如提供了泛型的Response,泛型的Callback等等。
因为Retrofit是基于Okhttp的封装,所以不论Retrofti的泛型call接口怎么设计,其内部访问肯定是需要构建Okhttp的call对象的。理解这一点的话,那么下面的伪代码就不难理解了:
//自定义Retrofit call
Class MyCall implements Call {
Response execute() {
//构建Okhttp Request对象
Request request = createRequest();
//构建Okhttp3的Call对象
Okhttp3.Call call = okhttpClient.newCall(request);
//执行请求:这里返回值有误,只是为了说明情况
return call.execute();
}
}
其实上面的伪代码就是Retrofit执行Okhttp请求的根本原理,Retrofit的牛叉之处就在构建Request对象的时候是通过注解来动态构建的。所以抛开伪代码,看看其内部牛叉哄哄的设计。
OkhttpCall的解析
通过上面的伪代码,我们知道在Retrofti内部无非也是调用了Okhttp的call来完成请求过程。为此Retrofit提供了OkhttpCall这个类:
final class OkHttpCall implements Call {
看看OkhttpCall的execute方法是怎么完成请求以及数据转换的:
//注意返回的不是Okhttp3.Response,而是Retrofit的Response
public Response execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
//省略部分无关代码
call = rawCall;
if (call == null) {
call = rawCall = createRawCall();
}
}
//省略部分无关代码
return parseResponse(call.execute());
}
删除无关紧要的代码之后execute()方法感觉跟赤裸的小绵羊一样,一切无处遁形!主要是做了三件事:
1、调用createRawCall()方法创建Okhttp3.Call对象
2、调用call.execute()方法返回一个Okhttp3的Response对象
3、调用parseResponse方法将步骤2的Response进行处理,而后返回Retrofit的Respone《T》对象。
.还记得文章开头说的Okhttp请求的三个步骤吗?记得的话那么createRawCall方法的内部原理也就不难猜出了:将接口文件(也就是你的ApiService.class)的注解信息进行解析再加上相关参数,然后这讲些数据通过Okhttp的Request.Builder()来对这些参数进行封装Request对象。最终通过OKhttp的Factory接口返回一个Call对象:
//Okhttp3的Factory接口
interface Factory {
Call newCall(Request request);
}
至于Retrofit怎么对数据进行解析的,因为代码比较繁琐,且为了本篇博文的有序性,会另外开博文单独说明。
下面来说说parseResponse对Okttp的Response是怎么处理的:
Response parseResponse(okhttp3.Response rawResponse) throws IOException {
//获取Okhttp的响应体
ResponseBody rawBody = rawResponse.body();
//对ResponseBody再处理
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
//省略与本文无关的代码
//ExceptionCatchingRequestBody extends ResponseBody
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
//将ResponseBody交给 serviceMethod.toResponse
T body = serviceMethod.toResponse(catchingBody);
//返回数据
return Response.success(body, rawResponse);
}
parseResponse方法的核心逻辑也不难理解,拿到OKhttp的响应体RequestBody之后,交给serviceMethod.toResponse进行数据转换,转换后的body就是你Api的某个方法所需要的真实数据对象,比如Call
。注意个人认为Retrofit最牛叉的地方之一就是toResponse的设计,该方法将网络返回的数据RequestBody通过该方法注重转换成你传入的T这个类型:
Converter responseConverter
R toResponse(ResponseBody body) throws IOException {
return responseConverter.convert(body);
}
最终交给responseConverter的convert方法,将body转换成你所需要的数据。我所需要的数据是什么呢?那么怎么讲convet来进行转换呢?答案是看你的具体实现,如果你想用Gson来转换,就用Gson,如果用其他来转换也可以。所以Retrofit提供了Conveter 这个接口,该接口的主要一个convet方法:
//F是源数据了类型,T是转换后的数据类型
public interface Converter {
T convert(F value)
}
完美的实现了内部只依赖于抽象,不依赖于细节的设计理念。
最终parseResponse将转换的数据T 以及Okhttp的源数据封装成Response.success(body, rawResponse)进行返回:
public static Response success(@Nullable T body, okhttp3.Response rawResponse) {
//省略部分代码
return new Response<>(rawResponse, body, null);
}
而这个Response的结构如下:
private final okhttp3.Response rawResponse;
private final @Nullable T body;//泛型传来的数据
private final @Nullable ResponseBody errorBody;
private Response(okhttp3.Response rawResponse, @Nullable T body,
@Nullable ResponseBody errorBody) {
this.rawResponse = rawResponse;
this.body = body;
this.errorBody = errorBody;
}
//我们可通过body方法拿到转换后的数据
public @Nullable T body() {
return body;
}
至于如果用Gson转换成T 对应的Java Bean,Retrofit 提供了GsonConverterFactory,当然需要添加依赖:
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
在构建Retrofit对象的时候可以这么使用:
.addConverterFactory(GsonConverterFactory.create())
至于解析的实现细节,可以看看博主Gson源码解析系列博文
综上所述所以简单的使用就是如下所示:
//对发送请求进行封装
Call testCall= mApi.getString("xxx");
//发送网络请求(同步)
Response response = testCall.execute();
String str = response.body();
//执行异步请求
testCall.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
//获取真实数据
String str = response.body();
}
@Override
public void onFailure(Call call, Throwable t) {
}
});
所以完整的请求流程就可以简单总结如下:
1、根据ApiService的注解及其参数构建Request对象
2、构建Okhttp的call对象
3、执行call的execute方法返回Okhttp3.Response
4、调用parseResponse对数据进行解析转换,转换数据需要的是Convert的实现类
上面的四个步骤是在HttpCall中实现的
说完了同步执行请求的execute方法,再来看看Retrofit的异步请求是怎么实现的,对应的是HttpCall的enqueue方法:
public void enqueue(final Callback callback) {
okhttp3.Call call;
Throwable failure;
synchronized (this) {
call = rawCall;
//构建Call ,详细参考上文
if (call == null && failure == null) {
call = rawCall = createRawCall();
}
//省略与本文无关的代码
//调用Okhttp.Call的enqueue来完成异步请求
call.enqueue(new okhttp3.Callback() {
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response response;
//仍然调用parseResponse方法对原始数据进行解析
response = parseResponse(rawResponse);
//执行callback回调
callback.onResponse(OkHttpCall.this, response);
}
//省略部分代码
}
可以看出Retrofit的异步请求直接就是用了Okhttp的enqueue方法,且在回调处理的时候同样的调用parseResponse进行原始数据转换。将转换的结果添加到call.onResponse中:
public interface Callback {
void onResponse(Call call, Response response);
}
到此为止,关于OkhttpCall进行同步和异步请求的原理讲解完毕。其实就是用Okhttp的Call对象来实现的,Retrofit的做法只不过是在请求参数构建以及返回的数据解析替我们做了更好的处理而已,方便了我们的使用。
抛开别的不谈,从总体上来看一个网络请求的过程其实就是:构建请求参数,发起请求,解析请求数据的过程.
1、构建封装请求参数,至于怎么封装和构建的方法有好多种,而Retrofit选用注解的方式进行构建
2、执行网络请求,请求参数封装好之后就是发起请求了(Retrofit使用Okhttp)
3、对服务器返回的内容进行数据解析。
前两个其实都很简单,之前自己对okhttp封装的时候也考虑过能根据用户传入的泛型来自动解析,但是思路上倒是限制得太死。反观Retrofit直接提供了一个Conveter接口,具体用什么转换由客户端自己来决定。把依赖倒置的原则耍的贼6。
本篇博文到此结束,如果不当之处欢迎批评指正,共同学习