本篇文章主要介绍OkHttp执行同步和异步请求的大体流程。主要流程如下图:

主要分析到getResponseWidthInterceptorChain方法,该方法为具体的根据请求获取响应部分,留着后面的博客再介绍。

Dispatcher类

Dispatcher类负责异步任务的请求策略。首先看它的部分定义:

public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private Runnable idleCallback; /** Executes calls. Created lazily. */
private ExecutorService executorService; /** 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<>(); public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
} public Dispatcher() {
} 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;
} ... }

内部有一个线程池,三个队列,分别是readyAsyncCalls、runningAsyncCalls和runningSyncCalls队列,其中runningSyncCalls用于存储同步请求RealCall对象,runningAsyncCalls队列用于存储进入队列的异步请求AsyncCall对象,而readyAsyncCalls队列用于当runningAsyncCalls的尺寸达到maxRequests参数时(默认64)存储新加的异步请求。至于为什么要什么做呢?
我的理解是为了避免一时间创造大量的线程浪费资源,那么为什么有线程池,还要用到这样一个控制策略呢?这是因为创建默认线程池的参数导致的。默认的executorService的创建类似于Executors.newCachedThreadPool,该线程池的问题在于不会限制线程数量,如果一下子需要开启1000乃至更多的线程,依然会开启,而OkHttp这儿在Dispacther中做了控制。待会儿在下面的分析中可以看到这种控制策略。

其余的参数maxRequestsPerHost表示每个主机的最大请求数,,默认为5,比如说如果这时好多个异步请求都是请求百度上面的图片,如果达到了5,那么新的请求就会被放入到readyAsyncCalls队列中,等该主机的请求数降下去后才会再次执行。
而参数idleCallback是Dispatcher中请求数量为0时的回调,这儿的请求包含同步请求和异步请求,该参数默认为null。
在Dispatcher中,需要明白一点,尽管同步请求自己负责执行请求,但是依然会先加入到Dispatcher的同步队列,完成后从队列中移除,而异步请求则完全属于Dispatcher控制,但是有些方法是对所有请求操作的,有些则是对异步请求操作的,需要特别注意。

一般地,我们会将OkHttpClient作为单例,而Dispatcher是其一个成员,自然也是单例,所以一般整个应用的所有请求都会经过Dispatcher,不论是同步请求还是异步请求。

同步请求的执行流程

使用OkHttp进行网络同步异步操作中知道了如何进行同步请求,创建一个Request对象,然后再创建一个Call对象,调用Call对象的execute方法即可。那么就从execute方法看起。Call是一个接口,具体实现是RealCall,下面是RealCall的execute方法实现:

@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();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}

首先是设置executed标志为true,同一个Call只允许执行一次,执行多次就会抛出异常。接下来是调用OkHttpClient的dispatcher()方法获得Dispatcher对象,然后调用其executed(RealCall)方法,然后就是调用getResponseWithInterceptorChain方法同步获取响应,最后调用Dispatcher的finished方法,下面先看executed方法:

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

从代码中可以看出,Dispatcher的executed方法只是将同步请求加入到了runningSyncCalls队列中。下面再看finished方法:

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}

从上面代码中,可以看到finished方法再调用另一个finished方法,并将runningSyncCalls队列传入,具体实现如下:

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

首先是从队列中移除请求,如果不能移除,则抛出异常;在上面finished方法调用中看出传入第三个参数为false,所以不会调用promoteCalls方法,该参数用于异步请求时为true,这个下面分析异步请求时再讲。然后调用runningCallsCount统计目前还在运行的请求,最后,如果正在运行的请求数为0表示Dispatcher中没有可运行的请求了,进入Idle状态,此时如果idleCallback不为null,则调用其run方法。下面是runningCallsCount()方法的实现:

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

可以看到这个方法返回的请求包括同步请求和异步请求。
至此,同步请求的执行流程分析完成,可以看到Dispatcher只是保存了一下同步请求和移除同步请求,而对于异步请求,Dispatcher的工作就不只是这么简单了。

异步请求的执行流程

我们知道如果要发起异步请求,那么就调用Call的enqueue方法并传入回调,依然从RealCall的enqueue方法看起:

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

可以看到,依然是首先将executed参数设为true,同样地,异步请求也不可以被执行两次,然后调用Dispatcher的enqueue方法,但是这儿涉及到了一个新的类,AsyncCall。AsyncCall是RealCall的一个内部类并且继承NamedRunnable,那么首先看NamedRunnable类是什么样的,如下:

/**
* Runnable implementation which always sets its thread name.
*/
public abstract class NamedRunnable implements Runnable {
protected final String name; public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
} @Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
} protected abstract void execute();
}

可以看到NamedRunnable实现了Runnbale接口并且是个抽象类,其抽象方法是execute(),该方法是在run方法中被调用的,这也就意味着NamedRunnable是一个任务,并且其子类应该实现execute方法。下面再看AsyncCall的实现:

final class AsyncCall extends NamedRunnable {
private final Callback responseCallback; private AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl().toString());
this.responseCallback = responseCallback;
} String host() {
return originalRequest.url().host();
} Request request() {
return originalRequest;
} RealCall get() {
return RealCall.this;
} @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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}

AsyncCall实现了execute方法,首先是调用getResponseWithInterceptorChain()方法获取响应,然后获取成功后,就调用回调的onReponse方法,如果失败,就调用回调的onFailure方法。最后,调用Dispatcher的finished方法。
由于AsyncCall的execute()方法是在run中被调用的,所以getResponseWithInterceptorChain是在非调用线程中被调用的,然后得到响应后再交给Callback。
从上面的流程看出,与Dispatcher的交互主要涉及enqueue方法和finished方法,与同步请求类似。下面先看enqueue方法:

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

