讨论Retrofit存在的意义以及源码导读

297 阅读11分钟

1.前言

本文核心是通过对比OkHttp和Retrofit的使用方式不同。聊聊Retrofit存在的意义,已经有okhttp的情况下,为什么又在此基础上发展出了Retrofit.

将一次网络请求划分为三步

  1. 请求前
    1. 确定接口地址,收集业务参数,请求头等,即创建request
  2. 请求中
    1. 客户端与服务端建立http连接,发送request
    2. okhttp替开发人员完成了绝大部分工作,无须关注客户端与服务器之间是如何建立连接的
  3. 请求后
    1. 接收response,处理数据

请求前,请求后是开发人员可以自主处理的。本文将按照上述思路去分析网络请求的使用

后续的源码分析,没有使用罗列大量代码的方式去写文,想通过文章去看懂源码是不现实的,关键点引导阅读的方式可能会更有效一点,输出一些自己的理解,帮助大家阅读源码。最终还是要自己去看的。 源码分析基于retrofit:2.9.0

2.okhttp的使用

如下代码展示使用okhttp完成一次同步的网络请求,向服务器传递json参数。大致流程

  1. 使用线程池切换到子线程
  2. json序列化参数
  3. 收集业务参数,创建request
  4. 使用OkHttp发起请求
  5. 获取response,Json反序列化
  6. 处理成功和错误逻辑,切换主线程,传递数据
  7. UI逻辑处理
  8. 注:使用 异步方法可以省去线程管理 call.enqueue(callback)

可以看出流程还是很繁琐的,每一个流程点都需要开发人员手动编写逻辑。

而且如下代码是很原始,朴素的,不可能用在商业项目中。可以预见的是,如果在项目中只使用OkHttp,需要优秀的封装才可以满足日常开发需求。

然而一个优秀网络模块封装对开发人员要求是很高的,所以出现了retrofit

private val fixedThreadPool = Executors.newFixedThreadPool(2)

		/**
     * 发起网络请求,向服务器传递json参数
     */
    private fun okhttpJson() {
        fixedThreadPool.execute(Runnable() {
						//请求前
            val gson = Gson()
            val user1 = UserEntity(1, "小明")
            val jsonParams = gson.toJson(user1)
            val requestBody =
                RequestBody.create(MediaType.parse("application/json;charset=utf-8"), jsonParams)
            val request = Request.Builder()
                .url("xxx")
                .post(requestBody)
                .build()
						//请求中
            val httpClient = OkHttpClient()
            val call = httpClient.newCall(request)
            val response = call.execute()
            //请求后
						if (response.isSuccessful) {
                val responseBody = response.body()
                val jsonData = responseBody?.string()
                val httpEntity = gson.fromJson(jsonData, HttpEntity::class.java)
                if (httpEntity.code == 200) {
                    val data = httpEntity.data
                    //  请求正常
                    uiCallback(200,"请求成功",data)
                } else {
                    val code = httpEntity.code
                    val message = httpEntity.msg
                    //  业务错误
                    uiCallback(code,message,null)
                }
            } else {
                val code = response.code()
                val message = response.message()
                // 网络错误
                uiCallback(code,message,null)
            }
        })
    }

    /**
     * 回调函数 切换主线程
     */
    private fun uiCallback(code:Int,msg:String,data:Any?){
        //  仍然在子线程,可以进行其他逻辑操作
        runOnUiThread(Runnable(){
            //  UI操作
        })
    }

3.Retrofit的使用

Retrofit实现一次网络请求流程如下

  1. 创建接口ApiService ,声明方法,通过注解标记接口地址,参数等信息
  2. 创建Retrofit对象
  3. 通过Retrofit生成ApiService 的实现类
  4. 调用方法,发起网络请求
  5. 获取response,处理成功和错误逻辑
  6. 切换主线程,传递数据
  7. UI逻辑处理
  8. 注:依然使用同步调用发起网络请求
interface ApiService {

    @GET("article/list/{page}/json")
    fun queryArticleList(
        @Path("page") page: Int,
        @Query("page_size") size: Int
    ): Call<HttpEntity<PageEntity<ArticleEntity>>>

