文章目录
- 1、简介
- 2、Retrofit 配置与基本用法
- 2.1 依赖引入与配置
- 2.2 基本用法
- 3、Retrofit 的注解
- 3.1 请求方法注解
- 3.2 请求头注解
- 3.3 请求参数注解
- 3.4 请求和响应格式(标记)注解
- 4、Retrofit 注解的配合使用
- 4.1 @GET 使用
- 4.2 @GET、@Query 使用
- 4.3 @GET、@QueryMap 使用
- 4.4 @POST 使用
- 4.5 @POST、@FormUrlEncoded、@File 使用
- 4.6 @POST、@FormUrlEncoded、@FieldMap 使用
- 4.7 @HTTP 使用
- 4.8 @Path 使用
- 4.9 @Url 使用
- 4.10 @Header 使用
- 4.11 @Headers 使用
- 4.12 @Streaming 使用
- 4.13 @Multipart、@part 使用
- 4.14 @Multipart、@PartMap 使用
- 5、Retrofit 源码解析
- 6、Retrofit 的实现与原理
- 6、总结
1、简介
- Retrofit 是基于 OkHttp 进行的封装;
- 从功能上说,Retrofit 完成了网络请求接口的封装,网络请求返回数据的解析和线程的自动切换,解决了 OkHttp 使用时存在的一些问题;
- 从代码结构上说,Retrofit 提供了很多注解,在我们配置网络请求时,简化了代码,且解耦。
OKHttp 使用时存在的问题:
- 网络请求的接口配置繁琐,需要配置请求头、请求体和请求参数;
- 网络请求返回的 Response 数据需要手动解析,且不能够复用;
- 无法自动进行线程的切换。
2、Retrofit 配置与基本用法
2.1 依赖引入与配置
// 模块的 build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// 基本属性数据转换器
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
// Gson 数据转换器
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Rxjava 适配器(可选)
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.2'
// OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
// RxJava RxAndroid RxKotlin(可选)
implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0'
// Kotlin 协程(可选)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
// 联网权限
<uses-permission android:name="android.permission.INTERNET" />
// 由于安卓 9.0 禁止明文传输,所以需要网络安全配置
// 第一步:在【包名/app/src/main/res/xml/】目录下,新建 network_security_config.xml 文件,内容如下:
// 包名/app/src/main/res/xml/network_security_config.xml
// 更详细的可以查看:https://developer.android.com/training/articles/security-config?hl=zh-cn
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true"/>
<debug-overrides>
<trust-anchors>
<certificates src="system" overridePins="true"/>
<certificates src="user" overridePins="true"/>
</trust-anchors>
</debug-overrides>
</network-security-config>
// 第二步:在 AndroidManifest.xml 的 application 标签中添加:
android:networkSecurityConfig="@xml/network_security_config"
//最后在模块的 build.gradle 中开启 java 1.8 支持,retrofit2 低版本可能不需要次支持,但是咱们用的是 2.9.0 版本,故需要支持该项
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
2.2 基本用法
// 1.定义请求接口
interface ApiService {
@GET("/article/list/{page}/json")
fun getArticleList1(@Path("page") page: Int): Call<String>
@GET("/article/list/{page}/json")
fun getArticleList2(@Path("page") page: Int): Flowable<ApiResponse<ArticleList>>
@GET("/article/list/{page}/json")
suspend fun getArticleList3(@Path("page") page: Int): ApiResponse<ArticleList>
}
// 2.构建 OkHttpClient 对象
val baseUrl: String = "http://wanandroid.com/"
// 设置拦截器
val httpLoggingInterceptor = HttpLoggingInterceptor() {
Log.e("okhttp.OkHttpClient", it)
}
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val okHttpClient = OkHttpClient().newBuilder()
// 设置连接超时为 10 秒
.connectTimeout(10L, TimeUnit.SECONDS)
// 设置文件读取超时为 60 秒
.readTimeout(60L, TimeUnit.SECONDS)
// 设置用于读取和写入缓存响应的响应缓存为 10M
.cache(Cache(cacheDir, 10240 * 1024))
// 设置 http 日志拦截器
// 使用 addInterceptor() 也可以,即为第一层自定义拦截器
// 使用 addNetworkInterceptor() 也可,即为第六层非网页网络拦截拦截器
.addInterceptor(httpLoggingInterceptor)
.build()
// 3.构建 Retrofit 对象
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())// 基本属性转换器
.addConverterFactory(GsonConverterFactory.create())// Gson 数据转换器
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())// RxJava 适配器
.build()
// 4.通过动态代理获取到所定义的接口
val apiService = retrofit.create(ApiService::class.java)
// 5.同步请求
Thread(Runnable {
try {
val response: Response<String> = apiService.getAritrilList1(0).execute()
val body = response.body()
Log.d("response", "onResponse: $body")
} catch (e: Exception) {
e.printStackTrace()
Log.d("response", "onFailure: ${e.message}")
}
}).start()
// 5.异步请求(Retrofit 成功或者失败的回调都自动切换到 UI 线程)
apiService.getArticleList2(0).enqueue(object : Callback<ApiResponse<ArticleList>> {
/**
* 请求成功的回调方法
*
* @param call Call<ApiResponse<ArticleList>>
* @param response Response<ApiResponse<ArticleList>>
*/
override fun onResponse(
call: Call<ApiResponse<ArticleList>>,
response: Response<ApiResponse<ArticleList>>
) {
Log.d("response", "onResponse: ${response.body()?.requireData}")
}
/**
* 请求失败的回调方法
*
* @param call Call<ApiResponse<ArticleList>>
* @param t Throwable
*/
override fun onFailure(call: Call<ApiResponse<ArticleList>>, t: Throwable) {
Log.d("response", "onFailure: ${t.message}")
}
})
// 5.OkHttp + Retrofit + RxJava 组合的异步 GET 请求
apiService.getArticleList3(0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.d("response", "onResponse: ${it.requireData}")
}, {
it.printStackTrace()
Log.d("response", "onFailure: ${it.message}")
})
// 5.OkHttp + Retrofit + Coroutines
val job: Job = GlobalScope.launch {
try {
val apiResponse = apiService.getArticleList4(0)
withContext(Dispatchers.Main) {
Log.d("response", "onResponse: ${apiResponse.requireData}")
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Log.d("response", "onFailure: ${e.message}")
}
}
}
3、Retrofit 的注解
3.1 请求方法注解
请求方法注解 | 说明 |
@GET | get 请求 |
@POST | post 请求 |
@PUT | put 请求 |
@DELETE | delete 请求 |
@PATCH | patch 请求,该请求是对 put 请求的补充,用于更新局部资源 |
@HEAD | head 请求 |
@OPTIONS | options 请求 |
@HTTP | 通过注解,可以替换以上所有的注解,它拥有三个属性:method、path、hasBody |
3.2 请求头注解
请求头注解 | 说明 |
@Headers | 用于添加固定请求头,可以同时添加多个,通过该注解的请求头不会相互覆盖,而是共同存在 |
@Header | 作为方法的参数传入,用于添加不固定的 header,它会更新已有请求头 |
3.3 请求参数注解
请求参数注解 | 说明 |
@Body | 多用于 Post 请求发送非表达数据,根据转换方式将实例对象转化为对应字符串传递参数,比如使用 Post 发送 Json 数据,添加 GsonConverterFactory 则是将 body 转化为 json 字符串进行传递 |
@Filed | 多用于 Post 方式传递参数,需要结合 @FromUrlEncoded 使用,即以表单的形式传递参数 |
@FiledMap | 多用于 Post 请求中的表单字段,需要结合 @FromUrlEncoded 使用 |
@Part | 用于表单字段,Part 和 PartMap 与 @multipart 注解结合使用,适合文件上传的情况 |
@PartMap | 用于表单字段,默认接受类型是 Map<String,RequestBody>,可用于实现多文件上传 |
@Path | 用于 Url 中的占位符 |
@Query | 用于 Get 请求中的参数 |
@QueryMap | 与 Query 类似,用于不确定表单参数,相当于多个 Query 参数 |
@Url | 指定请求路径 |
3.4 请求和响应格式(标记)注解
请求和响应格式(标记) | 说明 |
@FromUrlCoded | 表示请求发送编码表单数据,每个键值对需要使用 @Filed 注解 |
@Multipart | 表示请求发送 form_encoded 数据(使用于有文件上传的场景),每个键值对需要用 @Part 来注解键名,随后的对象需要提供值 |
@Streaming | 表示响应用字节流的形式返回,如果没有使用注解,默认会把数据全部载入到内存中,该注解在下载大文件时特别有用 |
4、Retrofit 注解的配合使用
4.1 @GET 使用
-
@GET
:请求方法注解,get 请求,括号内的是请求地址,Url 的一部分。
interface ApiService {
// https://api.github.com/user
@GET("/user")
fun getData1(): Call<ResponseBody>
}
4.2 @GET、@Query 使用
-
@Query
:请求参数注解,用于 Get 请求中的参数。
interface ApiService {
// https://api.github.com/user?id=10006&name=刘亦菲
@GET("/user")
fun getData2(@Query("id") id: Long, @Query("name") name: String): Call<ResponseBody>
}
4.3 @GET、@QueryMap 使用
-
@QueryMap
:请求参数注解,与 @Query 类似,用于不确定表单参数,通过 Map 将不确定的参数传入,相当于多个 Query 参数。
interface ApiService {
// https://api.github.com/user?id=10006&name=刘亦菲
@GET("/user")
fun getData3(@QueryMap map: Map<String, Any>): Call<ResponseBody>
}
val map = mutableMapOf<String, Any>()
map["id"] = 10006
map["name"] = "刘亦菲"
val call: Call<ResponseBody> = retrofit.create(ApiService.class).getData3(map)
4.4 @POST 使用
-
@POST
:请求方法注解,post 请求,括号内的是请求地址,Url 的一部分。
interface ApiService {
// https://api.github.com/user
@POST("/user/emails")
fun getData4(): Call<ResponseBody>
}
4.5 @POST、@FormUrlEncoded、@File 使用
-
@FormUrlEncoded
:请求格式注解,请求实体是一个 From 表单,每个键值对需要使用 @Field 注解。 -
@File
:请求参数注解,提交请求的表单字段,必须要添加,而且需要配合 @FormUrlEncoded 使用。
interface ApiService{
// https://api.github.com/user/emails
@FormUrlEncoded
@POST("/user/emails")
fun getData5(@Field("name") name: String, @Field("sex") sex: String): Call<ResponseBody>
}
4.6 @POST、@FormUrlEncoded、@FieldMap 使用
-
@FieldMap
:请求参数注解,与 @Field 作用一致,用于不确定表单参数,通过 Map 将不确定的参数传入,相当于多个 Field 参数。
interface ApiService{
// https://api.github.com/user/emails
@FormUrlEncoded
@POST("/user/emails")
fun getData6(@FieldMap map: Map<String, Any>): Call<ResponseBody>
}
val map = mutableMapOf<String, Any>()
map["id"] = 10006
map["name"] = "刘亦菲"
val call: Call<ResponseBody> = retrofit.create(ApiService.class).getData6(map)
4.7 @HTTP 使用
-
@HTTP
:替换 @GET、@POST、@PUT、@DELETE、@HEAD 以及更多拓展功能。 -
method
:表示请求的方法,区分大小写,这里的值 retrofit 不会再做任何处理,必须要保证正确。 -
path
:网络请求地址路径。 -
hasBody
:是否有请求体,boolean 类型。
interface ApiService{
// https://api.github.com/user/keys
@HTTP(method = "GET", path = "/user/keys", hasBody = false)
fun getData7(): Call<ResponseBody>
}
4.8 @Path 使用
-
@Path
:请求参数注解,用于 Url 中的占位符 {},所有在网址中的参数。
interface ApiService{
// 输入 id=111,输出 https://api.github.com/orgs/111
@GET("/orgs/{id}")
fun getData8(@Query("name") name: String, @Path("id") id: Long): Call<ResponseBody>
}
4.9 @Url 使用
-
@Url
:表示指定请求路径,可以当做参数传入,如果有 @Url 注解时,GET 传入的 Url 可以省略。
interface ApiService{
// 输入 url="/orgs/{id}",id=111,输出 https://api.github.com/orgs/111
@GET("/user/emails")
fun getData9(@Url url: String, @Query("id") id: Long): Call<ResponseBody>
}
4.10 @Header 使用
-
@Header
:用于添加不固定的请求头,作用于方法的参数,作为方法的参数传入,该注解会更新已有的请求头。
interface ApiService{
// 输出 https://api.github.com/user/emails
@GET("/user/emails")
fun getData10(@Header("token") token: String): Call<ResponseBody>
}
4.11 @Headers 使用
-
@Header
:请求头注解,用于添加不固定请求头。
interface ApiService{
// 输出 https://api.github.com/user/emails
@Headers({"phone-type:android", "version:1.1.1"})
@GET("/user/emails")
fun getData11(): Call<ResponseBody>
}
4.12 @Streaming 使用
-
@Streaming
:表示响应体的数据用流的方式返回,使用于返回数据比较大,该注解在下载大文件时特别有用。
interface ApiService{
// 输出 https://api.github.com/gists/public
@Streaming
@POST("/gists/public")
fun getData12(): Call<ResponseBody>
}
4.13 @Multipart、@part 使用
-
@Multipart
:表示请求实体是一个支持文件上传的表单,需要配合 @Part 和 @PartMap 使用,适用于文件上传。 -
@part
:用于表单字段,适用于文件上传的情况,@Part 支持三种类型:RequestBody、MultipartBody.Part、任意类型。
interface ApiService{
// 输出 https://api.github.com/user/followers
@Multipart
@POST("/user/followers")
fun getData13(@Part("name") name: RequestBody, @Part file: MultipartBody.Part): Call<ResponseBody>
}
// 声明类型,这里是文字类型
val textType: MediaType? = "text/plain".toMediaTypeOrNull()
// 根据声明的类型创建 RequestBody,就是转化为 RequestBody 对象
val name = RequestBody.create(textType, "这里是你需要写入的文本:刘亦菲")
// 创建文件,这里演示图片上传
val file = File("文件路径")
if (!file.exists()) {
file.mkdir()
}
// 将文件转化为 RequestBody 对象
// 需要在表单中进行文件上传时,就需要使用该格式:multipart/form-data
val imgBody: RequestBody = RequestBody.create("image/png".toMediaTypeOrNull(), file)
// 将文件转化为 MultipartBody.Part
// 第一个参数:上传文件的 key;第二个参数:文件名;第三个参数:RequestBody 对象
val filePart = createFormData("file", file.getName(), imgBody)
val partDataCall: Call<ResponseBody> =
retrofit.create(ApiService::class.java).getData13(name, filePart)
4.14 @Multipart、@PartMap 使用
-
@PartMap
:用于多文件上传, 与 @FieldMap 和 @QueryMap 的使用类似。
interface ApiService{
// 输出 https://api.github.com/user/followers
@Multipart
@POST("/user/followers")
fun getData14(@PartMap map: Map<String, MultipartBody.Part>): Call<ResponseBody>
}
val file1 = File("文件路径")
val file2 = File("文件路径")
if (!file1.exists()) {
file1.mkdir()
}
if (!file2.exists()) {
file2.mkdir()
}
val requestBody1 = RequestBody.create("image/png".toMediaTypeOrNull(), file1);
val requestBody2 = RequestBody.create("image/png".toMediaTypeOrNull(), file2);
val filePart1 = MultipartBody.Part.createFormData("file1", file1.name, requestBody1);
val filePart2 = MultipartBody.Part.createFormData("file2", file2.name, requestBody2);
val mapPart = mutableMapOf<String, MultipartBody.Part>()
mapPart["file1"] = filePart1
mapPart["file2"] = filePart2
val partMapDataCall: Call<ResponseBody> =
retrofit.create(ApiService.class).getData14(mapPart)
5、Retrofit 源码解析
6、Retrofit 的实现与原理
- Retrofit 采用动态代理创建实现了 Service 接口的代理对象。当我们调用 Service 的方法时候会执行 InvocationHandler.invoke() 方法。在 invoke() 方法中,会调用 loadServiceMethod() 方法;
- 在 loadServiceMethod() 方法内部,如果缓存中已有 method,直接返回对应的 ServiceMethod 对象,否则会通过大量的反射对方法注解进行解析,生成对应的 ServiceMethod 对象并返回;
- 这里返回的 ServiceMethod 实际上是 SuspendForBody 对象,SuspendForBody 继承于 HttpServiceMethod,HttpServiceMethod 继承于 ServiceMethod,接着调用 ServiceMethod.invoke() 方法,会走到 HttpServiceMethod.invoke() 方法,在 invoke() 方法中,创建 OkHttpCall 对象,并进一步封装成 ExecutorCallbackCall 对象(默认),实际发起网络请求的就是 OkHttpCall 对象;
- OkHttpCall 发起网络请求,通过 ConverterFactory 将网络请求返回的 Response 数据解析成 Java 对象,并通过 MainThreadExecutor 将回调转发至主线程。
6、总结
- Retrofit 采用动态代理创建实现了 Service 接口的代理对象,当我们调用 Api 接口实例中的方法时,这些方法的处理逻辑都会通过动态代理 Proxy.newProxyInstance 转发给 invoke() 方法,在 invoke() 方法中会通过 loadServiceMethod() 方法注解的所有参数解析进 ServiceMethod 中,然后将 ServiceMethod 传入 OkhttpCall 中去,接着将 OkhttpCall 传给 ServiceMethod 中的 CallAdapter 的 adapt() 方法,把 OkhttpCall 适配成不同平台的网络请求执行器,就可以得到不同平台的网络请求执行器。在 Android 平台中,CallAdapter 实例的 adapt() 方法会把 OkhttpCall 适配成 ExecutorCallbackCall,当我们发起网络请求时,ExecutorCallbackCall 就会委托 OkhttpCall 发起网络请求,OkhttpCall 内部就是用得 Okhttp 的同步或者异步网络请求,当网络请求数据返回时,ExecutorCallbackCall 就会通过 MainThreadExecutor 把线程切换主线程执行回调,这里的回调其实就是将原本的 Okhttp 的回调对接到 Retrofit 本身的成功或者失败会调用。
- 以异步为例,Retrofit 通过 ExecutorCallbackCall 的 enqueue() 方法发起网络请求,最终会通过 OkhttpCall 的 enqueue() 方法来发起网络请求,OkhttpCall 的 enqueue() 方法中,首先会调用创建一个来自 Okhttp的Call 实例,然后通过这个 Okhttp 的 Call 实例的 enqueue() 方法来发起异步请求,当网络结果 Okhttp 的 Response 返回时,调用 parseResponse 方法解析 Response,parseResponse 方法里面还会调用 ServiceMethod 的 toResponse() 方法通过 Converter 实例的 convert() 方法把 ResponseBody 转化成我们想要的数据,不同的数据转化有不同的实现,在 Retrofit 的默认实现中,它就是直接返回 Okhttp 的 ResponseBody,最后把这个转化后的 body 和原始的 Okhttp 的 Response 一并封装成 Retrofit 的 Response 返回,最后把 parseResponse() 方法返回的 Response 通过 callback 回调出去,这时 ExecutorCallbackCall 收到回调,通过线程切换执行器 callbackExecutor,切换到主线程执行 callback 回调,一次异步请求就完成了,同步请求也是大同小异,只是少了个回调。