首先如果正在运行的异步请求的数量小于maxRequests并且与该请求相同的主机数量小于maxRequestsPerHost,也就是说符合放入runningAsyncCalls队列的要求,那么放入队列,然后将AsyncCall交给线程池;如果不符合,那么就放入到readyAsyncCalls队列中。
当线程池执行AsyncCall任务时,它的execute方法会被调用,getResponseWithInterceptorChain()会去获取响应,最后调用Dispatcher的finished方法,下面看finished方法:

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

从上面的代码可以看出,与同步请求的finished方法不同的是第一个参数传入的是正在运行的异步队列,第三个参数为true,下面再看有是三个参数的finished方法:

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

与同步请求相同的是,移除请求,获取运行数量判断是否进入了Idle状态,不同的是会调用promoteCalls()方法,下面是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.
}
}
promoteCalls方法主要负责从异步等待队列中将请求移步到异步运行队列中。主要就是遍历等待队列,并且需要满足同一主机的请求小于maxRequestsPerHost时,就移到运行队列中并交给线程池运行。

至此,分析完了同步请求和异步请求的提交流程,Dispatcher负责异步请求是放入运行队列还是等待队列中,并且在每个异步请求执行完后,需要判断是否需要把等待队列中的请求移到运行队列中并运行。不管是同步请求还是异步请求,最终都会调用getResponseWithInterceptorChain()方法进行具体的网络请求,该方法下篇博客深入理解OkHttp源码(二)——获取响应会具体介绍。

深入理解OkHttp源码(一)——提交请求的更多相关文章

  1. 深入理解OkHttp源码(三)——网络操作

    这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...

  2. 深入理解OkHttp源码(二)——获取响应

    首先先看一张流程图,该图是从拆轮子系列:拆 OkHttp 中盗来的,如下: 在上一篇博客深入理解OkHttp源码(一)——提交请求中介绍到了getResponseWithInterceptorChai ...

  3. OKHttp源码学习同步请求和异步请求(二)

    OKHttp get private void doGet(String method, String s) throws IOException { String url = urlAddress ...

  4. OKHttp源码解析

    http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...

  5. 【转载】okhttp源码解析

    转自:http://www.open-open.com/lib/view/open1472216742720.html https://blog.piasy.com/2016/07/11/Unders ...

  6. 从设计模式角度看OkHttp源码

    前言 说到源码,很多朋友都觉得复杂,难理解. 但是,如果是一个结构清晰且完全解耦的优质源码库呢? OkHttp就是这样一个存在,对于这个原生网络框架,想必大家也看过很多很多相关的源码解析了. 它的源码 ...

  7. Okhttp源码分析--基本使用流程分析

    Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Re ...

  8. apiserver源码分析——处理请求

    前言 上一篇说道k8s-apiserver如何启动,本篇则介绍apiserver启动后,接收到客户端请求的处理流程.如下图所示 认证与授权一般系统都会使用到,认证是鉴别访问apiserver的请求方是 ...

  9. 在Activiti官方源码上提交的两个bugfix

    前段时间在Activiti官方源码上提交了两个bugfix,截图为证. 第1个是BPMN model输出的bug:

随机推荐

  1. 关于运行springboot时报Unregistering JMX-exposed beans on shutdown的解决方案

    其实这个错误并不影响程序的运行,但是对于处女座的同仁来说,看到报错难免不舒服,那么看看解决方法,此错误信息的意思是说:在关机状态下未注册jmx暴露的bean. 解决方案是在入口类上加上  @Enabl ...

  2. 【python进阶】深入理解系统进程2

    前言 在上一篇[python进阶]深入理解系统进程1中,我们讲述了多任务的一些概念,多进程的创建,fork等一些问题,这一节我们继续接着讲述系统进程的一些方法及注意点 multiprocessing ...

  3. 【转】javascript 分号问题

    javascript的分号代表语句的结束符,但由于javascript具有分号自动插入规则,所以它是一个十分容易让人模糊的东西,在一般情况下,一个换行就会产生一个分号,但实际情况却不然,也就是说在ja ...

  4. JavaScrip对象

    一.JavaScript对象概述 JavaScript 中的所有事物都是对象:字符串.数值.数组.函数...此外,JavaScript 允许自定义对象.JavaScript 提供多个内建对象,比如 S ...

  5. ELK 架构之 Elasticsearch 和 Kibana 安装配置

    阅读目录: 1. ELK Stack 简介 2. 环境准备 3. 安装 Elasticsearch 4. 安装 Kibana 5. Kibana 使用 6. Elasticsearch 命令 最近在开 ...

  6. mint-ui之picker爬坑记

    picker的数据来源为动态获取时,数据无法正常渲染!因为方法不对,所以坑大了!深刻地体会到'业不精,我之过',谨以此文,深刻地记录一下踩坑及爬坑的整个过程,以便日后不再入坑,也给后来者提供一下参考 ...

  7. Spring对象生存周期(Scope)的分析

    一.Scope定义 Scope用来声明容器中管理的对象所应该处的限定场景或者说对象的存活时间,即容器在对象进入相应的Scope之前,生产并装配这些对象,在该对象不再处于这些Scope之后,容器通常会销 ...

  8. 并发容器之CopyOnWriteArraySet

    CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法. 具体 ...

  9. Fedora Linux安装deb包

    前言: 我们知道,在Linux系统上有两种主要的软件包格式,分别是RPM和deb. RPM是"RPM Package Manager(RPM软件包管理器)"的递归缩写.RPM是一种 ...

  10. ubuntu旧版本源失效的处理方法

    (1)先备份 cp /etc/apt/sources.list /etc/apt/sources.list_backup (2)更换源 在ubuntu的网站中,提供了一个源供那些不再提供支持的版本使用 ...