OkHttp3源码解析(一)分发器Dispatcher原理分析
OkHttp3源码解析(二)五大拦截器原理分析
OkHttp 3.10.0版本,最新OkHttp为:4.0.1逻辑与3版本并没有太大变化,但是改为kotlin实现。
OkHttp介绍
OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。
优点:
- 支持Spdy、Http1.X、Http2、Quic以及WebSocket
- 连接池复用底层TCP(Socket),减少请求延时
- 无缝的支持GZIP减少数据流量
- 缓存响应数据减少重复的网络请求
- 请求失败自动重试主机的其他ip,自动重定向
- …….
异步get请求
String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(url)
.get()//默认就是GET请求,可以不写
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: ");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "onResponse: " + response.body().string());
}
});
异步Post请求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.protocol() + " " +response.code() + " " + response.message());
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
Log.d(TAG, headers.name(i) + ":" + headers.value(i));
}
Log.d(TAG, "onResponse: " + response.body().string());
}
});
1、OkHttp的调用流程
OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、Response,但是框架内部进行大量的逻辑处理。
所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依靠分发器来调配请求任务。
分发器:内部维护队列与线程池,完成请求调配;
拦截器:五大默认拦截器完成整个请求过程。
2、分发器:异步请求的工作流程
Call call = client.newCall(request);
Response response = call.enqueue();
final class RealCall implements Call
=> RealCall#enqueue
=> client.dispatcher().enqueue(new AsyncCall(responseCallback));
=> Dispatcher#enqueue
当我们调用client.dispatcher().enqueue(new AsyncCall(responseCallback));的时候会进入Dispatcher的enqueue方法,
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
一开始会传一个AsyncCall到dispatcher中,在dispatcher的enqueue里,AsyncCall可能会加入到正在运行的runningAsyncCalls队列或者加入到正在等待的readyAsyncCalls队列,如果加入到runningAsyncCalls队列的话,会被进一步提交到ThreadPool线程池中,因此AsyncCall肯定是一个实现了Runnable接口的“任务”。当这个任务在线程池中执行完毕的时候,会调用dispatcher里的finished方法,在finished中又会调用到dispatcher里的promoteCalls方法,去遍历readyAsyncCalls队列,找出满足条件的任务,加入到runningAsyncCalls队列里,又开始新一轮的执行。这就是分发器里整个异步请求的工作流程。
那么问题来了:
- 1、分发器dispatcher如何决定将请求放入runningAsyncCalls还是readyAsyncCalls?
- 2、从readyAsyncCalls移动到runningAsyncCalls的条件是什么?
- 3、分发器dispatcher线程池是怎么工作的?
3、源码分析
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
当正在请求的任务数量runningAsyncCalls小于maxRequests(异步请求同时存在的最大请求默认64个),这个maxRequests数量也是可以通过setMaxRequests方法来设置的,但最小不能小于1个,不然会报异常。另外一个限制是:异步请求同一域名同时存在的最大请求runningCallsForHost不能大于maxRequestsPerHost(默认是5个),当然maxRequestsPerHost也是能通过setMaxRequestsPerHost来自己重新设置的。如果满足这两个条件,就会把这个AsyncCall也就是Runnable加入到runningAsyncCalls队列中,同时加入到executorService线程池中,并且调用execute开始执行这个任务。
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher",
false));
}
return executorService;
}
进入到这个线程池,首先核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。而最大线程Integer.MAX_VALUE
与等待队列SynchronousQueue
的组合能够得到最大的吞吐量。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。一般来说,等待队列BlockingQueue
有:ArrayBlockingQueue
、LinkedBlockingQueue
与SynchronousQueue
。那么为什么要使用SynchronousQueue
: 无容量的队列。其实,使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合Integer.MAX_VALUE
就实现了真正的无等待。
回到之前的代码,如果不满足,加入到等待队列readyAsyncCalls。那么这个Runnable真正在哪里执行它的run方法呢?
final class AsyncCall extends NamedRunnable {
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();
}
我们看到AsyncCall是继承自NamedRunnable,NamedRunnable又实现了Runnable接口,在NamedRunnable的run方法里调用了execute方法,execute是个抽象方法,所以它真正实现的地方就是在AsyncCall的execute方法里
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
所以,我们先不去关心这个任务AsyncCall到底是怎么执行的。我们看到最后也就是任务跑完了,不管成功或者失败,一定会去执行finally里的代码块client.dispatcher().finished(this)表示这个任务结束了。所以我们先去看看dispatcher里的finished到底是怎么执行的。
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
private void finished(Deque 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从calls里(也就是传进来的runningAsyncCalls)移除。然后执行promoteCalls方法
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator 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.
}
}
一开始也是判断maxRequests和maxRequestsPerHost,然后遍历readyAsyncCalls队列,把任务从readyAsyncCalls里取出加入到runningAsyncCalls里。到这里,分发器里整个异步请求的工作流程源码分析完成了。同步请求就不展开讲了,很简单,收到任务时加入runningSyncCalls队列,执行完毕从runningSyncCalls队列移除。
最后,总结一下:
-
Q1: 如何决定将请求放入ready还是running?
如果当前正在请求数不小于64放入ready;如果小于64,但是已经存在同一域名主机的请求5个放入ready!
-
Q2: 从running移动ready的条件是什么?
每个请求执行完成就会从running移除,同时进行第一步相同逻辑的判断,决定是否移动!
-
Q3: 分发器线程池的工作行为?
无等待,最大并发