OkHttp 源碼概述
概覽
OkHttp是Android開發(fā)中一個支持Http協(xié)議的高效網(wǎng)絡請求框架,支持同步請求和異步請求。
我們通過配置Request的url,okhttp就可以幫我們配置好其它所有關于http協(xié)議通訊的所有事情,包括緩存(需要手動開啟)、添加請求頭、壓縮文件(GZIP)、自動重連、重定向、cookie、連接共享等等。
需要注意的是,OkHttp完全沒有使用java內(nèi)置的HttpUrlConnection。而是直接基于傳輸層的socket,重寫了一個Android上的Http請求客戶端,更加高效。

源碼
OkHttp的源碼主要分為兩部分:走攔截器之前的部分,和攔截器部分。攔截器是OkHttp最精髓的地方,同時支持擴展,添加自定義攔截器。
Okio
OkHttp對于socket的寫入數(shù)據(jù)和讀取數(shù)據(jù)是基于Okio的,為什么OkHttp不用java提供的IO流而是使用OKio呢?
- 較低的cpu和內(nèi)存消耗,okio使用segment組成的鏈表來存儲數(shù)據(jù),segment的內(nèi)部實際就是字節(jié)數(shù)組。segment通過對復制操作共享一個字節(jié)數(shù)組等操作,來節(jié)省內(nèi)存和存儲數(shù)據(jù)
- API便捷,直接就具有讀取字符串、整數(shù)等數(shù)據(jù)。無需IO流的多層包裝
- 提供了Gzip、加密操作MD5和SHA-1功能
DiskLruCache
只是一個1000多行的java文件,但功能很強大。內(nèi)部通過一個LinkedHashMap,來達到刪除時先刪除不常用的數(shù)據(jù)的功能。LinkedHashMap類本身就具有這個功能。通過LinkedHashMap的構造函數(shù),將參數(shù)accessOrder指定為true,這樣每次當我們訪問LinkedHashMap的元素時,就會將這個元素移至隊尾,當檢測到指定大小的空間已滿時,DiskLruCache的cleanupRunnable這個清理線程就會啟動,從LinkedHashMap的隊頭(不常用元素的一段)開始清理,直到size < maxSize ;也是就緩存空間小于我們的設定值,就停止清理。
DiskLruCache有一個日志文件,記錄我們對緩存的每次操作。這里有一個Dirty和Clean的概念。clean代表可讀,dirty代表正在被寫入。所以每當一個entry出現(xiàn)dirty的操作時,之后往往伴隨著clean操作或者delete操作
每一個entry有valuecount * 2 個文件。OkHttp中valuecount的值默認為2
注意,空間滿,清理的時候是按照當次運行程序的排序來清理的,而不是按照磁盤的整個訪問次序清理的。
每次應用啟動時加載DiskLruCache的時候會根據(jù)日志內(nèi)容初始化LinkedHashMap鏈表,填充內(nèi)容。
攔截器之前的部分
這部分的內(nèi)容很簡單,沒什么說的,
從Dispatcher的成員變量就可以看出他的作用
/** Executes calls. Created lazily. */
private @Nullable 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<>();
dispatcher內(nèi)部維護三個隊列,一個同步隊列。兩個異步隊列:正準備和正在運行。然后通過一個線程池來執(zhí)行異步請求。
不管是同步請求還是異步請求,真正的網(wǎng)絡請求邏輯就是先加入running的隊列,之后通過getResponseWithInterceptorChain()這個方法完成網(wǎng)絡請求的,也就是攔截器。(當異步執(zhí)行時,會先做判斷:)
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
okhttp對異步最多支持64個同時請求,而且對同一個host 的url只支持5個同時請求。
不管是同步還是異步,請求完成之后都會執(zhí)行dispatcher.finished()方法,finished方法調(diào)用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.
}
}
這里的邏輯就是遍歷readyAsyncCalls,也就是準備隊列,如果當前正在執(zhí)行的請求小于64個或者對同一個hosturl的請求小于5個,就把這個請求移出準備隊列,并加入running隊列,調(diào)用線程池去執(zhí)行這個請求。
攔截器部分
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
上面這一段代碼就是OkHttp的核心,完整的攔截器流程。從Request向下遞,至Reponse向上歸
責任鏈模式
每一個攔截器的intercept方法,被chain.proceed(request)攔截成兩部分,chain.proceed(request)上面的第一次發(fā)出請求依次往下執(zhí)行 遞 的過程,chain.proceed(request)下方的代碼在請求完成執(zhí)行 歸 的過程,就像這張圖

攔截器的職責
從上到下一共有五個攔截器,他們的職責簡單總結(jié)一下,需要注意的是每一次網(wǎng)絡請求都會經(jīng)歷每個攔截器兩次,請求報文一次,響應報文一次。
-
RetryAndFollowUpInterceptor:根據(jù)Http協(xié)議的策略、Http請求和響應報文,來自動進行重定向與重連。 -
BridgeInterceptor:“ 這個攔截器的職能相對簡單,就是對請求報文添加一些必要的headers,比如gzip,Host,Content-Length,Transfer-Encoding,Connection,User-Agent等等,對回來的響應報文也會刪除一些headers。 -
CacheInterceptor: 這一個攔截器如其名,就是緩存策略相關,緩存策略也是根據(jù)HTTP協(xié)議來制定的。 -
ConnectInterceptor:這個攔截器是真正通過socket與服務器建立連接的地方 -
CallServerInterceptor:通過Okio與上一個攔截器建立起連接的socket向服務器發(fā)送請求報文,并且接受響應報文并將之構建成Respose一層一層傳遞給上層的攔截器。
自定義攔截器
而我們可以自己配置兩種攔截器,interceptors和networkInterceptors,OkHttp官方分別叫他們 應用攔截器 和 網(wǎng)絡攔截器 。從getResponseWithInterceptorChain方法中可以看到二者的順序,分別是第一個攔截器和倒數(shù)第二個攔截器,具體說一下兩個攔截器的區(qū)別:

應用攔截器
- 這里得到的請求沒有BridgeInterceptor的重定向和BridgeInterceptor給它添加請求頭,所以這里得到的請求報文是最原始的。相對應的,這里得到的響應報文也是最終的客戶端接收到的響應報文。
- 這個攔截器是第一個執(zhí)行的,所以在這里有能力去決定是否要執(zhí)行
Chain.proceed(),讓請求向下傳遞,通過這個特點可以實現(xiàn)一些有趣的功能
網(wǎng)絡攔截器
- 這里能夠修改最終向服務器發(fā)送的響應報文
- 緩存得到的response不會走這個攔截器,因為他在cacheIntercepter那里就返回了
- 可以獲得服務器返回的最原始的響應報文
自定義攔截器
可以通過自定義攔截器來完成,動態(tài)添加Token,網(wǎng)絡信息日志打印等功能
,下面就是一個簡單的網(wǎng)絡請求信息日志打印攔截器的例子。
public class LoggingInterceptor extends BaseInterceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
LogUtils.i(TAG, "intercept: " + String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
LogUtils.i(TAG, "intercept: " + String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
自定義攔截器的核心是要理解責任鏈模式和遞歸,在chain.proceed(request) 上方的代碼是 遞 的過程,這個方法return之后是 歸 的過程,代碼寫在 遞 過程里,還是寫在 歸 過程里,是需要根據(jù)場景具體分析的