okhttp源码解析

从用法开始,一般的用法是:

  public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

  OkHttpClient client = new OkHttpClient();

  void post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
    Call mCall = client.newCall(request);
    mCall.enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {

      }
      @Override
      public void onResponse(Call call, Response response) throws IOException {

      }
    });
  }

先创建一个Request,然后再创建一个Call,然后调用call的enqueue发送异步请求。

先看OKHttpClient这个类的描述。

Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their responses.

<h3>OkHttpClients should be shared</h3>

OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.

大意就是说让我们复用OkHttpClient实例,每个请求新建一个client会浪费资源。

看看call是如何创建的

  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

RealCall里面:

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

直接创建了一个RealCall的实例。

在看call的enqueue方法:

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

主要部分就是,调用了dispatcher的enqueue,继续看dispatcher的enqueue方法。

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

判断了一下是否超过同时发生请求的最大限制,然后将请求放入等待队列或者直接放入executorService,看到这里就应该很熟悉了,这是java的线程池。

然后看他放入的这个AsyncCall的run方法,应该就是发送请求的开始的地方:

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

这个AsyncCall被封装了一下,主要是用于给Thread改一个名字。这里execute方法是在父类的run方法里执行的。

最主要的就是一句:

Response response = getResponseWithInterceptorChain();

看这个getResponseWithInterceptorChain方法

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

这里组装了一堆interceptor,然后创建了一个chain接口的实例,来看看interceptor和chain接口

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

interceptor接口只有一个intercept方法,但是他的参数是chain接口。
chain接口有很多方法, 其中一个比较重要的就是proceed方法。

接着看RealInterceptorChain这个类:

//RealInterceptorChain的proceed方法里主要的代码
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
//...
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
//...
    return response;
  }

新建了一个RealInterceptorChain的实例,但是传入的参数index+1了。获取当前的interceptor调用intercept方法。

看一下整体的类图:


okhttp.png

上面只画了我们直接接触的类,发送请求主要是组建Call类的实例,call类里引用了很多东西,比如okhttpclient,和request。

他的主要功能都是interceptor完成的。

如果我们自己不添加任何自定义interceptor,那么就只会有5个interceptor,第一个调用的就是RetryAndFollowUpInterceptor,但是他会调用下一个chain的proceed才返回,所以他也是最后一个返回的。

调用关系图:


image.png

每一次interceptor调用proceed方法的时候,都会再创建一个chain的实例,然后把下一个interceptor的index传进去,从而实现了interceptor的遍历调用。

调用顺序如下图:


okhttp.png

其中interceptors和networkInterceptors都是可以在创建httpclient的时候添加的,其中interceptors是整个请求的拦截,可以拦截包括okhttp里的一些已有的功能。networkInterceptors主要是部分拦截网络部分。

如果一个网址从A链接重定向到B链接,如果添加的是interceptors,那么就只会调用一次,如果是添加的networkInterceptors,那么会调用两次。

interceptors

  • 不需要考虑重中间状态的返回,比如重定向和重试。
  • 只会调用一次,即使结果是从缓存中返回的。
  • 监控原始请求,不需要考虑okhttp本身加入的一些headers比如If-None-Match
  • 允许直接拦截,并且不调用Chain.proceed()
  • 允许重试和多次调用Chain.proceed()

Network Interceptors:

  • 可以操作一些内部的返回,比如重定向的返回和重试的返回。
  • 当被拦截的时候不会被调用。比如命中缓存。
  • 监控那些会被发送到网络中的请求。
  • 可以访问带有请求数据的Connection类。

原文:https://square.github.io/okhttp/features/interceptors/

再来找几个interceptor看看。

retryAndFollowUpInterceptor的intercept方法。

    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {

里面有一个while(true),也就是通过多次调用Chain.proceed的方式实现多次请求。他里面调用的proceed是reallInterceptorChain的方法,而不是chain接口的方法,主要是为了复用StreamAllocation

BridgeInterceptor是为了将用户的请求转化为正式的网络请求(添加补充一些header等等),然后把网络的返回转换为用户接受到的返回,比如gzip解压缩等等。

CacheInterceptor是用来处理缓存的,根据请求先从缓存里找,如果有就直接返回,否则请求网络,并且将更新缓存。

ConnectInterceptor是用来跟目标服务器建立连接,然后发送请求。

CallServerInterceptor这是最后一个interceptor,用来请求服务端的interceptor。

总结:

  • 新的请求来了可能放入等待队列,每次有请求完成就会检查等待队列并且加入发送。所以会有一个listener监听请求是否完成等状态。
  • retryAndFollowUpInterceptor是第一个调用的interceptor,如果不添加自定义interceptor的话,他是通过在while循环里多次调用chain.proceed来实现多次发送请求。
  • 如果想要拦截一次网络请求,不需要管重定向,重试等等,则使用intercpetors。如果是想要监听所有的发出去的网络请求(包括重定向,重试,也可能命中缓存不会调用这个拦截器),则使用networkInterceptors。
  • response.body().string()会将source关闭,所以只能调用一遍。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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