OkHttp原始碼之同步、非同步的區別
對於okhttp來說,大家都知道有同步和非同步兩種模式,所謂同步就是在發起請求的執行緒完成整個網路的連線傳輸過程,非同步是新建一個執行緒完成這些流程,那麼不知大家有沒有想過,非同步是不是就新的執行緒中直接呼叫同步的請求方法實現的呢?
我們今天就來探討這個問題。
首先,我們看下同步請求方法:
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } //初始化異常撲捉器 captureCallStackTrace(); //進度監聽 eventListener.callStart(this); try { //只是簡單的加入到了dispatcher的同步請求佇列,沒有任何作用 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 { //將自己從dispatcher的同步佇列中移除 client.dispatcher().finished(this); } }
上面的註釋將每一步的作用都說的很清楚,核心就是那句getResponseWithInterceptorChain();這裡是獲取真正結果的。
那麼非同步的是怎麼樣的呢?
@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)); }
這裡我們看到這裡核心就是封裝了一個AsyncCall中,塞到了執行緒排程器中,真正的請求是線上程排程器中執行的,我們看下執行緒排程器中的相關程式碼:
synchronized void enqueue(AsyncCall call) { //省略不相干程式碼 executorService().execute(call); //省略不相干程式碼 }
可以看到,真正的非同步請求是通過封裝的AsyncCall來執行的,而且是通過一個執行緒池執行的,那麼我們可以大膽猜測AsyncCall肯定有一個run()方法,核心請求在那裡。
我們繼續看AsyncCall:
final class AsyncCall extends NamedRunnable {
可以看到,繼承了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(); }
整個NamedRunnable非常簡單,主要就是為了給執行緒設定名字而已,我們重點關注的是在run()方法中呼叫了execute()方法,所以,AsyncCall的核心方法在execute()中:
@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); } }
可以看到這裡並沒有直接呼叫RealCall的同步請求方法execute(),而是類似的程式碼又寫了一遍,這是為什麼呢?我們來對比下兩個方法有什麼不一樣
先看同步:
- 修改標記位
- 初始化異常捕捉器
- 進度監聽器回撥
- 獲取請求結果
再看非同步:
- 獲取請求結果
- 判斷請求是否結束,回撥結果
差異很明顯,我們現在要分析的是造成這些差別的原因,首先是同步請求中有的:修改標記位、初始化異常撲捉器、進度監聽器回撥
這個其實沒有在AsyncCall中執行,而是在RealCall的enqueue()方法中執行的:
@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)); }
也就是說這三點其實並沒有核心區別,只是在不同的地方呼叫了而已,那麼真正的區別就是非同步請求多了一個:判斷請求是否結束,然後回撥。
其實仔細想想,也不難理解,對於同步請求來說,這個請求只在當前執行緒進行,沒有其他執行緒操作這個請求,那麼請求結束後也不存在被取消的情況,然而非同步請求不一樣,在子執行緒請求的過程中,這個請求有可能在其他執行緒被取消了,所以加了這樣的判斷是非常合理的。
總結
其實沒什麼好總結的,之所以非同步請求沒有直接呼叫同步請求方法就是因為非同步多了一個判斷而已,done!