注意:以下分析都是基于Retrofit2
简单介绍
Retrofit发送网络请求通过okHttp,okhttp的诸多好处与内部实现机制,已经在之前的博客《OkHttp深入学习(一)——初探》进行了介绍。相对于直接使用okhttp的好处在于,它支持对Response接收数据进行解析,支持RxJava。
Retrofit和Volley一样,网络请求任务在背后线程中进行,返回结果的处理(或者说回调方法)在UI线程中执行。
此外这里给出Retrofit2相对于Retrofit1的改进
- 支持了在一个类型中的同步和异步,同时,一个请求也可以被真正地终止;Retrofit1之前是分开的,即需要定义两个方法
- 每一个 call 对象实例只能被调用一次,request 和 response 都是一一对应的;若需多次重复请求,则建议每次请求前clone一个call对象
- Response 对象增加了:响应码(the reponse code),响应消息(the response message),以及读取相应头(headers)
- @Url ,允许你直接传入一个请求的 URL,定义如下方法
- @GET
- Call<List<Contributor>> repoContributorsPaginate( @Url String url);
- 动态 URL Parameter
- String links = response.headers().get("Link");
- Call<List<Contributor>> nextCall = gitHubService.repoContributorsPaginate(nextLink);
基本使用
一、引入依赖
二、定义网络业务接口
Retrofit的网络请求都是写在一个接口中,并使用一定的注释如下面一组请求接口:注意接口中的每个方法的参数都需要使用标注,不采用标注将会报错。
1、Retrofit的网络请求返回的结果都是call<?>的形式,?代表希望对Response body解析得到的数据类型;如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
2、Retrofit的注解有如下几种常见注解
@GET 发送get方法的请求,@POST发送post方法的请求,@Header 网络请求的Header部分的键值对
@Body Post请求报文的Body内容,内容往往是一个对Java Bean解析后得到的JSON String,@Path网络路径的缺省值,@Query网络请求地址后面的查询键值对,如www.baidu.com/s?wd=REST&rsv_spt=1.
@FormUrlEncoded 和 @FieldMap配套使用,用于向Post表单传入键值对
3、一旦创建一个Retrofit实例就意味着网络请求根目录已经确定,但是Retrofit支持动态改变网络请求根目录。
4、使用@Multipart 标注的网络请求,用于上传文件;同时该请求必须在请求方法的参数中有一个使用@Part注解的参数。下面给出一个简单的使用例子
三、得到网络业务接口的实体
1、获得一个Retrofit对象
2、由retrofit得到一个网络请求接口的实现类
四、使用接口实体获取Response
call只能使用一次,调用第二次的时候,就会出现失败的错误。当你想要多次请求一个接口的时候,直接用 clone 的方法来生产一个新的,相同的可用对象,clone代价很低。
Retrofit2 中 Response 对象增加了曾经一直被我们忽略掉的重要元数据:响应码(the reponse code),响应消息(the response message),以及读取相应头(headers){cookies}。
五、关于授权
1、创建拦截器,拦截器的工作就是在发送请求前在请求报文的header中添加一些键值对
2、添加前面的拦截器到client中,为了提高网络访问效率我们还为OkHttp设置了缓存,到此为止跟okhttp
的操作都是一样的
3、为Retrofit设置个性化OkHttpClient对象
六、Retrofit和RxJava的配套使用
PartA 定义网络业务接口
网络请求方法的返回值由之前的call<?> 转变成Observable<?>, ?类支持使用gson对其进行序列化
PartB 得到网络业务接口的实体
1、默认发送同步的网络请求
2、默认发送异步的网络请求
3、获取网络请求实体
PartC 使用示例
1、获取到Observable<User>之后的使用与RxJava一样的使用流程
2、这里要十分注意的是在Android的Activity和Fragment中使用Rxjava需要在对应的onDestroy方法中调用subscription.unsubscribe()方法,防止OOM
源码学习
在正式学习源码之前首先看看我们想要了解的内容有哪些?
- Retrofit对象的创建需要哪些元素;该对象内部保存有哪些元素
- Retrofit对象的create方法返回的对象和传进去的接口有何种关联
- 业务接口使用各种注解对方法会产生何种影响?常用注解@GET、@POST、@MultiPart、@FieldMap等作用效果
- Retrofit的ConverterFactory; CallAdapterFactory方法设置的Converter.Factory和CallAdapter.Factory对象何时何地起作用
- Call<?> 对象及其execute和queue方法的内部逻辑
- Observable<?>相对于RxJava有什么特殊的地方
- Response<?>类型内部结构,有哪些数据可以访问
Retrofit.class
Retrofit类中存储有如下的对象
Fields
Retrofit()@Retrofit.class
1、unmodifiableList(list)方法近似于UnmodifiableList<E>(list);这样做的好处在于创建的新对象能够对list数据进行访问,但是不可通过该对象对list集合中的元素进行任何修改
Builder.class@Retrofit.class
该类中的域基本与Retrofit一样,但是多了一个如下的域
private Platform platform;
Builder()@Builder.class
1、如果是安卓系统则Platform.get()获取到的是一个如下的对象;也可以获得IOS、Java对应的类,很好很强大。
2、添加一个内置的转换器工厂到converterFactories中
3、返回一个默认的回调方法执行器,该执行器负责在指定的线程中执行回调方法,Android就是将回调方法在UI线程中执行
4、如果我们不准备将RxJava和Retrofit一起使用,一般都是使用的这个默认CallAdapter.Factory,因此我们在后面对ExecutorCallAdapterFactory.class源码进行解析,先不急着看等遇到了再去看。
所以到此为止Builder默认创建了Converter.Factory、CallAdapter.Factory,Executor三个系统默认的数据转换器工厂、网络请求适配器工厂、回调执行器。可能大伙儿有点陌生,这些个工厂究竟是干嘛的?特此先跟大伙儿说几句,本节的末尾还会详谈:
Converter.Factory
- public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) //对响应数据的解析
- public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) //对请求数据的解析
- public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) //普通对象转件String 如JSON
CallAdapter.Factory
- public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) //获取网络请求适配器
- protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); }
- protected static Class<?> getRawType(Type type) { return Utils.getRawType(type); }
CallAdapter<?>
- Type responseType(); //该请求适配器返回的数据类型
- <R> T adapt(Call<R> call); //该请求适配器对原始Call的再次封装,如Call<R>到Observable<R>
Build()@Builder.class
1、基地址检测
2、网络请求执行器检测,若为空直接设置为OkHttpClient
3、回调方法执行器检测,若为空直接设置为Buidler构造器中创建的Executor
4、复制Builder中的List<CallAdapter.Factory>,并向该集合中添加Buidler构造器中创建的CallAdapter.Factory请求适配器,添加在集合器末尾
5、复制Builder中的List<Converter.Factory>,虽然该集合没有像前面第四步那样添加默认转换器,但是别忘了其实在Builder的构造器中已经向Builder中的List<Converter.Factory>集合的第一个位置插入了一个默认的转换器。
请求适配器工厂集合存储的是:自定义1适配器工厂、自定义2适配器工厂...默认适配器工厂
数据转换器工厂集合存储的是:默认数据转换器工厂、自定义1数据转换器工厂、自定义2数据转换器工厂....
create()@Retrofit.class
1、判断该参数Service是不是一个接口,接口中是否有定义方法;否则抛出异常
2、判断是否需要提前验证,方法内容就是给接口中的定义的每个方法的注解进行解析并得到一个ServiceMethod对象,并以Method为键将该对象存入LinkedHashMap集合中。如果不是提前验证则在第四步的时候会动态解析对应的方法,得到一个ServiceMethod对象,最后存入到LinkedHashMap集合中,有延迟加载的意思。默认都是延迟加载。eagerlyValidateMethods方法后面还有更为详细的介绍。
3、创建一个代理——java.lang.reflect.Proxy对象;
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler invocationHandler)方法
近似于getProxyClass(loader, interfaces)
.getConstructor(InvocationHandler.class)
.newInstance(invocationHandler);从字面就可以知道,通过这个代理类,调用interfaces接口的方法实际上是通过调用InvocationHandler类型对象的invoke方法来完成指定的功能。
4、如果代理类传进来的方法是一般方法则不会执行到这里,执行到这里表明调用的方法是我们使用了符合Retrofit规则的
标注的
方法。该行语句是从serviceMethodCache集合中获取一个对应的ServiceMethod对象。如果没有会临时创建一个ServiceMethod对象,再返回。loadServiceMethod方法后面也有更为详细的介绍。
5、利用上面得到的ServiceMethod对象和方法参数创建一个OkHttpCall对象,该对象相对于okhttp3.Call会在网络请求前后对数据利用该方法对应的数据转换器进行一定的转换,之后内部再通过okhttp3.Call发送请求。OkHttpCall.class的相关方法在后面也会给出详细的介绍。
6、将上面得到的OkHttpCall对象传给ServiceMethod中对应的网络请求适配器工厂的adapt方法中,返回的对象类型就是ServiceMethod对应的方法的返回值类型,如Call<?>;
eagerlyValidateMethods()@@Retrofit.class
1、非平台默认方法则加载解析该方法,并将得到的ServiceMethod对象加入到LinkedHashMap<Method, ServiceMethod>集合中。使用LinkedHashMap该集合的好处就是lruEntries.values().iterator().next()获取到的是集合最不经常用到的元素,提供了一种Lru算法的实现。
loadServiceMethod()@Retrofit.class
1、尝试从集合中获取该方法
2、方法在集合中不存在,创建一个ServiceMethod对象,以method为键存入集合中。往下我们看看如何构建一个ServiceMethod对象
ServiceMethod.class
ServiceMethod()@ServiceMethod.class
该对象包含了访问网络的所有基本的信息。
内部类Builder<T>.class@ServiceMethod<T>.class
Fields
Builder()@Builder.class
build()@Builder.class
1、近似于 callAdapter = retrofit.callAdapter(returnType, annotations);即根据方法返回值类型和注释从retrofit中获取对应的网络请求适配器。callAdapter方法是从Retrofit的adapterFactories集合中从0开始遍历,找到第一个满足要求的网络请求适配器。
2、从前面的网络适配器(callAdapter)中获取该网络适配器返回的数据类型
3、返回数据类型不可以为retrofit2.Response.class和okhttp3.Response.class类型,Response<T>和Response是有区别的。
4、近似于 responseConverter = retrofit.responseBodyConverter(responseType, annotations);即根据方法返回值类型和注释从retrofit中获取对应的转换器。responseBodyConverter方法是从Retrofit的converterFactories集合中从0开始遍历,找到第一个满足要求的内容转换器。
5、对方法中的DELETE、GET、POST、HEAD、PATCH、PUT、OPTIONS、HTTP、retrofit2.http.Headers、Multipart、FormUrlEncoded几个标注进行处理,大多数标签都会调用
方法parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody),该方法对ServiceMethod中的httpMethod、hasBody、relativeUrl、relativeUrlParamNames域进行赋值。
6、请求报文中body中没有内容,但是使用了Multipart或者FormUrlEncode标注则抛出异常
7、当前方法中参数个数,为每个参数创建一个arameterHandler
8、为方法中的每个参数创建一个ParameterHandler<?>对象,该对象的创建过程就对方法参数中的Body、PartMap、Part、FieldMap、Field、Header、QueryMap、Query、Path、Url标注进行解析
9、后面就是一些判断了,最后创建ServiceMethod对象
该方法内容比较多我们再次梳理一下:
首先根据返回值类型和方法标注从Retrofit的网络请求适配器工厂集合和内容转换器工厂集合中分别获取到该方法对应的网络请求适配器和Response内容转换器;根据方法的标注对ServiceMethod的域进行赋值;最后为每个方法的参数的标注进行解析,获得一个ParameterHandler<?>对象,该对象保存有一个Request内容转换器——根据参数的类型从Retrofit的内容转换器工厂集合中获取一个Request内容转换器或者一个String内容转换器。
往下我们接着对create()@Retrofit.class中OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);利用ServiceMethod和方法参数args创建的OkHttpCall类进行分析。
Call.class
首先简单看一下retrofit.Call接口的定义
OkHttpCall.class
如果我们没有特别给Retrofit设置一个请求适配器工厂,则Retrofit使用默认的请求适配器工厂——ExecutorCallAdapterFactory.class,默认请求适配器工厂是存放在Retrofit的请求适配器工厂集合的末尾。通过ExecutorCallAdapterFactory得到Call<?> 对接收的网络请求没有经过任何特殊的处理直接交给OkHttpCall.class进行处理。证明分析见后面的ExecutorCallAdapterFactory.class源码部分。对于OkHttpCall类我们就分析下它的execute方法和enqueue方法。
final class OkHttpCall<T> implements Call<T>
OkHttpCall()@OkHttpCall.class
Fields
execute()@OkHttpCall.class
1、创建一个okhttp3.Call请求
Request request = serviceMethod.toRequest(args); 该方法会对使用对应的ParameterHandler进行解析,并利用ServiceMethod中存储的headers等数据构造一个okhttp请求
return serviceMethod.callFactory.newCall(request);近似于OkHttpClient.newCall(request)
2、对请求结果解析,方法内部逻辑如下:
以上过程就是整个网络请求的过程,首先对方法中每个参数利用对应ParameterHandler进行解析,再加上ServiceMethod中存储的Headers等数据创建一个okhttp的Request;使用okhttp发送这个请求;再对接收到的请求利用ServiceMethod存储的内容转换器对响应内容进行转换,最终得到一个Response<T>对象。
enqueue()@OkHttpCall.class
异步请求跟同步请求类似,也有创建okhttp请求,并对okhttp请求响应结果进行转换的过程;不同之处在于异步请求会将回调方法交给回调执行器在指定的线程中执行。对于okhttp的Call对象的enqueue、execute方法这里不再往下分析了,感兴趣的同学参考博客
到此为止,我们学习到的有:
一、通过Retrofit的Builder内部类如何创建一个Retrofit对象,通过buidler设置baseUrl和可选的Converter.Factory、CallAdapter.Factory。
二、调用Retrofit的create方法,根据create方法参数(接口类对象)构造一个代理,代理的执行实体是一个InnovationHandler对象。
三、调用接口的所有方法最终都是通过调用InnovationHandler的invoke方法,invoke方法接收的参数主要有Method和args,invoke方法内部逻辑为:
- 将Method对象转为一个ServiceMethod对象,构造过程会根据方法的标注从Retrofit的ArrayList<Converter.Factory> converterFactories和ArrayList<CallAdapter.Factory> adapterFactories集合中获取该方法对应的Converter<ResponBody,T> responseConverters和CallAdapter<?> callAdapter,同时根据方法参数的标注为每个参数创建一个ParameterHandler对象,该对象包含有一个Converter<?,RequestBody> requestConverter对象或者一个Converter<?,String> stringConverter对象;用于对每个参数进行转换处理。
- 利用前面的ServiceMethod对象和args创建一个OkHttpCall对象,该对象内部会包含一个okhttp3.Call对象,OkHttpCall会在okhttp3.Call对象进行网络访问前后分别利用对应的requestConverter和responseConverter对数据进行相应的转换。
- 通过ServiceMethod的callAdapter对象的adapter方法(参数OkHttpCall)创建一个本Method预期的返回值对象;
- 如果在构造Retrofit的时候没有设置CallAdapter.Factory则都是返回的Call<?>对象,该对象的execute方法和enqueue方法实际上都是直接调用OkHttpCall的同名方法,返回一个Response<T>对象
- 如果在构造Retrofit的时候设置了RxJavaCallAdapterFactory 那么可能返回对象可以定义为Observable<?> 类型,对于该类型我们的使用可以跟RxJava的使用一样,但是内部具体如何实现,我们还未曾介绍过。
上面我们对学过的内容进行了总结,Retrofit基本上也了解的差不多了。往下深入分析就是对Converter.Factory和CallAdapter.Factory的分析,如果童鞋们对这部分不感兴趣那么可以到此打住了,都散了吧。感兴趣同学请看下节内容。
reference: