Retrofit 系列一:Retrofit 核心思想、源码初探

375 阅读9分钟

Retrofit 系列一:Retrofit 核心思想、源码初探

本文概述:

  • 文章以Retrofit 框架为主题,探究了其核心思想AOP ,介绍了OkHttp 设计缺陷、Retrofit 设计思路、Retrofit 整体使用流程;从源码角度深入分析了Retrofit 执行流程,ServiceMethod 存在价值等;

知识补充:

什么是切面编程:AOP

  • AOP 的生活例子:

    • 就向切好西瓜后,用勺子从切面处挖西瓜吃 ,切面为我们提供了统一的操作入口;
  • AOP 的应用场景:

    • 处理内存泄漏:

      • leakCanary
      • BlockCannary
      • Matrix
    • 处理生命周期:

    • OkHttp 设计思想:

      • 拦截器在实现拦截请求时,也是AOP 思想
    • Retrofit 核心思想:AOP

Retrofit 能干什么?

  • Retrofit 不具有网络请求功能

    • 不是网络请求模块,只是通过封装OkHttp 使得用户更简单使用

      • Retrofit 的中文意思就是改造(型)

      • 所有的网络请求都会给OkHttp

        • 内部存在通道(Retrofit ---> OkHttp)
        • 这个通道就是AOP 思想;

正文部分:

OkHttp 设计的缺陷

  • 朴素版OkHttp 网络请求流程

    • 实例化OkHttpClient 、Request;
    • 将Request 实例作为参数实例化Call
    • 调用Call 实例对象进行同/异步 请求;
  • OkHttp 存在什么问题?

    • 设计初衷:仅完成网络请求(根据高内聚、低耦合、单一职责)

    • 但其不算是好用:利用Retrofit 进行改型

      • 用户网络请求的接口配置繁琐,尤其是配置复杂请求body 、请求头、参数;

        • 存在许多样板代码 并且 这些复杂的请求参数不能简单复用
      • 数据解析困难:需要用户手动拿到响应体(responsebody )进行解析且不能复用

        • GSON 解析
      • 线程切换困难:无法适配自动进行线程切换

        • android 4.0 后就不支持在主线程中发起网络请求
      • 回调陷阱:存在嵌套的网络请求

        • 例如登录拿到Token,再请求网络拿到数据,再进行新的网络请求;

        • OkHttp 在上述场景的处理逻辑

           //OkHttp 回调陷阱
           发起第一次请求;
           拿到第一次请求结果(response){
               发起第二次网络请求;
               拿到第二次网络请求结果(response){
                   发起第三次网络请求;
                   拿到第三次网络请求结果(response){
                   }
               }
           }
          

Retrofit 的设计思路

  • 设计目的:通过封装OkHttp 使其更加好用

  • 需要解决什么问题:

    • 请求前:

      • 统一的网络请求头配置

        • Retrofit 就处理好了
      • 一致适配请求Request

        • 根本都不用去实例化Request、Call
    • 结果返回:

      • 数据适配

        • Retrofit 完成了GSON 解析等
      • 线程切换

        • Retrofit 完成了从网络请求的异步线程切换回主线程;
  • Retrofit 源码概览

    • 16 个类
    • 8 种设计模式

Retrofit 的整体使用

  • 简单使用过程:

    1. 导入依赖:
    1. 实例化Retrofit:需要配置
    2. 基于访问接口创建一个接口类型对象
    3. 基于接口类型对象实例化OkHttp Call
    4. 将请求Call 添加到OkHttp 请求队列

Retrofit 的使用疑问

  • 为什么搞出来的那个接口可以直接拿来用?

    • 接口是不能实例化对象的,需要有个接口实现类,用这个类去实例化对象

    • 定义的接口:

       public interface ICoinRankService{
           @GET("coin/rank/{page}/json")
           Observable<CoinRankBean> getCoinRankList(@Path("page") int page);
       }
      
    • 好像直接就使用了这个接口:这个参数到底传给了谁?

       ICoinRankService iCoinRankService = retrofit.create(
                   ICoinRankService.class);
       Observale<CoinRankBean> coinRankList = ICoinRankService
                   .getCoinRankList(1);
      
  • 参数到底传给了谁?

  • 具体的URL 到底是怎么形成的?

    • 我只传了一个域名进去,URL 是怎么出来的?
  • 为什么所有的请求都是同一个方式

Retrofit 源码阅读

应该怎么看源码:

  • 先看框架,对大体流程、整体思路有个了解;
  • 再进入具体的细节;

Retrofit.build() 是什么玩的:设计模式

  • 示意图:

    image-20220731224125486

  • 采用建造者设计模式与外观模式,对Retrofit 的类属性进行赋值;

    • 什么时候使用建造者设计模式?

      • 参数过多(大于5个 )且参数可选

        • 例如dialog 等;
      • 建造者与工厂是两回事

    • 这里面是没有工厂模式的

      • 设计模式是类之间的组织方式而非对象之间
    • 为什么这里是外观(门面 )设计模式

      • Retrofit 是网络请求与用户打交道的唯一途径,就像一个门面,我们通过这个门面就可以拿到里面的所有东西
      • 注意:统一接口呈现内容不一定是外观模式,可能是委托、代理、装饰器等模式;

