NetAndroid技术知识Android开发

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

2018-01-27  本文已影响101人  FeelsChaotic

阅读源码基本套路: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 设计得非常精妙。最后贴张架构图,跑路!


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

上一篇 下一篇

猜你喜欢

热点阅读