美文网首页NetAndroid技术知识Android开发
「拆轮子」Retrofit 整体架构、细节和思考

「拆轮子」Retrofit 整体架构、细节和思考

作者: FeelsChaotic | 来源:发表于2018-01-27 19:44 被阅读101次

    阅读源码基本套路:WHW
    What:能做哪些事?提供了什么功能?
    How:采用什么方式实现的?由哪些模块组成?
    Why:为什么有这样的需求?模块这样封装的意图是什么?还有没有更好的方式?

    前言

    在业务常用框架中,Retrofit 算是代码量较少,难度较低的开源项目。虽然代码不多,但依然用到了大量的设计模式,具有非常好的扩展性。

    解析 Retrofit 源码的优秀文章不少,本文不再赘述,抓住易曲解的概念、整体架构和难理解的细节来做分析。

    明确概念

    一个简单的网络请求调用流程如下:

    //1. 新建一个retrofit对象
    Retrofit retrofit=new Retrofit.Builder()
    .baseUrl(url)
    .addConverterFactory(GsonConverterFactory.create())
    .build();
    ...
    
    //2. 用retrofit加工出对应的接口实例对象
    ApiService mApiService= retrofit.create(ApiService.class);
    
    //3. 调用接口函数,获得网络工作对象
    Call<User> callWorker= mApiService.getUserInfo();
    
    //4. 网络请求入队
    callWorker.enqueue(new Callback<User>() {
                @Override
                public void onResponse(Call<BizEntity> call, Response<User> response) {...}
                @Override
                public void onFailure(Call<BizEntity> call, Throwable t) {...}
            });
    
    

    我们从上面的应用场景可以看出,Retrofit的作用是按照接口去定制Call网络工作对象,也就是说:Retrofit并不直接做网络请求,只是生成一个能做网络请求的对象

    Retrofit在网络请求中的作用大概可以这样理解:


    我们看到,从一开始,Retrofit要提供的就是个Call工作对象。
    换句话说,对于给Retrofit提供的那个接口

    public interface ApiService {
        @POST("url")
        Call<User> getUserInfo();
    }
    

    这个接口并不是传统意义上的网络请求接口,这个接口不是用来获取数据的接口,而是用来生产对象的接口,这个接口相当于一个工厂,接口中每个函数的返回值不是网络数据,而是一个能进行网络请求的工作对象,我们要先调用函数获得工作对象,再用这个工作对象去请求网络数据。

    所以Retrofit的实用价值意义在于,他能根据你的接口定义,灵活地生成对应的网络工作对象,然后你再择机去调用这个对象访问网络。

    代码结构

    说白了,Retrofit就做了三件事:

    1. 根据注解解析 ApiService 中每个方法的参数,url,请求类型等信息,组装为 ServiceMethod 类。
    2. 根据 ServiceMethod 信息 new 请求对象OkHttpCall(内部持有真正的请求对象 okhttp3.Call 的引用,相当于包了一层)。
    3. 用适合的适配器 callbackExecutor 转换网络请求对象 Call 为我们声明的接口返回类型(如 Call<R> 到 Observable<R>),用适合的转换器 Converter 转换默认的Response为声明的接口返回值(如 Call<ResponseBody> 到 Call<UserInfo>),返回请求对象。

    以上加粗的关键类,我们可以详细看下类结构是怎么样的。

    可以看到,Retrofit 自身的结构很简单,代码量也不是很大。红色框部分是http包,代表的是所有的Annotation层。 通过这些注解类,把方法里声明的注解一一映射到构造 OkHttp 请求对象所需要的Request参数。

    几个主要类的UML简图:

    1. Retrofit 和 ServiceMethod

    Retrofit 和 ServiceMethod 使用了 Builder模式(省略了 Director 和Abstract Product 的 Builder模式)来构建自己,Retrofit 的作用很简单,传入需要的参数,构建一个 Retrofit 对象,然后通过动态代理的方式,得到我们自定义的方法接口的实例,参数中除了baseUrl 之外,其他都是可选的,如果没设置会使用默认值。

    ServiceMethod 作用就是解析Annotation,同时提供方法生成网络请求需要的Request和解析请求结果Response。

    2. CallAdapter 和 CallAdapter.Factory

    CallAdapt 的作用是把 Call 转变成你想要返回的对象,起作用的是 adapt 方法,CallAdapter.Factory 的作用是获取 CallAdapter 。ExecutorCallAdapterFactory 的 CallAdapter会将回调方法放到主线程中执行,能够接受的返回值类型为Call<T>。很明显,RxJavaCallAdapterFactory的CallAdapter能够接受的返回值是Oservable<T>。如果想让方法直接返回一个对象,可以自定义一个CallAdapter.Factory。

    3. Converter和Converter.Factory

    Converter 的作用是将网络请求结果 ResponseBody 转换为我们希望的返回值类型。Converter.Factory 的作用是获取 Converter,这里很明显采用了静态工厂模式。

    4. OkHttpCall

    OkHttpCall 继承自 interface Call,主要的作用是调起执行网络请求以及返回当前请求状态状态,但是真正的网络请求其实在okhttp3.Call接口,接口定义如下:

    这个接口的实现类是 okhttp3.RealCall,可以发现,Retrofit 的Call 接口和 okhttp3 的 Call 接口定义几乎是完全一样的,这样做的好处显而易见:利于扩展,解耦。

    5. RxJavaCallAdapterFactory

    CallAdapterFactory的作用及工作机理前面已经介绍过了,RxJavaCallAdapterFactory的作用也是一样的,只不过RxJavaCallAdapterFactory中内部又定义了三种CallAdapter:ResponseCallAdapter、ResultCallAdapter和SimpleCallAdapter,根据返回值类型决定到底使用哪个,代码如下:


    细节点

    以上流程中,有很多细节可以详细梳理下。

    1. 如何将 ApiService 接口转换为网络请求?
    ApiService mApiService= retrofit.create(ApiService.class);
    Call<User> callWorker= mApiService.getUserInfo();
    

    看到以上代码,我们不禁提出疑问,这里的mApiService是什么类型?为什么可以直接调用接口方法?create做了什么?生成接口实现类吗?

    我们 debug 到 retrofit.create() 中一看究竟:

    public <T> T create(final Class<T> service) {
        // 检查传入的类是否为接口并且没有继承其他接口
        Utils.validateServiceInterface(service);
        // 预加载开关,默认关,
        if (validateEagerly) {
          eagerlyValidateMethods(service);
        }
        // 重点是这里!
        // 首先会返回一个利用代理实现的 ApiService 对象
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
              private final Platform platform = Platform.get();
    
              // 我们调用该对象的每一个方法时都会进入到invoke方法里
              @Override public Object invoke(Object proxy, Method method, Object... args)
                  throws Throwable {
               
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                // 解析当前调用的方法
                ServiceMethod serviceMethod = loadServiceMethod(method);
                // 将刚刚解析完毕包装后的具体方法封装成 OkHttpCall ,你可以在该实现类找到 okhttp 请求所需要的参数
                OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
                // 将以上我们封装好的 call 返回给上层,这个时候我们就可以拿到 call,执行请求。
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    

    返回值就是我们自定义的接口实例对象T。由于T的所有方法都是抽象方法,当调用T的方法时,会被 InvocationHandler 拦截,真正的调用会转到 InvocationHandler 的 invoke() 方法中,其中 method 参数就是我们自己的抽象方法。

    invoke() 方法中,基于我们的接口方法构造了一个 ServiceMethod,构建过程中对方法中的各种注解做了解析。创建了一个 OkHttpCall 对象,这个对象将会在被adapt之后返回给客户端,类型取决于客户端的方法返回类型和设置的 CallAdapter。这里的代码其实不是很好,OkHttpCall 和 ServiceMethod 有互相引用的感觉,其实本意只是将 OkHttpCall 转换成客户端需要的返回值,那么 CallAdapter 对象是否有必要放在 ServiceMethod,我觉得可以再仔细斟酌一下。

    注:此处有个预加载开关 validateEagerly ,开启后将会在调用create时就先去解析ApiService中每个方法,并且add serviceMethodCache缓存里。等到调用方法时,无需再解析,就可以直接在缓存里取解析对象了。

    2. 谁去进行网络请求?我们的回调是怎么回到主线程的呢?

    拿到返回的Call对象,我们可以执行网络请求了。

    //4. 执行网络请求
    callWorker.enqueue(new Callback<User>() {
                @Override
                public void onResponse(Call<BizEntity> call, Response<User> response) {...}
                @Override
                public void onFailure(Call<BizEntity> call, Throwable t) {...}
            });
    

    调用call.enqueue,内部实际会调用ExecutorCallAdapter的enqueue方法。

    static final class ExecutorCallbackCall<T> implements Call<T> {
      final Executor callbackExecutor;
      final Call<T> delegate;
    
      ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
        this.callbackExecutor = callbackExecutor;
        this.delegate = delegate;
      }
    
      @Override 
      public void enqueue(final Callback<T> callback) {
        if (callback == null) throw new NullPointerException("callback == null");
        // 重点关注!
        delegate.enqueue(new Callback<T>() {
          @Override 
          public void onResponse(Call<T> call, final Response<T> response) {
            callbackExecutor.execute(new Runnable() {
              @Override 
              public void run() {
                if (delegate.isCanceled()) {
                  // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                  callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                } else {
                  callback.onResponse(ExecutorCallbackCall.this, response);
                }
              }
            });
          }
    
          // 省略
    }
    

    这里的 delegate 对应的就是 okhttp 的 call ,我们注意到 response 的回调由callbackExecutor.execute() 来执行。一步步追踪 callbackExecutor 来源,Retrofit 的 build() 方法里:

    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
      callbackExecutor = platform.defaultCallbackExecutor();
    }
    

    平台默认的回调调度器

    static class Android extends Platform {
      @Override 
      public Executor defaultCallbackExecutor() {
        return new MainThreadExecutor();
      }
    
      @Override 
      CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
        return new ExecutorCallAdapterFactory(callbackExecutor);
      }
    
      static class MainThreadExecutor implements Executor {
        private final Handler handler = new Handler(Looper.getMainLooper());
    
        @Override 
        public void execute(Runnable r) {
          handler.post(r);
        }
      }
    }
    

    我们发现,Android 默认的调度器是主线程的 Handler ,execute()方法也只是 mainHandler.post() 。所以 enqueue() 中 onResponse 方法调用 defaultCallbackExecutor.execute 方法,实际上就是使用主线程 Handler.post(runnable) 从而实现线程切换操作。

    3. 添加多个转换器和适配器时,内部优先逻辑是什么?
    Retrofit mRetrofit = new Retrofit.Builder()
                    .client(mClient)
                    .baseUrl(mBaseUrl)
                    .addConverterFactory(FastJsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
    

    addConverterFactory 扩展的是对返回的数据类型的自动转换,addCallAdapterFactory 扩展的是对网络工作对象 callWorker 的自动转换。就算我们不添加 CallAdapterFactory 也是能实现适配转换的,原因在于 Retrofit 的 build 函数里,会添加默认的 CallAdapterFactory。

    public Retrofit build() {
          ...
          okhttp3.Call.Factory callFactory = this.callFactory;
          if (callFactory == null) {
            callFactory = new OkHttpClient();//使用OkHttpClient处理网络请求
          }
          ...
          //根据当前运行平台,设置默认的callAdapterFactory
          adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
          ...
          return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
              callbackExecutor, validateEagerly);
        }
    

    addConverterFactory 和 addCallAdapterFactory 都是把工厂对象添加到各自数组里保存

    public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
          Annotation[] annotations) {
        // ···
        int start = adapterFactories.indexOf(skipPast) + 1;
        for (int i = start, count = adapterFactories.size(); i < count; i++) {
          CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
          if (adapter != null) {
            return adapter;
          }
        }
        // ···
      
    

    当需要转换或适配时,就循环数组,调用每个工厂对象的方法去尝试转换,转换成功,就表明合适。其实这也是责任链的另一种表现形式。

    思考

    总体来说,Retrofit 在类的单一职责方面分隔的很好,OkHttpCall 类只负责网络交互,凡是和函数定义相关的,都交给ServiceMethod 类去处理,而 ServiceMethod 类对使用者不公开,因为 Retrofit 是个 门面模式也就是外观模式,所有需要扩展的都在Retrofit的建造者中实现,用户只需要简单调用门面里的方法,就能满足需求,不需要关心内部的实现。

    我们尝试来分析下,在一个网络请求中,哪些是易变的,哪些是不变的,为什么 Retrofit 会这么去设计?

    由于 Retrofit 提供网络访问的工作对象,又是服务于具体业务,所以可以分网络访问和具体业务两部分来分析。

    • 网络访问的不变性
      对于网络访问来说,不变的是一定有一个实现网络访问的对象,Retrofit 选用了自家的 OkHttpClient,为了把 Retrofit 和OkHttp 解耦合,Retrofit根据依赖倒置原则定义了自己的接口 Call 即 retrofit2.Call,并定义了操作网络请求的具体类 OkHttpCall,和okHttp3.Call仅为引用关系。

    • 网络访问的易变性
      对于网络访问来说,易变的是网络访问的url、请求方式(get/post等)、Http请求的Header设置与安全设置等,以及返回的数据类型。

      针对易变的url和请求方式,Retrofit使用了方法注解的方式,可读性良好,但这需要实现对接口函数中注解的解析,这样就有了ServiceMethod。

      针对Http请求的各种设置,其实Retrofit没做什么,因为Retrofit使用的OkHttp有拦截器机制,可以应付这种变化。

      针对返回的数据类型,由于目标数据类型与业务有关,是不确定的,Retrofit无法提供一个万能的转换类,所以Retrofit提供了扩展接口,允许开发者自己定义 ConverterFactory 和 Converter,去实现潜在的数据类型转换。

    • 具体业务的不变性
      对于具体业务来说,不变的是一定要有一个Call网络工作对象,所以Retrofit可以有一个生产对象的机制(像工厂一样)

    • 具体业务的易变性
      对于具体业务来说,易变的就是这个Call网络工作对象的类型,不仅有CallBacl回调、可能还有Flowable工作流、或者其他潜在的对象类型。

      针对这种Call对象的易变性,Retrofit也是无法提供一个万能的实现类,所以也是提供了扩展解耦,允许开发者自己定义CallAdapterFactory和CallAdapter,去实现潜在的Call类型转换。

      因为这种Call对象的生产需要有大量的配套代码,为了简化代码,Retrofit使用动态代理来生产这个对象。

    最后,因为需要处理的方法和对象太多太复杂,需要使用建造者模式来把建造过程和使用过程分离开。

    可以说 Retrofit 设计得非常精妙。最后贴张架构图,跑路!


    参考:
    Retrofit 分析-漂亮的解耦套路
    框架源码 — 可能会有趣一点地简析学习 Retrofit
    Android:手把手带你 深入读懂 Retrofit 2.0 源码
    拆轮子系列 - 如何由浅入深探索 Retrofit 源码?

    相关文章

      网友评论

        本文标题:「拆轮子」Retrofit 整体架构、细节和思考

        本文链接:https://www.haomeiwen.com/subject/mxcnaxtx.html