Retrofit.create() 干了什么:AOP 思想

  • 业务逻辑:当实例化Retrofit 后,我们就可以拿这个对象去进行网络请求了

     //对接口进行“实例化”
     ISharedListService sharedListService = 
         retrofit.create(ISharedListService.class);
    
    • 什么样的东西才能为接口赋值?

      • 接口的实现类或者接口的子类
  • 返回了Proxy.newProxyInstance(

    • 这是动态代理,是Retrofit 的精华之所在

      • 程序运行到这个地方时,产生了一个类(实现了这个接口) ---> 所以为什么能对接口对象赋值
    • 为什么说是代理?

      • 这个类会去实现接口所指代的所有任务(接口中的函数)

        • 例如接口中有两个方法 ---> 动态生成的这个类会去实现这两个方法;
      • 当使用接口对象去调用接口的方法 ---> 调用到动态生成类的对象方法 ---> 将网络请求交给Handler 中的invoke 去实现而这个invoke 函数就是Retrofit.create 中的invoke 函数

        image-20220731230154071

      • 意思就是,所有接口网络请求都会进入统一的Retrofit.create 中的invoke 中去;

        • 这就是AOP !!!
        • 这就是典型的切面编程;

AOP 思想有什么好处?

  • 回归Retrofit 的本质:封装OkHttp

    • 我们可以将所有的网络请求封装为OkHttp

    image-20220731232617577

  • 这样封装解决了什么问题:OkHttp 设计缺陷得到完善

    • 既然都会经过invoke,请求适配、数据解析、线程切换等都可以在统一的invoke处进行;

      • 在切面处完成同一操作
    • 但有个限制:网络请求需要在同一域名(baseUrl )下

      • 域名相同 ---> 请求头相同;
      • 我们只需要修改后面的参数(进入不同的二级页面 )
  • AOP 的实现原理:动态代理

    • 程序运行到此处会自动生成(动态)既定网络接口的实现类,并实现其中所有方法(代理);
    • 这个类的invoke 方法就是切面,所有的网络请求都走到这个里面了

invoke 是如何完成Retrofit ---> OkHttp:ServiceMethod

  • Retrofit 能不能封装成单例?

    • 单例意味着什么:生命周期长

      • 单例会一直存在,这可能导致内存泄漏

        • 因为在Retrofit.build() 中会保存许多参数,这些都是会占据内存的;
        • 如果要封装成单例,那么在源码中的build() 方法内就做了
    • 业务场景:Retrofit 使用很频繁能不能将其搞成单例?

      • 这个时候,我们可能会想将其封装成单例;这个时候就不用Retrofit 了,那么我们就使用Socket长连接(Netty )

        • 比如说股票APP 需要一直推送消息;

ServiceMethod 的存在价值

  • 网络接口上存在许多注解

    • 代码展示:

       public interface ICoinRankService{
           @GET("coin/rank/{page}/json")
           Observable<CoinRankBean> getCoinRankList(@Path("page") int page);
       }
      
    • 这些注解是干什么的,怎么用的?

      • ServiceMethod 可以通过反射拿到这些注解,作为invoke 参数

         @Override 
         public Object invoke(Object proxy, Method method, Object... args)
        
  • ServiceMethod 干了什么?

    • 源码示意:
     ServiceMethod serviceMethod = loadServiceMethod(method);
    
    • 使用建造者设计模式配置了一系列参数,拿到了一系列注解

    • 回到Retrofit.create().invoke()

       //这个Method 是invoke() 的形参,这个是从网络接口的方法中来的 
       ServiceMethod serviceMethod = loadServiceMethod(method);
      
    • 现在的情况是:

      • 已经拿到了Retrofit 实例

      • 已经拿到了网络接口对象

        • 网络接口在定义时,指定了参数、注解;
      • 我想要的是得到一个Call ---> 将网络接口转变为请求Call

        • 其实网络接口的注解就是请求Call 的参数
        • 那么,ServiceMethod 要做的就是去适配Call
    • ServiceMethod 中有什么:ServiceMethod.Build()

      • 适配器:去适配Call

         callAdapter = createCallAdapter();
        
      • 响应类型:接收Response

         responseType = callAdapter.responseType();
        
      • 响应转换器(Response 转成 JavaBean )

         responseConverter = createResponseConverter();
        
      • 读取注解:for 循环

         for (Annotation annotation : methodAnnotations) {
             parseMethodAnnotation(annotation);
         }
        
      • 全部弄好了,保存在ServiceMethod 中去

        image-20220801001015998

    • 每一个网络接口对应着各自的ServiceMethod

      • 用于保存其请求的细节:解析所有的请求、注解、返回值
    • 解析了有什么用?

      • 解析好了就开始用:实例化OkHttpCall
       OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
      
    • 返回

       return serviceMethod.callAdapter.adapt(okHttpCall);
      
  • Retrofit 与 ServiceMethod

    • Retrofit 负责的是整个网络参数的保存,而ServiceMethod 负责的是具体的某一个网络接口对应的请求

      • 后者去前者中找自己需要的;
      • 前者就是菜市场,后者就是买菜的;

    Retrofit1

  • 注解可不是APT 技术

    • APT 是基于注解生成代码,类似于DataBinding等
  • serviceMethod.callAdapter

    • 最终执行ExecutorCallAdapterFactory.adapt

      • 返回ExecutorCallbackCall ,这个就是我们构建的Call
    • 是什么:ServiceMethod 的类属性

       final class ServiceMethod<T> {
           final CallAdapter<?> callAdapter;
      
    • 怎么初始化:createCallAdapter

       private CallAdapter<?> createCallAdapter() {
           return retrofit.callAdapter(returnType, annotations);
      
    • 进一步分析:retrofit.callAdapter

       public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
           return nextCallAdapter(null, returnType, annotations);
       }
      
    • 再进去:Retrofit.nextCallAdapter

       CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
      
      • 从adapterFactories 中来的
    • Factories 在哪里赋值的:Retrofit.build()

       List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
       adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
      
    • platform.defaultCallAdapterFactory 是什么东西

       //此处为调度终点
       CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
           if (callbackExecutor != null) {
               return new ExecutorCallAdapterFactory(callbackExecutor);
           }
           return DefaultCallAdapterFactory.INSTANCE;
       }
      
      • 所以Retrofit.build() 中的Adapter 就是ExecutorCallAdapterFactory.get() 拿到的Adapter :就是一个内部类

         @Override
         public CallAdapter<Call<?>> get() {
         ​
             //就是这个
             return new CallAdapter<Call<?>>() {
                 @Override public Type responseType() {
                     return responseType;
                 }
                 
             //最终执行这个adapt 
                 @Override public <R> Call<R> adapt(Call<R> call) {
                     return new ExecutorCallbackCall<>(callbackExecutor, call);
                 }
             };
         }
        
  • 整体来说:

    • 所有网络请求都会走到invoke;

    • invoke 走到ServiceMethod

    • ServiceMethod 最终会返回一个Call

       return new ExecutorCallbackCall<>(callbackExecutor, call);
      
  • ExecutorCallbackCall 与 Call 的区别

    • 前者是后者的实现类,所以前者对象可以赋值给Call

怎么将Call 放入OkHttp 的请求队列:enqueue 方法

  • 注意执行的是那个方法:ExecutorCallbackCall.enqueue

    • 整体来说:

      • 所有网络请求都会走到invoke;

      • invoke 走到ServiceMethod

      • ServiceMethod 最终会返回一个Call

         return new ExecutorCallbackCall<>(callbackExecutor, call);
        
  • 执行的逻辑:

     @Override public void enqueue(final Callback<T> callback) {
         //
         delegate.enqueue(new Callback<T>() {
               });
             }
    
    • delegate 是什么:ExecutorCallbackCall 的类属性

       static final class ExecutorCallbackCall<T> implements Call<T> {
           final Call<T> delegate;
      
    • 里面的泛型T 指的是什么:OkHttpCall

       //Retrofit.create().invoke()
       ServiceMethod serviceMethod = loadServiceMethod(method);
       OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
       return serviceMethod.callAdapter.adapt(okHttpCall);
      
    • 也就是说此时的enqueue 操作是交给OkHttpCall 去执行的

      • Retrofit 是对OkHttp 封装的有力证明
      • OkHttpCall 实现了Call (Retrofit 的接口)
  • enqueue 的执行逻辑

    • 创建真正的Call :createRawCall

       okhttp3.Call call;
       call = rawCall = createRawCall();
      
    • createRawCall 的执行逻辑

       //将之前保存在ServiceMethod 中解析好的参数拿出来,构建出Request 
       Request request = serviceMethod.toRequest(args);
       //构建OkHttp 的Call 
       okhttp3.Call call = serviceMethod.callFactory.newCall(request);
      
    • 得到的Call 入队列:

       okhttp3.Call call = serviceMethod.callFactory.newCall(request);
      
  • Retrofit 的调度流程总结

    • 创建Retrofit

    • 使用动态代理为网络接口创建代理类

    • 访问代理类的ISharedListService 函数,将其转接到Retrofit.create().invoke() 中;

    • 在invoke 中创建ServiceMethod (通过反射拿到网络接口的注解,将其作为参数) 同时 构造ExecutorCallbackCall ,并将其赋值给sharedListCall

      • 注意ExecutorCallbackCall 并不是真正的网络请求,内部是调用createRawCall 将其封装成OkHttpCall ,然后用OkHttpCall 去完成真正的网络请求入队列操作;

下集预告:

  • Retrofit 源码分析、动态代理原理、Retrofit 设计模式详解