Retrofit 源码解析(2.9.0 版本)

1、简介

  • Retrofit 是基于 OkHttp 进行的封装;
  • 从功能上说,Retrofit 完成了网络请求接口的封装,网络请求返回数据的解析和线程的自动切换,解决了 OkHttp 使用时存在的一些问题;
  • 从代码结构上说,Retrofit 提供了很多注解,在我们配置网络请求时,简化了代码,且解耦。

OKHttp 使用时存在的问题:

  1. 网络请求的接口配置繁琐,需要配置请求头、请求体和请求参数;
  2. 网络请求返回的 Response 数据需要手动解析,且不能够复用;
  3. 无法自动进行线程的切换。

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 请求方法注解
请求方法注解说明
@GETget 请求
@POSTpost 请求
@PUTput 请求
@DELETEdelete 请求
@PATCHpatch 请求,该请求是对 put 请求的补充,用于更新局部资源
@HEADhead 请求
@OPTIONSoptions 请求
@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 回调,一次异步请求就完成了,同步请求也是大同小异,只是少了个回调。
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值