OkHttpClient源码分析(一)—— 同步、异步请求分析和Dispatcher的任务调度

OkHttpClient同步请求的执行流程和源码分析

同步请求示例

OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();
        Call call = okHttpClient.newCall(request);
        try {
            Response response = call.execute();
            Log.e(TAG,"response: " + response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }

同步请求的步骤

  1. 创建OkHttpClient对象和Request对象,均是采用Builder模式创建,构建者(Builder)设计模式(又叫生成器设计模式)
  1. 将Request封装成Call对象
  1. 调用Call的execute()方法发送同步请求,发送请求后,就会进入阻塞状态,直到收到响应。

一、(1)OkHttpClient Builder对象分析

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
}

  OkHttpClient Builder的构造函数,主要是对一些参数赋值默认值,对一些对象进行初始化,Dispatcher是OkHttpClient中http请求的分发器,由它来决定异步请求是直接处理还是进行缓存等待,对于同步请求,它并没有做太多操作,只是把同步请求放到队列当中去执行。ConnectionPool是一个连接池对象,用于管理连接对象,当存在同样的Url请求时,可以复用,从连接池中找到对应缓存的连接对象。

(2)Request 对象分析

 public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
}

Request Builder的构造函数,默认请求方法为GET,同时初始化一个Header对象。

 public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
}

build()方法是创建Request对象,将当前的builder对象传入,接下来看Request的构造函数:

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
  }

  可以看到,是将传入的Builder对象中的属性赋值给Request的相关属性,这样就创建好了Request对象。

二、创建Call 对象分析

/**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

  OkHttpClient 对象中的newCall()方法,返回值是一个Call对象(接口),在这里可以看到实际上调用的RealCall.newRealCall()方法创建,RealCall是Call接口的一个实现类,接着查看RealCall类中newRealCall()方法:

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

  可以看到newRealCall()方法中创建了Call接口的实现类RealCall对象并返回该对象,到此Call对象的创建便完成了。

三、Call 对象exexcute()方法分析

  上面有提及到Call对象是一个接口,我们点击查看exexcute()方法时,需要点击查看该方法的实现,实际上是进入到RealCall对象的exexcute()方法:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

  在同步代码中,先通过判断executed标识,如果当前已经有在执行,则会抛出"Already Executed"信息的异常,如果没有执行过,则更改executed标识为true。

  接着调用captureCallStackTrace()方法,这个方法主要用于捕捉一些http请求的异常堆栈信息。

  eventListener.callStart(this)开启事件监听,通过查看该方法:

 /**
   * Invoked as soon as a call is enqueued or executed by a client. In case of thread or stream
   * limits, this call may be executed well before processing the request is able to begin.
   *
   * <p>This will be invoked only once for a single {@link Call}. Retries of different routes
   * or redirects will be handled within the boundaries of a single callStart and {@link
   * #callEnd}/{@link #callFailed} pair.
   */
  public void callStart(Call call) {
  }

  通过阅读该方法的注释,可以知道该方法会在调用Call对象的enqueue()或execute()方法的时候,就会开启这个listener。

接下来分析一下这个方法中的核心代码:

client.dispatcher().executed(this);

首先调用OkHttpClient的dispatcher()方法

public Dispatcher dispatcher() {
    return dispatcher;
}

该方法返回一个Dispatcher对象,紧接着调用该对象的executed()方法:


  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

  该方法中,runningSyncCalls是一个存放同步请求的队列,这里仅仅只是将RealCall加入到同步请求的队列中,Dispatcher对象中相关的队列有:

 /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  • readyAsyncCalls 是异步请求的就绪队列
  • runningAsyncCalls 是异步请求的执行队列
  • runningSyncCalls 是同步请求的执行队列

  调用完Dispatcher的executed()方法后,紧接着调用getResponseWithInterceptorChain()方法获取Response对象,这个其实是一个拦截器链的方法,该方法内部会依次调用拦截器来进行相应的操作。

最后看一下finally中:

finally {
      client.dispatcher().finished(this);
}

  通过调用Dispatcher的finished()方法,传入当前的RealCall对象,查看该方法的代码可以发现:

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

  该方法继续调用了其他一个同名的的方法,将正在执行的同步请求队列传了进来,在同步代码块中,移除掉同步请求队列中的call对象,并进行了判断,如果移除出错,则会抛出异常。接着判断promoteCalls,由于这里传入的promoteCalls为false,所以不会走promoteCalls()方法。

  接着,对runningCallsCount重新赋值,runningCallsCount用于记录当前正在执行的请求数,查看该方法的代码:

public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
}

该方法很简单,即返回正在执行的异步请求数和正在执行的同步请求数的总和。

if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
}

  最后通过判断当前正在执行的请求数,如果当前没有正在执行的请求数并且有设置闲置时的回调,则会回调其run()方法。

总结

  到此,同步请求的执行流程就已经分析完了,由上述的分析可以知道,在同步请求中,Dispatcher分发器做的工作非常简单,就两个操作,保存同步请求和移除同步请求

OkHttpClient异步请求的执行流程和源码分析

异步请求示例

 OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                
            }

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

            }
        });

异步请求的步骤

  1. 创建OkHttpClient对象和Request对象,均是采用Builder模式创建,构建者(Builder)设计模式(又叫生成器设计模式)
  1. 将Request封装成Call对象
  1. 调用Call的enqueue()方法进行异步请求

同步和异步的区别

  1. 发起请求的方法调用
  1. 阻塞线程与否

