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

阅读源码基本套路: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 源码?


我是 FeelsChaotic,一个写得了代码 p 得了图,剪得了视频画得了画的程序媛,致力于追求代码优雅、架构设计和 T 型成长。

欢迎关注 FeelsChaotic 的简书掘金,如果我的文章对你哪怕有一点点帮助,欢迎 ❤️!你的鼓励是我写作的最大动力!

最最重要的,请给出你的建议或意见,有错误请多多指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容