Android 源码解析 Retrofit2 原理
2017-06-13 15:55
381 查看
开发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、我的心得与看法
来自Retrofit官网的介绍:
A type-safe HTTP client for Android and Java
简单的说它是一个基于OkHttp的RESTFUL Api请求工具,从功能上来说和Google的Volley功能上很相似,但是使用上很不相似。
Volley使用上更加原始而且符合使用者的直觉,当App要发送一个Http请求时,你需要先创建一个
地址,一个处理response的回调,如果是一个POST请求,那么你还需要给这个
而Retrofit可以让你简单到调用一个Java方法的方式去请求一个api,这样App中的代码就会很简洁方便阅读
虽然Retrofit官网已经说明了,我还是要按照我的思路说一下它的使用方法
比如你要请求这么一个api,查看知乎专栏的某个作者信息:
https://zhuanlan.zhihu.com/api/columns/{user}
首先,你需要创建一个
其次,你要根据api新建一个Java接口,用Java注解来描述这个api
再用这个
这样就表示你要请求的api是
最后你就可以用这个
看到没,
从上面Retrofit的使用来看,Retrofit就是充当了一个适配器(Adapter)的角色:将一个Java接口翻译成一个Http请求,然后用OkHttp去发送这个请求
Volley描述一个HTTP请求是需要创建一个
问题来了:
Retrofit是怎么做的呢?
答案很简单,就是:Java的动态代理
我刚开始看Retrofit的代码,我对下面这句代码感到很困惑:
我给Retrofit对象传了一个
}
动态代理是个什么东西?
看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在Spring框架里大量的用到,但是它有什么用呢?
Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码
比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。
你看上面代码,获取数据的代码就是这句:
上面
Object proxy: 代理对象,不关心这个
Method method:调用的方法,就是
Object... args:方法的参数,就是
而Retrofit关心的就是
的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个
使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送
想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:
一个
余下的
Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:
这个接口就是retrofit请求数据返回的接口,只有两个方法
这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建
这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是
上面说到过,
上面讲到
上面
创建一个
刚才说到,
第一步,获取到上面说到的3个接口对象:
第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了
第三步,比如上面api中带有一个参数
最后,
之前讲到,
总的来说,
body传入
body转成一个Java对象
结合上面说的就可以看出,
你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?
我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;而且Retrofit会对解析过的请求进行缓存,就在
由于Retrofit设计的扩展性非常强,你只需要添加一个
上面代码创建了一个
Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求
Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)
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,查看知乎专栏的某个作者信息:
https://zhuanlan.zhihu.com/api/columns/{user}
首先,你需要创建一个
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的可能
相关文章推荐
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
- [置顶] Android 网络框架之Retrofit2使用详解及从源码中解析原理
- Android 热修复Nuwa的原理及Gradle插件源码解析
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Android 热修复Nuwa的原理及Gradle插件源码解析
- Android 带你从源码的角度解析Scroller的滚动实现原理
- android网络框架retrofit源码解析一
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Retrofit2 源码解析 理解原理能帮助我们更好的使用框架
- Android-AndFix 热修复框架原理及源码解析
- 【Android】AsyncTask原理应用及源码关键部分解析
- android网络框架retrofit源码解析二
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Android 热修复Nuwa的原理及Gradle插件源码解析
- Android 带你从源码的角度解析Scroller的滚动实现原理