Android Retrofit 基本使用和源码解析
1.关于Retrofit基本介绍
- Retrofit是Square 公司开发的一款正对Android 网络请求的框架。底层基于OkHttp 实现,OkHttp 已经得到了google 官方的认可。
- Retrofit是由Square公司出品的针对于Android和Java的类型安全的Http客户端,如果看源码会发现其实本质上是OkHttp的封装,使用面向接口的方式进行网络请求,利用动态生成的代理类封装了网络接口请求的底层,其将请求返回JavaBean,对网络认证REST API进行了很友好的支持。使用Retrofit将会极大的提高我们应用的网络体验。
- RxJava + Retrofit + okHttp组合,流行的网络请求框架
- Retrofit 负责请求的数据和请求的结果,使用接口的方式呈现,OkHttp 负责请求的过程,RxJava 负责异步,各种线程之间的切换。
- RxJava 在 GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。总之就是让异步操作变得非常简单。
- 为什么要使用Retrofit?
- 优点
- 请求的方法参数注解可以定制
- 支持同步、异步和RxJava
- 超级解耦
- 可以配置不同的反序列化工具来解析数据,如json、xml等
- 其他说明
- 在处理HTTP请求的时候,因为不同场景或者边界情况等比较难处理。你需要考虑网络状态,需要在请求失败后重试,需要处理HTTPS等问题,二这些事情让你很苦恼,而Retrofit可以将你从这些头疼的事情中解放出来。
- 效率高,其次Retrofit强大且配置灵活,第三和OkHttp无缝衔接,第四Jack Wharton主导的(你懂的)。
- 在处理HTTP请求的时候,因为不同场景或者边界情况等比较难处理。你需要考虑网络状态,需要在请求失败后重试,需要处理HTTPS等问题,二这些事情让你很苦恼,而Retrofit可以将你从这些头疼的事情中解放出来。
- 优点
2.最简单使用
- Api接口
1 | public interface DouBookApi { |
- Model类
1 | public class DouBookModel { |
- 抽取类
1 | public class RetrofitWrapper { |
- 使用
1 | DouBookModel model = DouBookModel.getInstance(activity); |
3.注解的种类
- 请求方法注解
1 | @GET get请求 |
- 请求头注解
1 | @Headers 用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在 |
- 标记注解
1 | @FormUrlEncoded |
- 参数注解
1 | 参数注解:@Query 、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap |
- 其它注解
1 | @Path、@Url |
4.Retrofit相关请求参数
- @Query()【备注:get请求/ 接上参数 】
1 | @Query:作用于方法参数,用于添加查询参数,即请求参数 |
- @QueryMap()【备注:get请求/ 接上参数 】
1 | @QueryMap:作用于方法的参数。以map的形式添加查询参数,即请求参数,参数的键和值都通过String.valueOf()转换为String格式。默认map的值进行URL编码,map中的每一项发键和值都不能为空,否则跑出IllegalArgumentException异常。 |
- @Path()【备注:get请求/ 替换url中某个字段】
1 | /** |
- @Body()【备注:post请求/ 指定一个对象作为HTTP请求体】
1 | 使用@Body 注解定义的参数不能为null 。当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。 |
- @Field()【备注:post请求/ 用于传送表单数据】
1 | 用于传送表单数据: |
- @FieldMap()【备注:post请求/ 用于传送表单数据】
1 | @FormUrlEncoded |
- @Header/@Headers()【备注: 添加请求头部 】
1 | 用于动态添加请求头部: |
- @Part()作用于方法的参数,用于定义Multipart请求的每和part
1 | 使用该注解定义的参数,参数值可以为空,为空时,则忽略。使用该注解定义的参数类型有如下3中方式可选: |
- @PartMap()作用于方法的参数
1 | 以map的方式定义Multipart请求的每个part map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常。 |
使用时注意事项
- 1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
- 2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在Url上;Field、FieldMap的数据是请求体
- 3、{占位符}和PATH尽量只用在URL的path部分,url的参数使用Query、QueryMap代替,保证接口的简洁
- 4、Query、Field、Part支持数据和实现了iterable接口的类型,如List、Set等,方便向后台传递数组,代码如下:
- 5、以上部分注解真正的实现在ParameterHandler类中,每个注解的真正实现都是ParameterHandler类中的一个final类型的内部类,每个内部类都对各个注解的使用要求做了限制,比如参数是否可空、键和值是否可空等。
- 6、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
- 7、@Path 与@Url 注解不能同时使用,否则会抛出parameterError(p, “@Path parameters may not be used with @Url.”),可在ServcieMethod类中parseParameterAnnotation()方法中找到不能同时使用的具体代码。其实原因也是很好理解:Path注解用于替换url中的参数,这就要求在使用path注解时,必须已经存在请求路径。不然没法替换路径中指定的参数。而@Url 注解是在参数中指定了请求路径的,这时候情定请求路径已经晚,path注解找不到请求路径,更别提更换请求路径了中的参数了。
- 8、使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了FormUrlEncoded或Multipart注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)
5.Retrofit与RxJava结合
使Rxjava与retrofit结合条件
- 在Retrofit对象建立的时候添加一句代码addCallAdapterFactory(RxJavaCallAdapterFactory.create())
1 | 完整代码 |
- 可以看到 Observable观察者
1 | public Observable<DouBookBean> getHotMovie(String tag, int start , int count) { |
- 可以看到订阅者
- RxAndroid其实就是对RxJava的扩展。比如上面这个Android主线程在RxJava中就没有,因此要使用的话就必须得引用RxAndroid
1 | DouBookModel model = DouBookModel.getInstance(activity); |
6.OkHttpClient
拦截器说明
- addNetworkInterceptor添加的是网络拦截器Network,Interfacetor它会在request和response时分别被调用一次;
- addInterceptor添加的是应用拦截器Application Interceptor他只会在response被调用一次。
日志拦截器
- 一种是使用HttpLoggingInterceptor,需要使用到依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16compile 'com.squareup.okhttp3:logging-interceptor:3.5.0'
/**
* 创建日志拦截器
* @return
*/
public static HttpLoggingInterceptor getHttpLoggingInterceptor() {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.e("OkHttp", "log = " + message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return loggingInterceptor;
}另一种是创建自定义日志拦截器
请求头拦截器
1 | /** |
- 统一请求拦截器
- 使用addInterceptor()方法添加到OkHttpClient中,统一请求拦截器的功能跟请求头拦截器相类似
1 | /** |
- 缓存拦截器
- 使用okhttp缓存的话,先要创建Cache,然后在创建缓存拦截器
1 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); |
- 缓存拦截器, 缓存时间自己根据情况设定
1 | /** |
- 自定义CookieJar
1 | /** |
7.踩坑经验
- url被转义
1 | http://api.mydemo.com/api%2Fnews%2FnewsList? |
8.Form表单提交与multipart/form-data
8.1 form表单常用属性
- action:url 地址,服务器接收表单数据的地址
- method:提交服务器的http方法,一般为post和get
- name:最好好吃name属性的唯一性
- enctype: 表单数据提交时使用的编码类型,默认使用”pplication/x-www-form-urlencoded”,如果是使用POST请求,则请求头中的content-type指定值就是该值。如果表单中有上传文件,编码类型需要使用”multipart/form-data”,类型,才能完成传递文件数据。
8.2 浏览器提交表单时,会执行如下步骤
- 识别出表单中表单元素的有效项,作为提交项
- 构建一个表单数据集
- 根据form表单中的enctype属性的值作为content-type对数据进行编码
- 根据form表单中的action属性和method属性向指定的地址发送数据
8.3 提交方式
- get:表单数据会被encodeURIComponent后以参数的形式:name1=value1&name2=value2 附带在url?后面,再发送给服务器,并在url中显示出来。
- post:content-type 默认”application/x-www-form-urlencoded”对表单数据进行编码,数据以键值对在http请求体重发送给服务器;如果enctype 属性为”multipart/form-data”,则以消息的形式发送给服务器。
8.4 POST请求
- HTTP/1.1 协议规定的HTTP请求方法有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 这几种。其中POST一般用于向服务器提交数据。
- 大家知道,HTTP协议是以ASCII 码传输,建立在TCP/IP协议之上的应用层规范。规范把HTTP请求分为3大块:状态行、请求头、消息体。类似于如下:
1 | <method> <request-URL> <version> |
- 协议规定POST提交的数据必须放在消息主题(entity-body)中,但协议并没有规定数据必须使用什么编码方式。实际上,开发者可以自己决定消息体的格式,只要后面发送的HTTP请求满足上面的格式就可以了。
- 但是,数据发送出去后,还要服务器解析成功才有意义。一般服务器都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的Content-Type字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到POST提交数据方法,包含了Content-Type和消息主题编码方式两部分。
8.5 enctype指定的content-type
- application/x-www-form-urlencoded
- application/json
- text/xml
- multipart/form-data
9.content-type介绍
9.1 application/x-www-form-urlencoded
这应该是最常见的POST提交数据的方式了。浏览器的原生
9.2 application/json
- application/json 这个Content-Type作为响应头大家肯定不陌生。事实上现在已经基本都是都是这种方式了,来通知服务器消息体是序列化后的JSON字符串。由于JSON规范的流行,除了低版本的IE之外的现在主流浏览器都原生支持JSON。当然服务器也有处理JSON的函数。
- JSON格式支持比键值对更复杂的结构化数据,这样点也很有用,在需要提交数据层次非常深的数据时,用JSON序列化之后提交,非常方便。
1 | POST http://www.hao123.com/ HTTP/1.1 |
- 这种方案,可以很方便的提交复杂的结构化的数据,特别适合RESTful的接口。而且各大抓包工具如chrome自带的开发者工具,Firebug、Fidder,都会以树形结构展示JSON数据,非常友好。
9.3 text/xml
它是一种使用HTTP作为传输协议,XML作为编码方式的远程调用规范。典型的XML-RPC是这样的:
XML-RPC 协议很简单、功能够用,各种语言的实现都有。它的使用也很广泛,但是我还是比较倾向于JSON,因为相比于JSON,XML太过于臃肿。
1
2
3
4
5
6
7
8
9
10
11POST http://www.example.com HTTP/1.1
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>
9.4 multipart/form-data
- 在最初的http协议中,没有定义上传文件的Method, 为了实现这个功能,http协议组改造了post请求,添加一种post规范,设定这种规范的Content-Type为multipart/form-data;boundary=${bound},其中${bound}是定义分割符,用于分割各项内容(文件,key-value对),不然服务器无法正确识别各项内容。post body里需要用到,尽量保证随机唯一。
- 这又是一个常见的POST数据提交的方式。我们使用表单上传文件时,必须让form表单enctype等于multipart/form-data。
1 | <form action="/upload" enctype="multipart/form-data" method="post"> |
案例如下所示
- 这个例子稍微复杂点。首先生成了一个boundary用于分割不同的字段,为了避免与正文内容重复,boundary很长很复杂。然后Content-Type里指明了数据以multipart/form-data来编码,本次请求的boundary是什么内容。消息主体里按照字段个数又分为多个结构类型的部分,每个部分都以—boundary开始,紧接着是内容描述信息,然后是回车,然后是字段的具体内容(文本和二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以—-boundary—-标志结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42header
Content-Type: multipart/form-data; boundary={boundary}\r\n
body
普通 input 数据
--{boundary}\r\n
Content-Disposition: form-data; name="username"\r\n
\r\n
Tom\r\n
文件上传 input 数据
--{boundary}\r\n
Content-Disposition: form-data; name="file"; filename="myfile.txt"\r\n
Content-Type: text/plain\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
hello word\r\n
结束标志
--{boundary}--\r\n
数据示例
POST /upload HTTP/1.1
Host: 172.16.100.128:5000
Content-Length: 394
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLumpDpF3AwbRwRBn
Referer: http://172.16.100.128:5000/
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="username"
Tom
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="password"
passwd
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain
hello world
------WebKitFormBoundaryUNZIuug9PIVmZWuw--