您的位置:首页 > 其它

retrofit 2 源码解析

2017-03-03 18:14 239 查看
retrofit 官网地址:http://square.github.io/retrofit/

retrofit GitHub地址:https://github.com/square/retrofit

参考:

http://www.tuicool.com/articles/eeQFVrM

http://blog.csdn.net/feng88724/article/details/52238321

原文链接:http://bxbxbai.github.io/2015/12/13/retrofit2-analysis/

开发Android App肯定会使用Http请求与服务器通信,上传或下载数据等。目前开源的Http请求工具也有很多,比如Google开发的Volley,loopj的Android Async Http,Square开源的OkHttp或者Retrofit等。

我觉得Retrofit 无疑是这几个当中最好用的一个,设计这个库的思路很特别而且巧妙。Retrofit的代码很少,花点时间读它的源码肯定会收获很多

本文的源码分析基于Retrofit 2,和Retrofit 1.0的Api有较大的不同, 本文主要分为几部分:0、Retrofit 是什么,1、Retrofit怎么用,2、Retrofit的原理是什么,3、我的心得与看法

0 Retrofit是什么

来自Retrofit官网的介绍:


A type-safe HTTP client for Android and Java


简单的说它是一个基于OkHttp的RESTFUL Api请求工具,从功能上来说和Google的Volley功能上很相似,但是使用上很不相似。

Volley使用上更加原始而且符合使用者的直觉,当App要发送一个Http请求时,你需要先创建一个
Request
对象,指定这个Request用的是GET、POST或其他方法,一个api 地址,一个处理response的回调,如果是一个POST请求,那么你还需要给这个
Request
对象设置一个body,有时候你还需要自定义添加Header什么的,然后将这个
Request
对象添加到
RequestQueue
中,接下去检查Cache以及发送Http请求的事情,Volley会帮你处理。如果一个App中api不同的api请求很多,这样代码就会很难看。

而Retrofit可以让你简单到调用一个Java方法的方式去请求一个api,这样App中的代码就会很简洁方便阅读

1 Retrofit怎么用

虽然Retrofit官网已经说明了,我还是要按照我的思路说一下它的使用方法

比如你要请求这么一个api,查看知乎专栏的某个作者信息:


首先,你需要创建一个
Retrofit
对象,并且指定api的域名:

public static final String API_URL = "https://zhuanlan.zhihu.com";

Create a very simple REST adapter which points the Zhuanlan API.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();


其次,你要根据api新建一个Java接口,用Java注解来描述这个api

public interface ZhuanLanApi {
@GET("/api/columns/{user} ")
Call<ZhuanLanAuthor> getAuthor(@Path("user") String user)
}


再用这个
retrofit
对象创建一个
ZhuanLanApi
对象:

ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);

Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");


这样就表示你要请求的api是
https://zhuanlan.zhihu.com/api/columns/qinchao


最后你就可以用这个
call
对象获得数据了,
enqueue
方法是异步发送http请求的,如果你想用同步的方式发送可以使用
execute()
方法,
call
对象还提供
cancel()
isCancel()
等方法获取这个Http请求的状态

// 请求数据,并且处理response
call.enqueue(new Callback<ZhuanLanAuthor>() {
@Override
public void onResponse(Response<ZhuanLanAuthor> author) {
System.out.println("name: " + author.getName());
}
@Override
public void onFailure(Throwable t) {
}
});


看到没,
Retrofit
只要创建一个接口来描述Http请求,然后可以让我们可以像调用Java方法一样请求一个Api,是不是觉得很神奇,很不可思议!!

2 Retrofit的原理

从上面Retrofit的使用来看,Retrofit就是充当了一个适配器(Adapter)的角色:将一个Java接口翻译成一个Http请求,然后用OkHttp去发送这个请求

Volley描述一个HTTP请求是需要创建一个
Request
对象,而执行这个请求呢,就是把这个请求对象放到一个队列中,在网络线程中用
HttpUrlConnection
去请求

问题来了:

Retrofit是怎么做的呢?

答案很简单,就是:Java的动态代理

动态代理

我刚开始看Retrofit的代码,我对下面这句代码感到很困惑:

ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);

我给Retrofit对象传了一个
ZhuanLanApi
接口的Class对象,怎么又返回一个
ZhuanLanApi
对象呢?进入
create
方法一看,没几行代码,但是我觉得这几行代码就是Retrofit的精妙的地方

/** Create an implementation of the API defined by the {@code service} interface. */
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);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}


create
方法就是返回了一个
Proxy.newProxyInstance
动态代理对象。那么问题来了...

动态代理是个什么东西?

看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在spring框架里大量的用到,但是它有什么用呢?

Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码

比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。

为什么要使用动态代理

你看上面代码,获取数据的代码就是这句:

Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");


