Retrofit出来之后,就曾学习过它的使用方法,也做过简单Demo。但是这次在项目中应用Retrofit2.0的时候,还是发现一些新问题。趁着项目封包上线,特来全面梳理总结,从最简单的请求网络一步步的丰富功能打造一套完善可行的网络请求框架,以及自己在项目应用中遇到的问题和解决办法:
先附上官方介绍:Retrofit、Github源码地址
如何使用Retrofit进行网络请求
- 添加Retrofit依赖
- 创建 用于描述网络请求 的接口
- 创建 Retrofit 实例
- 创建 网络请求接口实例 并 配置网络请求参数
- 发送网络请求
1.添加Retrofit及相关库的依赖
compile 'com.squareup.retrofit2:retrofit:2.2.0'
这里我们看见只添加Retrofit的依赖,但是大家不要误会,Retrofit只是RESTful 的 HTTP 网络请求框架的封装。可以在Retrofit 2.0源码的maven配置里发现添加了对OkHttp的依赖。网络请求本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装。
App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作;
在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析。
附:也可通过添加依赖来指定OkHttp版本:
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
2.创建用于描述网络请求的接口
- 这里定义的是interface;
- 这里@GET注解是定义网络请求方式;
- 后面的括号里的参数是接口路径名,我们知道无法只通过接口路径定位到接口,先带着疑问往下看;
- 这里@Query注解是服务接口的方法参数;
- 返回值这里先返回完整的响应体,稍后会解释怎么数据解析。
这里注解还有很多,这里暂不一一解释,后文会给出。
3.创建Retrofit实例 & 4.创建网络请求接口实例并配置网络请求参数
- 解惑:在创建Retrofit实例的时候设置了baseUrl,Retrofit把 网络请求的URL 分成了两部分设置。网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置;
- 创建网络请求接口的实例,把Retrofit实例跟描述网络请求的接口关联起来;
- 添加方法调用网络请求。
5.发送网络请求
通过上面的工作,我们的Retrofit就已经做好了网络请求的准备了。下面正式发起请求,来验证流程是否正确。
注意申请网络权限
<uses-permission android:name="android.permission.INTERNET"/>
- 通过获取Retrofit实例发起请求传递参数;
- 在enqueue()里面设置请求响应的回调处理,我们可以看见,在回调里面能直接更新UI,说明这里回调是在主线程里,但具体是怎么操作的,后文去阅读源码再做介绍。
关于Retrofit那些注解
Retrofit的注解我们上面已经接触过了@GET、@Query,现在我来更全面的了解这些注解。其实Retrofit提供三类一共22个注解,帮助我们在使用过程中配置网络。
1.HTTP请求方法类注解
表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method、path、hasBody。下面我们试着用@HTTP替代我们上面接口里面的定义:
2.标记类注解
- @FormUrlEncoded 表示发送form-encoded的数据,每个键值对需要用@Filed来注解键名,随后的对象需要提供值;
- @Multipart 表示发送form-encoded的数据(适用于 有文件 上传的场景),每个键值对需要用@Part来注解键名,随后的对象需要提供值。
3.网络请求参数类注解
- @Header 添加请求头
- @Headers 添加不固定的请求头
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()
// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法
- @Body 以 Post方式 传递 自定义数据类型 给服务器;
特别注意:如果提交的是一个Map,那么作用相当于 @Field。
Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");
- @Field & @FieldMap 发送 Post请求 时提交请求的表单字段
与 @FormUrlEncoded 注解配合使用
public interface GetRequest_Interface {
/**
*表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
* <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);
/**
* Map的key作为表单的键
*/
@POST("/form")
@FormUrlEncoded
Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);
}
// 具体使用
// @Field
Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);
// @FieldMap
// 实现的效果与上面相同,但要传入Map
Map<String, Object> map = new HashMap<>();
map.put("username", "Carson");
map.put("age", 24);
Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);
- @Part & @PartMap 发送 Post请求 时提交请求的表单字段
与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于有文件上传的场景
与 @Multipart 注解配合使用
public interface GetRequest_Interface {
/**
* {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
* 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);
/**
* PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
* 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
* 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
*/
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
@POST("/form")
@Multipart
Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}
// 具体使用
MediaType textType = MediaType.parse("text/plain");
RequestBody name = RequestBody.create(textType, "Carson");
RequestBody age = RequestBody.create(textType, "24");
RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");
// @Part
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
ResponseBodyPrinter.printResponseBody(call3);
// @PartMap
// 实现和上面同样的效果
Map<String, RequestBody> fileUpload2Args = new HashMap<>();
fileUpload2Args.put("name", name);
fileUpload2Args.put("age", age);
//这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
//fileUpload2Args.put("file", file);
Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
ResponseBodyPrinter.printResponseBody(call4);
}
- @Query和@QueryMap 用于 @GET 方法的查询参数(URL请求里?后面的键值对)
配置时只需要在接口方法中增加一个参数即可
@GET("bookcontent/")
Call<ResponseBody> getBookInfo(@Query("bookid") String bookId);
- @Path URL地址的缺省值
可用于配置动态的URL地址
@GET("users/{user}/repos")
Call<ResponseBody> getBlog(@Path("user") String user );
// 访问的API是:https://api.github.com/users/{user}/repos
// 在发起请求时, {user} 会被替换为方法的第一个参数 user(被@Path注解作用)
- @Url 直接传入一个请求的 URL变量 用于URL设置
@GET
Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
// 当有URL注解时,@GET传入的URL就可以省略
// 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供
一般在Android开发中,设计网络请求框架的时候我们都要考虑三个功能:网络请求、请求结果数据解析、异步响应(因为考虑到要在主线程刷新UI)。在上文中我们关于Retrofit网络请求模块的使用已经有了完整的认识,下面我们继续介绍数据解析、和异步响应的知识点。
Retrofit数据解析器(Converter)
在前面的例子中我们请求中都是直接返回的Call<ResponseBody> ,这里并没有做响应结果的解析,我们还需要自己去响应体里提取和解析数据。其实,这些需求Retrofit的设计者们都已经给我们考虑到了,Retrofit支持包括常见的Json在内的,多种数据解析方式。以Json为例具体如下使用:
1.使用时需要在Gradle添加依赖
数据解析器 | Gradle依赖 |
---|---|
Gson | com.squareup.retrofit2:converter-gson:2.0.2 |
Jackson | com.squareup.retrofit2:converter-jackson:2.0.2 |
Simple XML | com.squareup.retrofit2:converter-simplexml:2.0.2 |
Moshi | com.squareup.retrofit2:converter-moshi:2.0.2 |
Wire | com.squareup.retrofit2:converter-wire:2.0.2 |
Scalars | com.squareup.retrofit2:converter-scalars:2.0.2 |
// 在build.gradle中添加依赖
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
2.创建Retrofit实例时,添加ConverterFactory
3.创建bean类并设置接口返回类型
Retrofit网络请求适配器(CallAdapter)
Retrofit支持多种网络请求适配器方式:guava、Java8和Rxjava ,使用时如使用的是 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加,以Rxjava为例具体如下使用:
1.使用时需要在Gradle添加依赖
网络请求适配器 | Gradle依赖 |
---|---|
guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
rxjava | com.squareup.retrofit2:adapter-rxjava:2.0.2 |
这里有一个很坑的地方是, com.squareup.retrofit2:adapter-rxjava:2.0.2,用的是RxJava1.X,现在所以很多Rx2的新特性都没有,所以在这里为了支持Rx2.0,并不建议使用square的适配器。我用的是jakewharton大神出的一款适配器,应用如下:
// 引入请求适配器
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0-RC3'
// 引入RxAndroid适应Android开发需求
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
2.创建Retrofit实例时,添加CallAdapterFactory
3.修改接口返回类型
调用接口请求数据:
至此关于在项目中引入Retrofit+OkHttp+RxJava的流程已经简单的使用就已经介绍完毕,但是自己在项目的使用过程中还是遇见了一些问题,下面会做出总结。
在项目使用过程中遇见的问题
1.OkHttp在4.4及以下不支持TLS协议的解决方法
这个bug最新是测试发现安装包在某个机型上无法请求网络,然后找来测试机调试。发现在网络请求的时候,会报异常,具体信息如下:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x79f145b0: Failure in SSL library, usually a protocol error
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
解决方案参考:# OkHttp在4.4及以下不支持TLS协议的解决方法
2.混淆问题
这个问题就有点坑了,测试用release包发现所有页面都请求不到数据。当时听描述就大概锁定了是混淆问题。但是自己多次对照官方文档的混淆配置,发现自己的配置跟官方配置一样。然后调试的时候看了一下异常信息,大概是说解析的时候的问题。然后我去掉了Retrofit的Gson数据解析器进行测试,发现请求是能正常发送和响应的。锁定了问题之后问题之后开始找解决办法,由于converter-gson的资料很少。我想着其原理应该是Gson类似,于是这是找Gson有关的混淆问题,最后解决办法如下:
# Retrofit
-dontnote retrofit2.Platform
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions
# okhttp
-dontwarn okio.**
# converter-gson
-keep class cn.tianya.light.reader.model.bean.**{*;} # 自定义数据模型的bean目录