从问题到解析,读懂Retrofit2原理

在项目开发中其实最常见的网络请求框架就是OkHttp+Retrofit,在上一篇老生新谈,从OkHttp原理看网络请求剖析了OkHttp的原理,今天这篇文章将从不同的角度来看Retrofit的内部实现。

读完这篇文章,您将了解到:

  • 什么是动态代理模式?
  • Retrofit为什么要使用动态代理?
  • Retrofit如何与OkHttp相结合?
  • Retrofit是如何将子线程切换到主线程?
  • Retrofit弥补了OkHttp的哪些缺点?

以下源码版本为retrofit:2.9.0

什么是动态代理?

在Retrofit源码分析之前需要了解的一个比较重要的点,就是动态代理模式,它是Retrofit的核心设计的开始。

动态代理和静态代理都属于代理模式,动态代理是可以在运行期动态创建某个interface的实例,我们通过Proxy.newProxyInstance产生的代理类,当调用接口的任何方法时,都会被InvocationHandler#invoke方法拦截,同时,在这个方法中可以拿到所传入的参数等,依照参数值再做相应的处理。

定义比较抽象,举个普通的例子。

例如某用户近期需要购房,那么他就需要委托中介帮忙推荐房源,之后买家通过中介与开发商达成买卖协议。这里的中介就是代理对象,买家则是委托者,而其中“推荐房源”这件事情,则是抽象对象,也是委托者真正需要做的动作。

看下伪代码,首先定义委托者想要做的动作,并且告诉中介我对房子的一些基本要求:

/**
 * 买家委托中介购房。
 */
interface ToDo {
    fun buyHouse()
}

定义代理对象,即中介,并实现InvocationHandler,根据委托者的要求点返回代理对象:

/**
 * 中介
 */
class Middlemen(private val any: Any) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        return method?.invoke(any, *(args ?: arrayOfNulls<Any>(0)))
    }
}

委托者告诉中介,我要购房的信息:

/**
 * 买家
 */
class Buyer : ToDo {
    override fun buyHouse() {
        print("中介,请帮我买房子")
    }
}

最后利用代理对象实现购房的动作:

fun main(args: Array<String>) {
        val toDo: ToDo = Buyer()
        val dynamicMid = Proxy.newProxyInstance(
            toDo.javaClass.classLoader, toDo.javaClass.interfaces,
            Middlemen(toDo)
        ) as ToDo
        dynamicMid.buyHouse()
    }

动态代理最主要的部分在于代理对象实现InvocationHandler,并重写invoke方法。 当代理对象代理了委托者的要求,不管要求有多少,当代理执行时,都会走进invoke()方法中。这是重点,圈起来后面要考。

我们应该了解到,Retrofit的核心部分就在这动态代理中,那Retrofit为什么要使用动态代理?且动态代理中又做了哪些动作? ⁉️

那就接着往下分析源码。

源码解析

在源码分析之前,有两个重要的对象需要提前说明,提前了解他们的作用,更有利于后续的源码解读。

一个是CallAdapter,另外则是Converter

  • CallAdapter: 适配器,我们默认定义API Service方法的返回值为Call<T>类型,但是有时候会自定义返回类型,例如和RxJava相结合,返回Observable或者Single类型的时候应该怎么处理?CallAdapter的作用就是帮助开发者去适配这些返回类型,你定义了什么类型的数据,就可以通过CallAdapter#adapt进行返回。
  • Converter: 数据装换器,主要负责把服务器返回的数据ResponseBody转化为 T 类型的对象。例如在使用retrofit进行网络请求时,我们都会先定义一个返回值的实体类,Converter就会将网络请求的返回值转换为我们所需要的类型。

CallAdapter和Converter的作用如上述所说,接下来就直接进入到retrofit的动态代理源码中。

  public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                1️⃣
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                2️⃣
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

retrofit的创建在于retrofit.create(ApiService::class.java),内部使用动态代理模式,可以看到invoke方法中一开始就有几个判断:

  1. 1️⃣判断当前方法是否是在接口中声明,如果不是则直接按照正常流程调用invoke;
  2. 2️⃣判断是否属于Android平台的默认方法,如果是则直接按照正常流程调用invoke,反之,则就到了动态代理的核心部分:loadServiceMethod(method).invoke(args)

最后一行代码主要分为两步:loadServiceMethod(method)invoke(args)

loadServiceMethod(method)

这个方法的作用先提一下,它主要是将网络请求方法中的信息进行初步的处理,我们在创建api service具体接口时,会加上注解(@GET,@POST,@PUT...),参数(@Path、@Query...)等,该方法就是对接口中的注解、参数等进行解析,解析接口后又生成了一个RequestFactory请求工厂对象,并且利用这个RequestFactory对象创建了一个CallAdapter。

invoke(args)

final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

invoke的实现中也只有两行代码。