    @POST("xxx")
    fun register(@Body userEntity: UserEntity) : Call<HttpEntity<Any>>
}
private val fixedThreadPool = Executors.newFixedThreadPool(2)

private fun createRetrofit() {
        fixedThreadPool.execute(Runnable() {
            val retrofit = Retrofit.Builder()
                .baseUrl("https://www.wanandroid.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
            val apiService = retrofit.create(ApiService::class.java)
            val call = apiService.queryArticleList(page = 0, size = 10)
            val response = call.execute()
            if (response.isSuccessful) {
                val httpEntity: HttpEntity<PageEntity<ArticleEntity>>? = response.body()
                httpEntity?.let {
                    val code = it.code
                    val msg = it.msg
                    val page = it.data
                    uiCallback(code, msg, page)
                }
            } else {
                val code = response.code()
                val msg = response.message()
                uiCallback(code, msg, null)
            }
        })
    }

    /**
     * 回调函数 切换主线程
     */
    private fun uiCallback(code: Int, msg: String, data: PageEntity<ArticleEntity>?) {
        // 仍然在子线程,可以进行其他逻辑操作
        runOnUiThread(Runnable() {
            // UI操作
        })
    }

4.对比

在分别实现了OkHttp与Retrofit发起一次网络请求后,通过对比代码

  1. 请求前
    1. Retrofit把创建Request这一过程,单独拆分放到接口类。利用注解把调用接口需要的数据和抽象方法的声明结合起来,Retrofit帮助开发人员创建Request,结构清晰明了。OKHttp需要编写相对复杂的代码,创建Request,RequestBody对象
    2. 由于的Converter的存在,Retrofit会自动对数据进行序列化操作,无需开发人员处理。OkHttp需要自行引入Gson等Json序列化组件,自行实现数据序列化操作
  2. 请求后
    1. 返回结果自动序列化完成,无需开发人员处理
    2. 目前 后续数据处理与OkHttp基本一致,(可实现 CallAdapter ,结合rxjava使用,对请求后数据处理流程进行优化)

4.小结

  1. 个人感觉Retrofit最大的意义是建立规范
    1. 利用接口和注解,规范,约束开发人员如何创建request,发起一次请求
    2. 利用Converter 规范数据序列化方式
    3. 利用CallAdapter 规范请求后返回类型,如何处理数据
  2. 举个例子,如果使用OkHttp,仅仅创建request这一个步骤。不同项目,不同开发人员,对于同一种情况可能写出百花齐放的代码(永远不要小看人类的想象力呀! 🐶)
    1. 使用Retrofit之后,无论谁写代码,都被规范为一种统一格式:接口与注解。
    2. 分分钟上手的好嘛,只要用过Retrofit的开发人员就不需要额外讲解这东西怎么用,降低开发成本
  3. ConverterCallAdapter 也是同理,并且更加是天才构思!客户端需要与多个服务器进行交互也是常见的需求。服务器A返回数据是JSON格式,服务器B返回数据是XML格式。当出现这种情况,使用Retrofit只需要,创建两个Retrofit对象,一个添加JsonConverter 另一个添加XmlConverter即可。 如果使用OKHttp自行封装,可要了命了

5.分析原理前的思考

在看retrofit源码的时候 或者 任何一个框架的源码时 都要牢记一点,

作者设计这个框架的根本目的是什么!!!

甭管是谁,写代码肯定是为了决解问题或者达到一个目的。把握或者揣测框架作者的初心很重要,写代码肯定会遵循一定的逻辑,作者的初心就是逻辑的起点。 有起点有目的可能会走一些弯路,但不会太偏,脑子没一点思路 直接就翻源码的感觉 大家应该都经历过 🐶Doge

retrofit是基于okhttp的二次封装,虽然经过封装后api的使用已经大变样,但是核心不会变,最终还是通过okhttp的api发起网络请求。

也就是说retrofit的源码中,依然存在如okhttp的使用 一节中的代码原始代码。

很容易就会想到一些问题

  1. retrofit怎么变成okhttp的
  2. retrofit的注解怎么转化为okhttp的request
  3. 接口是怎么实例化成为一个可调用对象的
  4. ConverterCallAdapter 是如何工作的

6.源码分析

很头疼的一节,通过描述阅读思路的方式 提供说明引导。 源码还是得自己读!

val retrofit = Retrofit.Builder()
                .baseUrl("https://www.wanandroid.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
val apiService = retrofit.create(ApiService::class.java)
val call = apiService.queryArticleList(page = 0, size = 10)
val response = call.execute()
  1. val response = call.execute() 从执行网络请求入手,想看execute方法内部是如何实现的
  2. 点击进入源码发现是 retrofit2.Call 接口,不能直接看现实,那么就查找 call对象是如何生成的,找到实现类 自然就可以看实现了
  3. apiService.queryArticleList(page = 0, size = 10) 方法返回call对象,但是apiService 也是接口,咱开发人员自己定义,也是看不到具体实现。 那么就需要看ApiService对象是如何创建的
  4. retrofit.create(ApiService::class.*java*) create()中包含两处逻辑
    1. validateServiceInterface(service);
      1. 非核心流程,对class参数进行校验,判断class类型是否接口,不允许是泛型类,通过循环遍历class继承的所有接口 是否为泛型类
    2. Proxy.*newProxyInstance*
      1. 核心流程,java动态代理,在运行时根据接口class 动态创建 接口的实现类,返回咱们要找的ApiService
      2. 我认为retrofit使用动态代理的主要目的是为了动态创建对象,并不是常规意义上AOP的概念,对某一个功能进行增强。源码中也不存在 代理模式中真实对象的概念
      3. 通过动态代理,创建接口的代理对象,每调用一个方法 最终都会调用到InvocationHandler.invoke(Object proxy, Method method, @Nullable Object[] args) invoke方法有三个参数 分别是:当前代理对象,反射对象Method,方法参数值。
      4. 在这种情境下的方法 方法的作用更像一个javaBean,携带完成一次网络请求需要的所有信息
      5. 利用Method对象,可以拿到关于这个方法的一切信息,比如:返回值类型,方法名,方法注解,参数类型,参数注解等等。所以可以看出,invoke方法接收的是 当前调用方法的信息。开发者利用这些信息,实现自己的业务
      6. 在看看在接口中定义的对网络请求方法的定义。 可以想象到 Retrofit分析方法注解,参数注解,组装成OkHttp发起请求需要的request。 分析方法返回值类型,在接收网络请求数据时进行解析封装
      		@GET("article/list/{page}/json")
          fun queryArticleList(@Path("page") page: Int,@Query("page_size") size: Int
          ): Call<HttpEntity<PageEntity<ArticleEntity>>>
      
          @POST("xxx/aaa")
          fun register(@Body userEntity: UserEntity) : Observable<HttpEntity<Any>>
      
  5. 上面讨论了动态代理 和 InvocationHandler.invoke() 的作用和意义。视线拉回来,查看继续走源码流程,查看 retrofit.create() 方法源码的原因是 希望找到 ApiService对象是如何创建的,retrofit2.Call 接口的实现类,call.execute() 方法是如何实现的
    1. 核心方法 return ..省略.. loadServiceMethod(method).invoke(args);
    2. 已经清楚ApiService对象 是通过动态代理创建,所有的接口方法调用最终会走到InvocationHandler.invoke() 。那么invoke()方法的返回值 就是方法声明的返回值。
    3. 上述示例中的Call<HttpEntity<PageEntity<ArticleEntity>>>Observable<HttpEntity<Any>>
  6. 进入invoke方法, ServiceMethod.invoke() 抽象类,抽象方法 没有实现 有个静态方法ServiceMethod.parseAnnotations() 看方法名的作用是注解解析
  7. 因为无法直接查看 ServiceMethod.invoke() 所以要去查看上一步,找到ServiceMethod 的实现类。即查看loadServiceMethod()
  8. loadServiceMethod() 实现了map缓存, 如果缓存中没有,则解析对method对象继续宁解析,逻辑在静态方法ServiceMethod.parseAnnotations();步骤6提过
  9. ServiceMethod.parseAnnotations(); 主要做了三件事
    1. 创建RequestFactory 对象,看名字也可以推断出类的作用,用来创建request对象
      1. 其中需要关注 create(Object[] args) 方法返回值类型是okhttp3.Request
      2. RequestFactory.Builder 中 包含 一系列***parseXXX()***方法,解析注解获取数据,最终组装成okhttp3.Request 与之前的分析猜想几乎一致
    2. 校验返回值类型
    3. 调用 return HttpServiceMethod.*parseAnnotations()* 因为直接return 所以这个方法一定返回 ServiceMethod 抽象类的子类对象。 所以其中一定包含invoke 方法的实现 。
  10. 进入HttpServiceMethod 发现它还是一个抽象类,表明它还会有子类实现。现在有两种思路
    1. 直接找invoke 方法
    2. 先解读HttpServiceMethod.*parseAnnotations()*
    3. 我打算先看invoke 方法
  11. invoke 方法做了两件事
    1. 创建OkHttpCall 对象。OkHttpCall 实现了retrofit2.Call 接口。
      1. 这里需要注意!!! 回到最初的步骤1,2。想看val response = call.execute() 是如何实现的,进入源码发现是类型为retrofit2.Call 的接口
      2. 经过不断的跟踪主流程,终于发现了实现类,喜大普奔
    2. 调用抽象方法adapt()
    3. 所以现在又有两种选择,
      1. 直接看OkHttpCall 的实现
      2. 回过头去HttpServiceMethod.*parseAnnotations() 方法*。找HttpServiceMethod 的子类是如何实现adapt()
      3. 我选择去看HttpServiceMethod.*parseAnnotations() 直接去看*OkHttpCall 的实现,不能算作是跟着源码流程走,如果想看实现通过AndroidStudio也能看,还是跟着流程走比较好
  12. 进入HttpServiceMethod.*parseAnnotations() 方法 关键点如下*
    1. 变量 boolean isKotlinSuspendFunction 是否为kotlin的挂起函数,走不同的逻辑
    2. createCallAdapter() 发现一个熟悉的身影,获取callAdapter的位置被咱们找到了,让retrofit支持RxJava就是通过CallAdapter实现的。
      1. retrofit内部维护CallAdapter实现类的List容器,初始化Retrofit对象时通过Retrofit.Builder().addCallAdapterFactory() 添加自定义的CallAdapter,就是添加到内部的List容器中
      2. 当程序运行到*createCallAdapter()* 时,会比对返回值类型,遍历List,找到匹配的类型,这也是为什么Retrofit能够支持多种返回类型的原因
      3. retrofit提供了默认的CallAdapter实现,在Retrofit.Builder().build() 方法中调用platform.defaultCallAdapterFactories() 获取默认实现。类名是DefaultCallAdapterFactory
      4. 所以*createCallAdapter()* 方法返回对象的真是类型就是DefaultCallAdapterFactory
    3. createResponseConverter() 又一个熟悉的身影 用来获取Converter ,逻辑于*createCallAdapter()* 一致不过多解释
    4. 最末return语句,通过if-else语句区分是否为kotlin携程的情况返回三种类型的HttpServiceMethod 子类实现
      1. CallAdapted
      2. SuspendForResponse
      3. SuspendForBody
      4. !!!!!上述三个类都继承自HttpServiceMethod 实现adapt() 抽象方法
  13. HttpServiceMethod.adapt(Call<ResponseT> call, Object[] args) 有两个参数
    1. Call<ResponseT> call步骤11提到的 OkHttpCall 也是步骤1最初咱们要查看的val response = call.execute() 的实现类
      1. 进入OkHttpCall.execute() 内部调用了 okhttp3.Call.execute()
    2. Object[] args 追述到遥远的步骤4 动态代理部分,传入的方法参数

7.源码绘图总结

微信截图_20220322232652.png