因為最近的一個項目用到了Retrofit2,所以也就用到了OkHttp3 ,因為Retrofit2 只支持OKHttp3了,所以除了研究Retrofit2以外還研究了OKhttp3的原理,然后在這做了記錄。
1、OKHttp3的基本使用
這里已GET請求為例,
異步請求
Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
Request request = requestBuilder.build();
Call mcall= mOkHttpClient.newCall(request);
mcall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
response.body();
}
});
}
可以看出最后通過調(diào)用Call 的enqueue方法執(zhí)行網(wǎng)絡請求,這就是執(zhí)行入口,所以源碼分析從這里開始。
2、源碼分析
Call 對象生成
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
最后生成了RealCall,所以Call 的enqueue方法,最后就會執(zhí)行RealCall的enqueue方法。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//執(zhí)行請求的地方
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
這里就要繼續(xù)看client.dispatcher()以及enqueue方法了。client就是全局設置的OkHttpClient單例,而dispatcher()則獲取請求分發(fā)器,返回的是Dispatcher類的實例。因為Dispatcher是final的,所以不能修改。然后追蹤到Dispatcher的enqueue方法。
//最大同時請求數(shù)
private int maxRequests = 64;
//每個IP最多請求數(shù)
private int maxRequestsPerHost = 5;
synchronized void enqueue(AsyncCall call) {
//從判斷條件可以看出,OKHttp3同時最多有64個請求執(zhí)行,而每一個host最多有5個請求同時執(zhí)行。其他請求都放在等待隊列中。
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(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;
}
我上一篇博客說過ThreadPoolExecutor線程池,可以看出這個線程池,沒有常存的核心線程,最多線程數(shù)為Integer.MAX_VALUE,線程空閑時存活時間為60秒,而SynchronousQueue是不保存任務的,所以只要把任務添加進去就會執(zhí)行。而 SynchronousQueue的特性如下:
原博客引用地址SynchronousQueue:同步Queue,屬于線程安全的BlockingQueue的一種,此隊列設計的理念類似于"單工模式",對于每個put/offer操作,必須等待一個take/poll操作,類似于我們的現(xiàn)實生活中的"火把傳遞":一個火把傳遞地他人,需要2個人"觸手可及"才行. 因為這種策略,最終導致隊列中并沒有一個真正的元素;這是一種pipleline思路的基于queue的"操作傳遞".
void put(E o):向隊列提交一個元素,阻塞直到其他線程take或者poll此元素.
boolean offer(E o):向隊列中提交一個元素,如果此時有其他線程正在被take阻塞(即其他線程已準備接收)或者"碰巧"有poll操作,那么將返回true,否則返回false。
E take():獲取并刪除一個元素,阻塞直到有其他線程offer/put.
boolean poll():獲取并刪除一個元素,如果此時有其他線程正在被put阻塞(即其他線程提交元素正等待被接收)或者"碰巧"有offer操作,那么將返回true,否則返回false.
既然是線程池執(zhí)行線程,那我們也就知道會執(zhí)行Runnable的run方法,那么最后會執(zhí)行AsyncCall的run方法
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
private AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl().toString());
this.responseCallback = responseCallback;
}
.....
省略
.....
@Override protected void execute() {
boolean signalledCallback = false;
try {
//執(zhí)行網(wǎng)絡請求,獲取請求結(jié)果
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 {
//異常回調(diào)
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//最后一定會執(zhí)行
client.dispatcher().finished(this);
}
}
}
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);
}
}
//實現(xiàn)請求任務的地方
protected abstract void execute();
}
所以OKHttp不是在線程池中維護線程的個數(shù),線程是一直在Dispatcher中直接控制。線程池中的請求都是運行中的請求。這也就是說線程的重用不是線程池控制的,那么線程的重用是在什么地方呢?如何知道線程池怎么重用的,想到這就應該有個基本的思路:就是上個請求結(jié)束的地方肯定是那個請求開始的地方,也就是線程重用的地方。所以找到線程重用的地方。
該方法為AsyncCall的execute方法,
@Override protected void execute() {
.....
省略
.....
finally{
//最后一定執(zhí)行
client.dispatcher().finished(this);
}
}
前面已經(jīng)分析client.dispatcher()返回的就是Dispatcher實例,所以看Dispatcher的finished的方法。
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//call為要結(jié)束的請求,calls為runningSyncCalls,所以把當前請求移除運行中隊列。
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//因為promoteCalls為true,所以執(zhí)行promoteCalls方法。
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
最后找到了promoteCalls方法。
private void promoteCalls() {
//當前運行線程數(shù)大于最大允許線程數(shù),不繼續(xù)執(zhí)行。
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
//等待允許請求隊列為空,不繼續(xù)執(zhí)行。
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//遍歷所有等待的請求任務,找到相同host同時請求數(shù)小于maxRequestsPerHost的請求,執(zhí)行該請求任務。
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//該任務必須是已經(jīng)請求的host不多于maxRequestsPerHost的
if (runningCallsForHost(call) < maxRequestsPerHost) {
//remove方法,將該請求任務從等待隊列中去除,同時執(zhí)行該任務。
i.remove();
runningAsyncCalls.add(call);
//執(zhí)行請求任務
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
這里就做到了線程的重用。