第一行:主要利用loadServiceMethod所创建的RequestFactory以及一些转换类作为参数生成一个OkHttpCall对象, OkHttpCall其实是对OkHttp中的realCall进行了一层包装(realCall可参考上一篇OkHttp的解析),
在Retrofit里,OkHttpCall紧密连接OkHttp,它的内部同样可以调用同步execute、 异步execute方法进行网络请求,其实真正调用的也就是OkHttp的execute和execute方法。在此同时,Retrofit中会对请求响应也做了解析。

我们先来看看OkHttpCall中网络请求的细节:

  @Override
  public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(callback, "callback == null");

    okhttp3.Call call;
    synchronized (this) {
      ...
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        }
      }
    ...
    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              //解析请求返回值
              response = parseResponse(rawResponse);
            }
              ...
            try {
              callback.onResponse(OkHttpCall.this, response);
            }

          @Override
          public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }

          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            }
          }
        });
  }

以一个异步请求为例,

  1. 内部首先调用了一个createRawCall()方法,创建rawCall,这个rawCall其实指代的就是Okhttp3的call,也就是OkHttp进行网络请求调用的一个调度器

  2. 创建好OkHttp的call后,就开始调用enqueue进行异步请求,发现在异步请求内响应的回调属于okhttp3.Callback,所返回来的结果,也都是okhttp3.Response,到这里就可以大概知道了,retrofit的网络请求其实还是由OkHttp来实现。

  3. okhttp3.Response这个响应不方便开发者直接使用,所以retrofit在收到结果后,又对响应结果进行新一轮的解析 response = parseResponse(rawResponse),以Response对象的形式返回给开发者。

另外还有最后一行: adapt(call, args); 它的内部其实是由一个适配器CallAdapted来调用,如下:

  protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      return callAdapter.adapt(call);
    }

在源码解析的一开始就对callAdapter进行了定义,这里将详细说明。

adapt是一个抽象方法,里面传入了上面刚创建的OkHttpCall作为参数。adapt即adapter,适配器的意思,那我们可以猜测一下,传入OkHttpCall,是不是就是为了对OkHttpCall再进行适配的工作?但,OkHttpCall已经可以进行网络请求了,为什么还需要使用CallAdapted进行再次的适配呢? ⁉️这个问题还是先记下,抛到后面解答。

咱们继续来分析动态代理的主线条,从上面分析知道,retrofit之所以调用简便,是因为内部对@GET,@POST,@Path等注解以及请求参数进行了解析,让开发者只需要关心自己新增的请求方法是不是符合规范。

而上述动态代理最主要做的事情就是创建了CallAdapted,CallAdapted其实只是一个适配器,主要需要了解的是它适配的是什么? 是否还记得我们在上面抛出了一个关于CallAdapted的问题:那就先来看看他是如何创建的?⁉️

CallAdapted

loadServiceMethod方法其实返回的就是CallAdapted对象,那我们就直奔主题,进去瞧一瞧,

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {

  static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {

    Type adapterType;
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }

    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
  ...

    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    }

跟踪到最后,是在HttpServiceMethod类中的parseAnnotations中发现了CallAdapted的创建..

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

CallAdapter是根据returnType和annotations的类型,从callAdapterFactories工厂中进行查找,从而返回所对应的网络请求适配器,这里returnType指的是网络请求接口里方法的返回值类型,如Call<?>、Observable<?>等。annotations则指代的是注解类型,如@GET、@POST等。

这里也提到了一个适配器工厂callAdapterFactories,不同的CallAdapter就是从这个工厂中查询出来的,有查找那就必定有添加,那适配器CallAdapter是怎么添加到工厂类中的? ⁉️callAdapterFactories这个变量是属于Retrofit类,跟踪发现是由Retrofit构造函数传入,也就是Retrofit初始化时进行了赋值。

Retrofit的初始化是由一种建造者模式来创建,在Retrofit的build()方法中,找到了适配器工厂对其适配器的添加:

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

      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      //添加适配器callAdapter
      callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
     ...
      return new Retrofit(
          callFactory,
          baseUrl,
          unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories),
          callbackExecutor,
          validateEagerly);
    }

platform.defaultCallAdapterFactories 指的是Android平台的一个默认的适配器工厂,当我们不使用自定义适配器工厂时,则添加的就是这默认的工厂。这里提到了自定义适配器工厂,其实我们在使用Retrofit的时候,有时候会和RxJava结合,例如在创建Retrofit时,也会addCallAdapterFactory,将RxJava2CallAdapterFactory添加到callAdapterFactories中。

   mRetrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

添加调用适配器工厂的目的就是支持Call以外的服务方法返回类型,如支持Observable,Single返回类型等。 在callAdapterFactories集合器添加一个默认适配器工厂时,也附带传进去了一个参数callbackExecutor,callbackExecutor是Java8或者Android平台的一个默认线程调度器,它的作用涉及到一个线程切换的问题,也就是后续需要分析的retrofit是如何将子线程切换到主线程?⁉️

这个问题还是先记下,后面针对线程切换详细说明。

在上面源码创建CallAdapter时,有一个对象也同时被创建,那就是Converter

Converter

Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType);

Converter在文章一开头也说过,是数据装换器。它的添加和获取方式和CallAdapter类似。在Retrofit初始化时添加到List<Converter.Factory>工厂集合,例如与GSon结合时,addConverterFactory(GsonConverterFactory.create()

mRetrofit = new Retrofit.Builder()
                .client(mOkHttpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create()
                .build();

后面也根据类型Type取出不同的Converter,对返回的网络响应,做出数据的转换,例如转换成实体类。

基本逻辑与CallAdapter类似。

说了这么多,是不是有点思路了,我们先来捋一捋。

  1. 一开始动态代理中调用loadServiceMethod方法,解析接口方法中的注解,参数,头部信息等;
  2. 依据接口方法的返回类型,从适配器工厂集合里进行查询,生成相应的适配器CallAdapter,区分是RxJava的Observable、Single还是Call或者其他类型,(适配器工厂集合的数据是由构建Retrofit时addCallAdapterFactory()添加,如无自定义,则添加Android平台默认适配器)。以相同的方式取出数据转换器Converter;
  3. 利用上面生成的CallAdapter,调用invoke方法,创建OkHttpCall对象,即针对请求信息,利用OkHttp进行异步或者同步网络请求,并且对响应结果进行实体类转换;
  4. 创建好OkHttpCall后,又利用上面查询到的适配器CallAdapter调用adapt,返回RxJava的Observable、Single或者Call对象。

在Retrofit初始化时有涉及到一个参数callbackExecutor,当时抛出了一个问题:Retrofit是如何将子线程切换到主线程?这里就来详细看看。

Retrofit是如何将子线程切换到主线程?

在添加默认适配器工厂defaultCallAdapterFactories时,将callbackExecutor作为了一个参数,那么它的具体实现也就是在这个默认适配器工厂中。
我们来看下callbackExecutor在里面做了些啥。

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;
    ...

    @Override
    public void enqueue(final Callback<T> callback) {

      delegate.enqueue(
          new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
              callbackExecutor.execute(
                  () -> {
                    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);
                    }
                  });
            }

            @Override
            public void onFailure(Call<T> call, final Throwable t) {
              callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
            }
          });
    }

在上述代码里了解到,callbackExecutor即Executor,一个线程调度器。在Call的enqueue实现里执行了一个异步网络请求delegate.enqueue,在请求的响应onResponse、onFailure中
Executor也同样执行了一个线程,这里就有个疑问,为什么要在一个异步请求里又调用一个线程?我们知道callbackExecutor是一个线程调度器,那他内部到底实现的是什么?
默认callbackExecutor的创建在Retrofit的初始化中,callbackExecutor = platform.defaultCallbackExecutor();

static final class Android extends Platform {

    @Override
    public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }

    static final class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override
      public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }
}

platform是一个Android平台,defaultCallbackExecutor 内部其实调用的是 new MainThreadExecutor() ,很清楚的看到, handler.post(r) 内部使用Handler将响应抛到了主线程。

这就是Retrofit将子线程切换到主线程的核心所在。

Retrofit为什么要使用动态代理?

文章一开始就抛出了这个问题,经过一轮分析后,也就有了答案。

我们想一下,动态代理的优势是什么?是不是不用暴露真实的委托者,根据不同的委托创建不同的代理,通过代理去做事情。

那Retrofit弥补了OkHttp的缺点又指的是什么?OkHttp在使用的时候,请求参数的配置是不是很繁琐,尤其当有一些表单提交时,又臭又长,而Retrofit就是弥补了这个缺点,利用@GET、@POST、@Path、@Body等注解以及一些参数很简便的就构造出了请求。

当Retrofit创建了不同的接口,动态代理就发挥出了作用。每当不同接口方法执行时,动态代理都会拦截该请求,对接口中的注解,参数进行解析,构建出不同的Request,最后则交给OkHttp去真正执行。

Retrofit结合动态代理,不用关心真正的接口方法,对符合规范的接口进行统一化的管理,以统一的方式解析注解和参数,拼接成request。

总结

通过源码以及问题的解答,了解到Retrofit其实是OkHttp的封装类,内部网络请求还是靠的OkHttp,那Retrofit封装后改变了什么?

  • 接口请求更加简便,标注注解@GET、@POST、@Path、@Body等就形成一个网络请求;
  • 默认帮助开发者解析responseBody,另外还可以自定义解析策略;
  • Retrofit帮助开发者进行线程切换;
  • Retrofit带给开发者更多的权限,可自定义适配网络请求。

Retrofit源码不多,但是却包含了很多设计技巧,后续将开一篇详细说明Retrofit的设计之美。

以上便是Retrofit的源码解析,希望这篇文章能帮到您,感谢阅读。

推荐阅读

老生新谈,从OkHttp原理看网络请求
【网络篇】开发必备知识点:UDP/TCP协议

欢迎关注公 z 号:9点大前端,每天9点推荐更多前端、Android、Flutter文章

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

推荐阅读更多精彩内容