源码分析

  异步请求的前两步,和同步请求的一致,都是一些准备工作,并没有发起请求,这里不再重复说明,最主要的是第三步,调用Call对象的enqueue()方法,具体的实现还是在RealCall类中,查看该方法代码:

  @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));
  }

  前面的操作和同步请求的execute()方法相似,主要是 client.dispatcher().enqueue(new AsyncCall(responseCallback)) 这行代码,调用Dispatcher的enqueue()方法,将Callback回调封装成AsyncCall对象作为参数传入,通过查看代码,了解到AsyncCall对象继承自NamedRunnable对象,而NamedRunnable对象实现了Runnable接口,接着继续查看Dispatcher的enqueue()方法源码:

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

  该方法前加了synchronized修饰符,是一个同步方法,根据判断当前执行的异步请求数是否小于maxRequests(最大请求数,默认为64) 且当前执行的异步请求队列中相同主机的请求数小于maxRequestsPerHost(每个主机最大请求数,默认为5) 来进行处理,如果二者都小于设置的值,则将该请求添加到runningAsyncCalls(异步请求执行队列)中,否则则添加到readyAsyncCalls(异步请求准备队列)中。

runningCallsForHost()方法的代码:

 /** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

  通过注释可以知道,该方法返回同一个主机的请求数目,通过遍历执行中的异步请求队列,和传入的AsyncCall对象的主机对比,如果相同则记录数递增,以此获得和传入AsyncCall对象相同主机的请求数。

enqueue()方法中,主要的代码:

executorService().execute(call);

这里是进行异步请求操作的代码,先看下executorService()方法的代码:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
}

  该方法也是一个同步方法,主要用于返回 ExecutorService 对象,在这里仅一次创建了线程池对象 ThreadPoolExecutor,第二个参数传入了Integer的最大值,即线程池所能容纳的最大线程数为Integer.MAX_VALUE,虽然这里设置了很大的值,但是实际情况下并非会达到最大值,因为上面enqueue()方法中有做了判断,主要的还是maxRequests这个值决定异步请求线程池的最大数量。

  executorService()方法返回了线程池对象,接着调用它的execute()方法,传入实现Runnable接口的AsyncCall对象,上面提及到AsyncCall继承NamedRunnable,而NamedRunnable对象实现了Runnable接口,所以我们想知道该线程池执行这个任务做了什么,就得看下NamedRunnable对象的 run() 方法:

@Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
}

该方法中,真正的处理逻辑是在execute()方法中:

 protected abstract void execute();

而execute()方法是一个抽象方法,所以要回到继承NamedRunnable对象的AsyncCall类中:

 @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);
      }
}

  这里才是真正进行异步请求操作的逻辑,同样也是通过getResponseWithInterceptorChain()方法得到Response对象,关于getResponseWithInterceptorChain()方法的分析在下面的文章里将会介绍,接着通过判断retryAndFollowUpInterceptor是否取消回调CallBack接口的onFailure()或onResponse()方法,最后finally中,和同步请求的处理一样,调用了Dispatcher对象的finished()方法:

 /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

也是调用了带三个参数的finished()方法,传入了runningAsyncCalls,call,第三个参数传入了true。

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
}

这里的处理和同步请求结束后的处理多了一个promoteCalls()方法的调用,因为这里promoteCalls传入了true,所以会走promoteCalls()方法:

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

  看完这个方法,会有一种恍然大悟的感觉,因为上面调用enqueue()方法的时候,会根据情况将请求添加到runningAsyncCalls(异步请求执行队列)或readyAsyncCalls(异步请求准备队列)中,而readyAsyncCalls队列中的请求什么时候执行呢,相信在看enqueue()方法的时候会有这个疑问,看了promoteCalls()后疑问将会被解答,为了方便阅读再次贴上enqueue()方法:

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

  promoteCalls()方法中,首先做了一些判断,当runningAsyncCalls(异步请求执行队列)已经达到设置的最大的请求数或当前readyAsyncCalls(异步请求准备队列)中没有请求的时候,则直接返回不做处理,如果满足条件,则会遍历readyAsyncCalls队列,将该请求添加到runningAsyncCalls队列中,并调用 executorService().execute(call) 对该请求进行处理。

总结

  如果异步请求数超过最大请求数或同个主机最大请求数超过设置的值的时候,该请求就会添加到readyAsyncCalls(异步请求准备队列)中,当执行完runningAsyncCalls(异步请求执行队列)的请求后,将会调用Dispatcher的finished()三个参数的方法,第三个参数传入true,会调用promoteCalls()方法,遍历准备队列readyAsyncCalls,将该队列的中的请求添加到执行队列runningAsyncCalls中,调用 executorService().execute(call)进行处理。

Dispatcher的作用

维护请求的状态,并维护一个线程池,用于执行请求。

异步请求为什么需要两个队列

异步请求的设计可以将其理解成生产者消费者模式,其中各个角色分别为:

  • Dispatcher 生产者
  • ExecutorService 消费者池
  • Deque<AsyncCall> readyAsyncCalls 缓存
  • Deque<AsyncCall> runningAsyncCalls 正在运行的任务

当同步和异步请求结束后,会调用dispatcher的finished方法,将当前的请求从队列中移除。

下一篇文章中,将为大家讲解一下OkHttp的拦截器链,感兴趣的朋友可以继续阅读:

OkHttpClient源码分析(二) —— RetryAndFollowUpInterceptor和BridgeInterceptor

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

推荐阅读更多精彩内容