Retrofit 与 OkHttp 流程梳理

Retrofit 与 OkHttp 流程梳理

前言:

本文会带你分析一下,retrofit 是怎么一个 java 的 interface 转化成了一个 okhttp 的请求,并把请求的 model 返回给业务层的。

注解定义的参数在哪里解析:

首先我们看一下,我们对于 java 的 interface 添加的注解是怎么解析的。Retrofit 整体采用了一个 Builder 模式来创建新的对象。在这里面,会设置一些相关的工厂。主要有四类需要关注的:

  1. okhttp3.Call.Factory callFactory = this.callFactory;
  2. Executor callbackExecutor = this.callbackExecutor;
  3. List<CallAdapter.Factory> callAdapterFactories
  4. List<Converter.Factory>
    这里会指定 okhttp3.Call.Factory ,这也是后面我们创建 ServiceMethod 的实例的时候,创建实际创建 okhttp call 的时候使用的工厂。

之后,我们进入正题:Proxy.newProxyInstance
这里是利用了 java 的动态代理机制,将所有对代理对象的请求都转发给 InvocationHandler 中。我们只需要实现 InvocationHandler 就可以。一般时候我们使用动态代理,是我们有一个现成的对象,比如叫 MyInterfaceImpl myInterfaceImpl,那么,如果我们想使用动态代理的话,就可以在 InvocationHandler 中拿到对目标方法的请求,然后,常见的是在请求的前后打 log,然后实际的去调用请求,比如:method.invoke(myInterfaceImpl, args) 。在 retrofit 中,我们拿到了请求的目标 method 后,必须得明确,我们拿到的其实并不是一个实际的可以运行的方法,因为我们并没有一个类似 MyInterfaceImpl 的接口的实现类。这也就引出了 retrofit 跟我们平时写的一般的动态代理的不同的点:一般的动态代理只是在目标对象的方法调用前后,进行拦截;而 retrofit 是通过动态代理来动态生成目标对象的目标方法。也就是说,retrofit 在运行的时候,只有一个代理类,而没有这个接口的真正的实现类。而这个代理类的用处,只是得知我们的代码的“意图”。

回想我们使用 retrofit 时候,会定义一个接口,然后对里面的接口中的方法添加相关的注解。正是这些注解,提高了 retrofit 的解耦性,堪称是 retrofit 的灵魂!那么这些注解具体是怎么解析的呢?

在 InvocationHandler 的 invoke 中,会通过调用 loadServiceMethod(method).invoke(args) 来创建真正的 ServiceMethod,并把方法的调用代理给他。这个 ServiceMethod 对象的作用就跟我们上文中说的接口的实现类 myInterfaceImpl 的作用是一样的。loadServiceMethod中,会先检查一个缓存map,如果有 ServiceMethod 对象,就直接返回;如果没有,就新建。毕竟 ServiceMethod 对象的创建涉及注解的解析,实际运行的时候还是比较重的,非常适合对 ServiceMethod 实例对象进行缓存。

loadServiceMethodServiceMethod.parseAnnotations(this, method) 中,又把整个函数的流指向了 HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory),而这里正是真正实现了解析注解并生成 ServiceMethod 实例的过程。可以看到,里面首先创建了 CallAdapter,并解析出了 responseType

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

然后创建了 responseConverter

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

然后会创建出 CallAdapted 实例。这里的 CallAdapted实例,是继承自 HttpServiceMethod 的,也就是运行时真正的实例。

然后,回到我们在 InvocationHandler 中看到的 loadServiceMethod(method).invoke(args),这里就会调用 HttpServiceMethod中的 invoke 方法。

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

这里会真正的创建 OkHttpCall,然后把这个 call 通过 callAdapter 转化一下。具体的转化的目的和流程,我们会在后面提到。

请求的 enqueue 和 execute 怎么最终调用到 OkHttp 的?

上面我们了解到,ServiceMethod 最终会创建一个 OkHttpCall 作为请求的真正执行者。虽然在创建出 OkHttpCall 实例之后,还会调用 adapt 来把这个 Call 转化一下,然后提供给业务层使用,但是,转换之后的,比如默认的 ExecutorCallbackCall,其实是一个静态代理, 还是会把请求真正执行的部分代理给 OkHttpCall

我们知道,在使用 retrofit 的时候,有两种方式:executeenqueue。前者是同步方法,后者是异步方法。简单起见,我们只分析同步方法。

@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) {
        throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
        creationFailure = e;
        throw e;
      }
    }
  }

  if (canceled) {
    call.cancel();
  }

  return parseResponse(call.execute());
}

首先会判断 executed 变量。一个请求只能执行一次,再次执行的时候就会报错。然后是通过 createRawCall()来创建真正的 okhttp3.Call 。然后是在执行之前,会检查是否 cancel。然后会调用 call.execute()获取请求结果。最后是 parseResponse 来对请求结果进行解析。

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
  ResponseBody rawBody = rawResponse.body();

  // Remove the body's source (the only stateful object) so we can pass the response along.
  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);
  }

  ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
  try {
    T body = responseConverter.convert(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;
  }
}

首先是处理 if (code < 200 || code >= 300) {,也就是请求出错的情况。然后检查是否是 204 或者 205。这两种情况的大多数是为了表示“请求成功了,但是没有数据返回给客户端”,这样做的好处是可以减少不必要的数据传输,只通过一个状态码就表达了状态。

然后就是正常的请求处理过程,也就是调用 responseConverter.convert()。比如,我们最常用的 GsonConverter 里面的实现就是:

@Override public T convert(ResponseBody value) throws IOException {
  JsonReader jsonReader = gson.newJsonReader(value.charStream());
  try {
    T result = adapter.read(jsonReader);
    if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
      throw new JsonIOException("JSON document was not fully consumed.");
    }
    return result;
  } finally {
    value.close();
  }
}

会通过 jsonReader 把返回的 body 转化成 json,然后通过 TypeAdapterjson 转化成上层业务方使用的 model

关于 CallAdapter

return new CallAdapter<Object, Call<?>>() {
    public Type responseType() {
        return responseType;
    }

    public Call<Object> adapt(Call<Object> call) {
        return (Call)(new DefaultCallAdapterFactory.ExecutorCallbackCall(executor, call));
    }
};

可以看到,retrofit默认的 callAdapter 是把一个 OkHttpCall,转化成了一个 ExecutorCallbackCall 。相当于对 OkHttpCall 做了一个静态代理:ExecutorCallbackCall里面的 enqueueexecute 方法都是直接代理给 OkHttpCall 的实例来执行的。然后,观察 ExecutorCallbackCall 的 enqueque 方法,会发现,在 delegateonResponse 里面,会调用

ExecutorCallbackCall.this.callbackExecutor.execute(new Runnable() {
    public void run() {
        if (ExecutorCallbackCall.this.delegate.isCanceled()) {
            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
        } else {
            callback.onResponse(ExecutorCallbackCall.this, response);
        }
    }
});

人如其名。也就是,ExecutorCallbackCall会简单的在 onResponse 里面,把结果放在 executor 里面执行。在android中,就是一个 MainThreadExecutor

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

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

而,对于 retrofit 的两种使用方式:executeenqueueExecutorCallbackCall也只是处理了 enqueue,对异步的请求结果通过线程转换的方式让业务层直接在主线程拿到相应的 model。对于 execute 的同步请求,直接代理给了底层的 OkHttpCall

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

推荐阅读更多精彩内容