上面
api
对象其实是一个动态代理对象,并不是一个真正的
ZhuanLanApi
接口的
implements
产生的对象,当
api
对象调用
getAuthor
方法时会被动态代理拦截,然后调用
Proxy.newProxyInstance
方法中的
InvocationHandler
对象,它的
invoke
方法会传入3个参数:

Object proxy: 代理对象,不关心这个

Method method:调用的方法,就是
getAuthor
方法

Object... args:方法的参数,就是
"qinchao"


而Retrofit关心的就是
method
和它的参数
args
,接下去Retrofit就会用Java反射获取到
getAuthor
方法的注解信息,配合
args
参数,创建一个
ServiceMethod
对象

ServiceMethod
就像是一个中央处理器,传入
Retrofit
对象和
Method
对象,调用各个接口和解析器,最终生成一个
Request
,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个
Call
对象,Retrofit2中Call接口的默认实现是
OkHttpCall
,它默认使用OkHttp3作为底层http请求client

使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送

3 Retrofit的源码分析

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

一个
retrofit2.http
包,里面全部是定义HTTP请求的Java注解,比如
GET
POST
PUT
DELETE
Headers
Path
Query
等等

余下的
retrofit2
包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了

Retrofit接口

Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:

Callback<T>

这个接口就是retrofit请求数据返回的接口,只有两个方法

void onResponse(Response<T> response);


void onFailure(Throwable t);


Converter<F, T>

这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建
Retrofit
对象时添加你需要使用的
Converter
实现(看上面创建Retrofit对象的代码)

Call<T>

这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是
OkHttpCall<T>
,你可以根据实际情况实现你自己的Call类,这个设计和Volley的
HttpStack
接口设计的思想非常相似,子类可以实现基于
HttpClient
HttpUrlConnetction
的HTTP请求工具,这种设计非常的插件化,而且灵活

CallAdapter<T>

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

Retrofit的运行过程

上面讲到
ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
代码返回了一个动态代理对象,而执行
Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
代码时返回了一个
OkHttpCall
对象,拿到这个
Call
对象才能执行HTTP请求

上面
api
对象其实是一个动态代理对象,并不是一个真正的
ZhuanLanApi
接口的
implements
产生的对象,当
api
对象调用
getAuthor
方法时会被动态代理拦截,然后调用
Proxy.newProxyInstance
方法中的
InvocationHandler
对象, 创建一个
ServiceMethod
对象

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);


创建ServiceMethod

刚才说到,
ServiceMethod
就像是一个中央处理器,具体来看一下创建这个
ServiceMethod
的过程是怎么样的

第一步,获取到上面说到的3个接口对象:

callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
responseConverter = createResponseConverter();


第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了
@Multipart
,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨

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

if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}


第三步,比如上面api中带有一个参数
{user}
,这是一个占位符,而真实的参数值在Java方法中传入,那么Retrofit会使用一个
ParameterHandler
来进行替换:

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];


最后,
ServiceMethod
会做其他的检查,比如用了
@FormUrlEncoded
注解,那么方法参数中必须至少有一个
@Field
@FieldMap


执行Http请求

之前讲到,
OkHttpCall
是实现了
Call
接口的,并且是真正调用
OkHttp3
发送Http请求的类。
OkHttp3
发送一个Http请求需要一个
Request
对象,而这个
Request
对象就是从
ServiceMethod
toRequest
返回的

总的来说,
OkHttpCall
就是调用
ServiceMethod
获得一个可以执行的
Request
对象,然后等到Http请求返回后,再将response body传入
ServiceMethod
中,
ServiceMethod
就可以调用
Converter
接口将response body转成一个Java对象

结合上面说的就可以看出,
ServiceMethod
中几乎保存了一个api请求所有需要的数据,
OkHttpCall
需要从
ServiceMethod
中获得一个
Request
对象,然后得到response后,还需要传入
ServiceMethod
Converter
转换成Java对象

你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?

我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;而且Retrofit会对解析过的请求进行缓存,就在
Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
这个对象中

如何在Retrofit中使用RxJava

由于Retrofit设计的扩展性非常强,你只需要添加一个
CallAdapter
就可以了

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


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

4 最后

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

Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)

Retrofit中接口设计的恰到好处,在你创建
Retrofit
对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的
Converter
、使用不同的
CallAdapter
,这也就提供了你使用RxJava来调用Retrofit的可能

我也慢慢看了Picasso和Retrofit的代码了,收获还是很多的,也更加深入的理解面向接口的编程方法,这个写代码就是好的代码就是依赖接口而不是实现最好的例子

好感谢开源的世界,让我能读到大牛的代码。我一直觉得一个人如果没有读过好的代码是不太可能写出好代码的。什么是好的代码?像Picasso和Retrofit这样的就是好的代码,扩展性强、低耦合、插件化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: