Retrofit源码解析(二)

  • 适配器模式

    上一篇文章我们已经分析了Retrofit解析注解封装进ServiceMethod的流程,读者在这里要记住,一个ServiceMethod就代表一个interface接口里的方法。相信部分读者已经忘了Retrofit的create流程,这里我们再贴一下这部分代码,然后继续分析下去。

    public <T> T create(final Class<T> service) {
        Utils.validateServiceInterface(service);
        if (validateEagerly) {
          eagerlyValidateMethods(service);
        }
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
            new InvocationHandler() {
              private final Platform platform = Platform.get();
    
              @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                if (platform.isDefaultMethod(method)) {
                  return platform.invokeDefaultMethod(method, service, proxy, args);
                }
                ServiceMethod<Object, Object> serviceMethod =
                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
              }
            });
      }
    

    ServiceMethod初始化完毕之后,接下来需要创建Call对象。

    OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
    

    看到没有?这部分默认使用的是OkHttpCall,并且是写死的,用户无法自行扩展。如果你不想默认使用okhttp,那只能修改这部分代码,重新打包retrofit了。

    构造完okhttpCall对象,接下来该做什么呢?

    有了接口代理对象之后,我们就需要把http请求发送出去了。发送可以使用同步方式与异步方式。同步方式我们好理解,http请求发送出去后程序需要阻塞等待http响应的返回,这样我们自然会自己开辟子线程来执行同步请求等待结果并处理;但异步形式呢?既然是异步发送,http响应的到达一般是通过回调的形式通知的。而异步发送请求在Retrofit里面本身又是运行在子线程的,如果不经过特殊处理,回调自然也是运行在子线程。

    这么说,难道还要用户自行从子线程切换到主线程去处理回调?这也太麻烦了吧?

    想太多!!!Retrofit这么神奇的库,当然早就处理好了这种情况。还记得我们在上一篇文章里面提到的MainThreadExecutor吗?聪明的你肯定已经想起来了。是的,它就是用来执行切换操作的神器。

    既然知道有这个神器存在,接下来该怎么使用呢?默认的okhttpCall对象和MainThreadExecutor是没有任何挂钩的,okhttpCall的异步请求的实现也没有去做任何的切换操作,而我们又需要执行异步操作完毕后可以自行切换到主线程去回调,很显然,okhttpCall不能满足我们的使用场景,该怎么办呢?

    这时候就需要适配器模式来发挥作用了,把不能满足使用情形的Call适配成符合场景的另外一个Call。这个系列的文章开头就已经大致介绍了CallAdapter的作用,但还没有实际看过这个接口内部的构造,我们现在跟进去看看。

    public interface CallAdapter<R, T> {
    
      //返回要将http响应转换到的Java对象类型,如Call<Repo>,则返回类型是Repo
      Type responseType();
    
    
      T adapt(Call<R> call);
    
    
      abstract class Factory {
    
        public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
            Retrofit retrofit);
    
        protected static Type getParameterUpperBound(int index, ParameterizedType type) {
          return Utils.getParameterUpperBound(index, type);
        }
    
        protected static Class<?> getRawType(Type type) {
          return Utils.getRawType(type);
        }
      }
    }
    

    CallAdapter里面最重要的一个方法是adapt,它的作用是把传递进来的Call对象适配成符合程序需求的其他类型的对象。

    还记得上一篇文章中,在建造者模式这一节中我们分析了默认的适配器工厂的赋值吗?是的,它的默认值是ExecutorCallAdapterFactory(忘记的读者请自行异步上一篇文章回顾哦)。我们再重新温习一下ExecutorCallAdapterFactory。

    final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
      final Executor callbackExecutor;
    
      ExecutorCallAdapterFactory(Executor callbackExecutor) {
        this.callbackExecutor = callbackExecutor;
      }
    
      @Override
      public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if (getRawType(returnType) != Call.class) {
          return null;
        }
        final Type responseType = Utils.getCallResponseType(returnType);
        return new CallAdapter<Object, Call<?>>() {
          @Override public Type responseType() {
            return responseType;
          }
    
          @Override public Call<Object> adapt(Call<Object> call) {
            return new ExecutorCallbackCall<>(callbackExecutor, call);
          }
        };
      }
      }
    

    ExecutorCallAdapterFactory的get方法是获取CallAdapter的入口,可以看到,get内部返回了一个匿名的callAdapter对象,实现了responseType和adapt两个方法。

    因此,Retrofit.create的serviceMethod.callAdapter.adapt(okHttpCall)最终调用的是由ExecutorCallAdapterFactory获取到的CallAdapter的adapt方法。okhttpCall最终也被适配成ExecutorCallbackCall返回。这就是Retrofit需要的Call对象。

    这里我们也关注两个点:

    1. 构造ExecutorCallbackCall对象时,传入的callbackExecutor是我们在上一篇文章分析过的MainThreadExecutor;

    2. 构造ExecutorCallbackCall对象时,传入的call是OkhttpCall;

    至于适配的其他平台,如RxJava\Java8\Guava等,我们不做展开,这里主要是梳理分析Retrofit的主要流程源码。若有较多读者有需要,可以反馈给我,后续开专栏补上。


  • 装饰者模式

    创建完代理对象后,接下来就是使用代理对象来发送http请求了。发送分为同步和异步两种,对应Call接口里的execute和enqueue。我们首先分析execute流程,然后再分析enqueue流程,循序渐进。

    经过Retrofit.create方法创建的Call对象实际上是ExecutorCallbackCall对象,因此,同步、异步请求真正调用的是ExecutorCallbackCall的execute和enqueue方法。

    execute

     @Override public Response<T> execute() throws IOException {
      return delegate.execute();
    }
    

    execute内部的实现非常简单,仅仅是调用了delegate的execute方法。那么delegate是什么呢?顾名思义,它是被ExecutorCallbackCall委托来执行execute操作的。我们再来看看ExecutorCallbackCall对于delegate的赋值。

    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;
    }
    
    ...
    
    }
    

    有没有似曾相识的感觉?是的,我们在上文的分析中,曾经划重点指出了ExecutorCallbackCall在构造时的默认赋值,其中,callbackExecutor的默认赋值是MainThreadExecutor,delegate的默认赋值是OkhttpCall。也就是说,实际的发起同步请求的操作还是由OkhttpCall来发起的。

    将某个值通过依赖注入的形式在类内部持有其引用,之后再在类对应的方法里调用该引用的方法(前后可增加一些操作),这种模式就是装饰者模式。

    好了,现在定位到OkhttpCall的execute方法。

    @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;
    
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;
    
      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException | Error e) {
          creationFailure = e;
          throw e;
        }
      }
    }
    
    if (canceled) {
      call.cancel();
    }
    
    return parseResponse(call.execute());
    }
    
    

    Retrofit的一个请求对象只能执行一次,因此execute一开始会进行判断。通过判断之后,会对call对象赋值rawCall。rawCall一开始显然是null,所以会执行到createRawCall()来创建call对象。

     private okhttp3.Call createRawCall() throws IOException {
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
    }
    

    Retrofit在创建ServiceMethod对象时,已经将请求的注解信息都封装进去了,这里直接调用ServiceMethod.toRequest就可以把一个接口方法转换成Request请求对象了。我们来看看ServiceMethod是如何将一个接口方法转换成Request对象的。

      Request toRequest(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);
    
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    
    int argumentCount = args != null ? args.length : 0;
    
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    
    return requestBuilder.build();
    }
    

    toRequest内部使用了RequestBuilder来封装解析的注解信息,并对每个参数注解调用其处理器的apply方法进行转换解析,最后调用RequestBuilder.build方法构造出一个Request对象。RequestBuilder就是一个Request建造器,其内部源码不复杂,读者也请自行翻阅,这里不做展开,我们的解读目标依然是Retrofit主线。

    创建出了Request对象之后,Request对象如何传输到网络上,还需要Call来实现。接下来createRawCall会执行serviceMethod.callFactory.newCall(request)。我们在上一篇文章中已经提到过,serviceMethod.callFactory实际上就是OkhttpClient,因此实际构造Call的操作是交由Okhttp来完成的(Okhtpp实际创建的是RealCall对象),本系列文章主要是探索Retrofit的源码实现,Okhttp部分的源码我们就不做深入分析了,读者有兴趣可以自行去阅读。

    到此,Call对象就已经创建出来了,其本质是Okhttp的RealCall对象,这也直接说明了Retrofit本身并不负责网络请求,而是将该功能交由okhttp来执行,很好地解除了依赖。

    Call对象创建完毕,剩下的就是以下步骤了。

    parseResponse(call.execute())
    

    call.execute实际就是调用Okhttp.RealCall.execute了。那么RealCall里面的execute到底做了什么呢?这里我们小小地涉足一下,浅尝辄止。

    
    Okhtpp.RealCall
    
     @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
    }
    

    相信细心的读者已经发现了。

    Response result = getResponseWithInterceptorChain(false);
    

    RealCall是通过上述getResponseWithInterceptorChain方法获取到了http响应的。Okhttp发起网络请求和解析网络响应的核心正是使用了责任链模式实现的,这部分源码太多,这里不做展开,读者可自行搜索相关文章查看。

    好了,现在让我们回到Retrofit。

    call.execute执行完毕以后,程序会获取到Okhttp.Response类型的http响应。而Okhttp.Response在Retrofit中是无法被识别的,因此需要将其转换成REtrofit可以识别的对象,parseResponse()就是起这个作用的。

     Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();
    
    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();
    
    int code = rawResponse.code();
    if (code < 200 || code >= 300) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
    }
    
    if (code == 204 || code == 205) {
      rawBody.close();
      return Response.success(null, rawResponse);
    }
    
    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throw e;
    }
    }
    

    parseResponse解析出rawBody之后,调用了ServiceMethod.toResponse方法,将rawBody转换成对应的JavaBean对象。

    R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
    }
    

    可以看到,ServiceMethod的toResponse方法很简单,就是调用事先已经注册好的responseConverter来将rawBody对象convert成我们事先定义好的JavaBean。那么responseConverter的值各位读者还记得吗?对了,它就是我们一开始的时候注册进去的GsonConverterFactory.GsonResponseBodyConverter。

    经过以上流程,execute方法就执行完毕了。


    enqueue

    分析完同步请求execute的流程,现在我们来分析异步请求enqueue。

    call.enqueue调用的还是ExecutorCallbackCall的enqueue方法。

    ExecutorCallbackCall
    
     @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(new Runnable() {
            @Override public void run() {
              if (delegate.isCanceled()) {
    
                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(new Runnable() {
            @Override public void run() {
              callback.onFailure(ExecutorCallbackCall.this, t);
            }
          });
        }
      });
    }
    

    这里的enqueue依然是交由OkhttpCall的enqueue方法来执行。我们来看看OkhttpCall的enqueue方法。

    OkhttpCall
    
     @Override public void enqueue(final Callback<T> callback) {
    
    okhttp3.Call call;
    Throwable failure;
    
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;
    
      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }
    }
    
    if (failure != null) {
      callback.onFailure(this, failure);
      return;
    }
    
    if (canceled) {
      call.cancel();
    }
    
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }
    

    与execute方法的执行流程相似,首先还是会调用createRawCall()创建一个Okhttp.RealCall对象,再调用该对象的enqueue方法发起真正的http异步请求。

    OkhttpCall.enqueue方法的参数callBack是从ExecutorCallbackCall的enqueue方法传入的。当请求成功获取到响应时,OkhttpCall会执行和execute同样的解析操作:调用parseResponse,在parseResponse里面调用ServiceMethod.toResponse方法,将Okhttp.Response转换成JavaBean,最终再经由Retrofit.Response.success进行封装返回Retrofit.Response对象。

    无论成功或者失败,OkhttpCall都会通过callBack将结果进行回调。这里的回调执行的是ExecutorCallbackCall传递的callBack。

    我们再来回顾一下ExecutorCallbackCall的回调吧。

    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()) {
    
                    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(new Runnable() {
                @Override public void run() {
                  callback.onFailure(ExecutorCallbackCall.this, t);
                }
              });
            }
          }
    

    可以看到,在onResponse和onFailure里面,真正执行回调的是callbackExecutor!!!

    callbackExecutor.execute(...)
    

    各位,各位,这里的callbackExecutor对象还记得吗???

    它就是我们反复强调的MainThreadExecutor了!!!

    从这里开始,异步请求的回调就顺利地从子线程切换到了主线程。


    跋山涉水,一番探索之后,我们终于把Retrofit的源码捋顺了。希望这两篇文章对各位读者理解Retrofit的设计有所帮助,谢谢!

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

推荐阅读更多精彩内容