1、OkHttp总结(一)–简单使用
2、OkHttp总结(二)–分发器(Dispatcher)
在OkHttp总结(一)–简单使用 中,我们简单介绍了OkHttp以及它的简单应用,从本篇文章开始,我们将从OkHttp的分发器入手逐步地分析OkHttp的源码结构。
一、OkHttpClient 详解
OkHttpClient是OkHttp的入口,大多数的调用都是从该类开始的。
1、在OkHttpClient类内部包含了很多对象,例如:Dispatcher、List< ConnectionSpec >、CookieJar、Cache、ConnectionPool等等。其实这个类包装了很多OKHttpClient的功能模块,让该类单独提供对外的API,这种外观模式的设计十分的优雅(外观模式)。
2、而内部模块比较多,就使用了Builder模式(建造器模式)(该段出自http://www.jianshu.com/p/82f74db14a18)。
二、Request和Response 详解
1、Request
Request是对所有请求的封装,首先看一下这个类定义的字段和构造函数:
// 请求的URL地址
private final HttpUrl url;
// 请求方法(get、post等)
private final String method;
// 请求头
private final Headers headers;
// 请求体
private final RequestBody body;
// 标签
private final Object tag;
// 对于缓存的控制
private volatile CacheControl cacheControl;
private Request(Request.Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null?builder.tag:this;
}
从上诉代码中我们可以看出,Request类的构造也用到了建造者模型,通过Builder来构造自身,那么我们接着看一下Request.Builder类:
private HttpUrl url;
private String method;
private okhttp3.Headers.Builder headers;
private RequestBody body;
private Object tag;
public Builder() {
this.method = "GET";
this.headers = new okhttp3.Headers.Builder();
}
从上述代码可以看出,OkHttp默认是以get方式进行请求的。对于一个请求最主要看三点:请求行、请求头和请求实体。
(1)请求行:请求行是由请求协议、请求方法和组成的,这几个在Buidler中已经很明确了;
(2)请求头:请求头来说明服务器要使用的附加信息,例如是否可以缓存、缓存的有效时间、数据采用什么数据结构等等,在请求中主要以键-值对的形式出现。在OkHttp中主要利用Headers类进行描述。Headers也是利用建筑者模式,内部维护一个private final String[] namesAndValues;
String类型的数组,在这个数组当中,偶地址放的是key字段,在其下个地址(奇地址)放的是这个key值对value值。
(3)请求实体:请求实体是请求中要发给服务端的数据。在OkHttp中,主要由RequestBody来实现,它是是一个抽象类,有两个实现类分别是FormBody (表单提交的)和 MultipartBody(文件上传),分别对应两种不同的MIME类型FormBody :”application/x-www-form-urlencoded”和MultipartBody:”multipart/”+xxx。
2、Response
Reponse是对所有响应的封装。Response包括Headers和RequestBody,而ResponseBody是abstract的,他也有两个子类:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。
三、Dispatcher 详解
Dispatcher的创建时机是在一个OkHttpClient实例化的时候,每一个OkHttpClient对象都会持有一个Dispatcher对象,下面首先让我们看一看Dispatcher定义的字段及构造函数:
// 同时运行的请求数量的最大值(同通过set()方式更改)。
private int maxRequests = 64;
// 同时请求相同主机的请求数量最大值(同通过set()方式更改)。
private int maxRequestsPerHost = 5;
// 请求都执行完成后的回调。
private Runnable idleCallback;
// 线程池。
private ExecutorService executorService;
// 异步请求准备运行队列。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque();
// 异步请求运行队列。
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque();
// 同步请求运行队列。
private final Deque<RealCall> runningSyncCalls = new ArrayDeque();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
通过上诉代码,不难发现,对于Dispatcher是通过三个不同的双向队列来对请求进行同于的管理。对与同步请求,直接在调用线程去执行;而对于异步请求则需要将请求放入到线程池中,并在调用线程中等待结果的回调。
对于线程池,我们在这里不做过多的介绍和分析,我们只是看一下默认的线程池:
public synchronized ExecutorService executorService() {
if(this.executorService == null) {
this.executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
}
return this.executorService;
}
从构造中可以看出,该线程池是个无限大的线程池,也就是说当我们将一个任务丢给线程池的时候,若此时没有可以利用的线程就单独开一个线程去执行;当这个任务执行完以后,该线程不会被立即回收掉,而是会持续60s,若这60s内仍没有新的任务才会被线程池回收。
四、Call 详解
无论是用同步请求还是异步请求,在进行请求之前,我们都会通过OkHttpClient对象和Request对象创建一个Call对象。
首先让我们看一下Call这个接口:
public interface Call {
// 返回当前Call对象所绑定的请求。
Request request();
// 返回当前Call对象所绑定的请求。
Response execute() throws IOException;
// 发起异步网络请求。
void enqueue(Callback var1);
// 关闭当前请求。
void cancel();
// 判断当前请求是否在执行。
boolean isExecuted();
// 判断当前请求是否被关闭。
boolean isCanceled();
public interface Factory {
Call newCall(Request var1);
}
}
该接口中各个函数的功能请参照注解。其中,我们会发现在接口Call的内部还有一个内部接口Factory,该接口用来返回一个Call对象。在OkHttpClient类的定义中实现了该接口,并在我们进行网络请求的时候,调用了newCall()方法返回了一个Call对象。
Call call = okHttpClient.newCall(request);
进入newCall():
public Call newCall(Request request) {
return new RealCall(this, request);
}
其内部利用OkHttpClient对象和对应请求对象创建了一个RealCall对象并返回。RealCall是Call对象在OkHttp中的唯一实现类(注:AsyncCall不是Call的实现类,实际上它实现了Runnable的接口,它只是RealCall的内部类,为了给Call提供异步操作)。
五、发起网络请求
在我们得到了Call对象以后,就可以通过execute()和enqueue()两个方法发起网络请求了。
1、首先,让我们先看一下同步请求方法:execute()。
在我们调用execute()的时候,实际上是调用RealCall的execute(),先上源码:
public Response execute() throws IOException {
synchronized(this) {
if(this.executed) {
throw new IllegalStateException("Already Executed");
}
// 线程同步方法,置执行标记位,指示当前同步请求进入执行状态。
this.executed = true;
}
Response var2;
try {
// 1、调用dispatcher的executed()。
this.client.dispatcher().executed(this);
// 2、在当前进程直接进行网络的请求。
Response result = this.getResponseWithInterceptorChain();
var2 = result;
} finally {
// 3、请求结束,通知dispatcher。
this.client.dispatcher().finished(this);
}
return var2;
}
在RealCall的execute()中主要进行了三步操作:
第一步:调用dispatcher的executed();
第二步:在当前进程直接进行网络的请求;
第三步:请求结束,通知dispatcher。
先看一下dispatcher的executed():
synchronized void executed(RealCall call) {
this.runningSyncCalls.add(call);
}
方法很简单,就是将当前的call对象加入到正在执行的同步请求队列中。
在Dispatcher中维护了三个双向队列结构runningSyncCalls、readyAsyncCalls和runningAsyncCalls。其中runningSyncCalls对应正在执行的同步请求队列;readyAsyncCalls对应正在等待执行的异步请求队列;runningAsyncCalls对应正在执行的异步请求队列。
getResponseWithInterceptorChain()
的方法我们暂时不做讨论,会在后续的文章中进行详细的解析。
最后我们看一下this.client.dispatcher().finished(this);
void finished(RealCall call) {
this.finished(this.runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized(this) {
// 1、从对应队列中移除该请求。
if(!calls.remove(call)) {
throw new AssertionError("Call wasn\'t in-flight!");
}
// 2、进行异步请求中,准备运行队列的请求放入到运行队列中。对于同步请求promoteCalls的这个参数为false,并不执行if语句块中的操作。
if(promoteCalls) {
// 该操作会在异步请求当中进行详细分析。
this.promoteCalls();
}
// 统计当前正在运行请求总数(包括同步请求和异步请求)。
runningCallsCount = this.runningCallsCount();
idleCallback = this.idleCallback;
}
// 若当前运行总数为0,则进行对应的回调。
if(runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
2、异步请求方法:enqueue()
public void enqueue(Callback responseCallback) {
synchronized(this) {
if(this.executed) {
throw new IllegalStateException("Already Executed");
}
// 线程同步方法,置执行标记位,指示当前请求进入执行或者准备执行的状态。
this.executed = true;
}
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
在我们进行异步网络请求的时候,是直接将请求交给dispatcher()进行处理。进入到dispatcher().enqueue()继续分析:
synchronized void enqueue(AsyncCall call) {
// 根据两个条件进行判断,当前请求是放入异步运行队列还是放入到异步等待运行的队列当中。
// 条件1:当前运行队列中的总数量是否小于所规定的最大值;
// 条件2:根据请求call的host,计算出当前运行队列中请求host主机的总数量,判断该数量是否小于所规定的最大值。
if(this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
// 将当前请求放入到异步请求运行队列当中。
this.runningAsyncCalls.add(call);
// 放入到线程池当中进行请求。
this.executorService().execute(call);
} else {
// 将当前请求放入到异步请求准备运行队列当中。
this.readyAsyncCalls.add(call);
}
}
在将call放入到线程池之后。实际上执行的是call的execute(),即AsyncCall类的execute():
protected void execute() {
boolean signalledCallback = false;
try {
// 进行真正的请求,和同步方式一样,后续进行详细的分析。
Response e = RealCall.this.getResponseWithInterceptorChain();
// 重定向关闭。在重定向拦截器时会详细讨论。
if(RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
// 置标记位,并回调Callback.onFailure()。
signalledCallback = true;
this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
// 重定向成功。置标记位,并回调Callback.onResponse()。
signalledCallback = true;
this.responseCallback.onResponse(RealCall.this, e);
}
} catch (IOException var6) {
if(signalledCallback) {
Platform.get().log(4, "Callback failure for " + RealCall.this.toLoggableString(), var6);
} else {
this.responseCallback.onFailure(RealCall.this, var6);
}
} finally {
// 请求结束,通知dispatcher()。
RealCall.this.client.dispatcher().finished(this);
}
}
再来看看dispatcher.finished()(这个函数与同步调用的dispatcher.finished()不同,属于重载方法,传入的参数类型不同)
void finished(AsyncCall call) {
this.finished(this.runningAsyncCalls, call, true);
}
此时的调用和同步请求的调用相同,只不过中间多了调用promoteCalls()的过程。
private void promoteCalls() {
// 判断当前运行的总数是否小于所规定的最大值。小于则进入if代码块。
if(this.runningAsyncCalls.size() < this.maxRequests) {
// 当前异步等待队列是否为空,不为空进入if代码块。
if(!this.readyAsyncCalls.isEmpty()) {
// 获取异步等待队列的遍历器
Iterator i = this.readyAsyncCalls.iterator();
// 进入do-while循环
do {
// 遍历完毕返回。
if(!i.hasNext()) {
return;
}
AsyncCall call = (AsyncCall)i.next();
// 当前遍历请求的主机数量是否小于最大值,小于则进入if代码块。
if(this.runningCallsForHost(call) < this.maxRequestsPerHost) {
// 首先,先将该消息从异步准备请求队列当中移除。
i.remove();
// 然后,再将该请求放入异步运行队列当中并且开始执行。
this.runningAsyncCalls.add(call);
this.executorService().execute(call);
}
} while(this.runningAsyncCalls.size() < this.maxRequests);// 判断当前运行的请求总数量是否小于所规定的最大值,若小于则进入do语句块继续循环,若不满足跳出循环。
}
}
}
dispatcher通过promoteCalls(),在一个异步请求结束后,将符合条件的请求从准备运行队列放入运行队列中去执行。
六、总结
通过上述分析,我们可以了解到OkHttp的请求是从OkHttpClient类开始的,在这类封装了OKHttp的大多数功能,比如分发器、连接池等。在OkHttp中分别对Request和Response进行了封装,并通过Request对象构建了Call对象,而真正的请求是由分发器进行维护和分发的。对于同步请求,分发器直接在调用的线程中进行请求;而对于异步请求,主要是利用线程池进行,在请求结束后对准备请求的队列进行筛选,将合适的请求放入到运行队列当中并对异步请求进行相应的回调,通知调用者。