okhttp 核心剖析
基本使用
從使用方法出發,首先是怎麼使用,其次是我們使用的功能在內部是如何實現的.建議大家下載 OkHttp 原始碼之後,跟著本文,過一遍原始碼。
官方部落格栗子:http://square.github.io/okhttp/#examples
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
Request、Response、Call 基本概念
上面的程式碼中涉及到幾個常用的類:Request、Response和Call。下面分別介紹:
Request
每一個HTTP請求包含一個URL、一個方法(GET或POST或其他)、一些HTTP頭。請求還可能包含一個特定內容型別的資料類的主體部分。
Response
響應是對請求的回覆,包含狀態碼、HTTP頭和主體部分。
Call
OkHttp使用Call抽象出一個滿足請求的模型,儘管中間可能會有多個請求或響應。執行Call有兩種方式,同步或非同步
第一步:建立 OkHttpClient物件,進行原始碼分析:
OkHttpClient client = new OkHttpClient();`
通過okhttp原始碼分析,直接建立的 OkHttpClient物件並且預設構造builder物件進行初始化
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
......
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
}
}
第二步:接下來發起 HTTP 請求
Request request = new Request.Builder().url("url").build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
第二步:程式碼流程分析:
Request request = new Request.Builder().url("url").build();
初始化構建者模式和請求物件,並且用URL替換Web套接字URL。
public final class Request {
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
public Builder url(String url) {
......
// Silently replace web socket URLs with HTTP URLs.
if (url.regionMatches(true, 0, "ws:", 0, 3)) {
url = "http:" + url.substring(3);
} else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
url = "https:" + url.substring(4);
}
HttpUrl parsed = HttpUrl.parse(url);
......
return url(parsed);
}
public Request build() {
......
return new Request(this);
}
}
第三步:方法解析:
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
原始碼分析:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
@Override
public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
}
RealCall實現了Call.Factory介面建立了一個RealCall的例項,而RealCall是Call介面的實現。
非同步請求的執行流程
final class RealCall implements Call {
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
}
由以上原始碼得知:
1) 檢查這個 call 是否已經被執行了,每個 call 只能被執行一次,如果想要一個完全一樣的 call,可以利用 call#clone 方法進行克隆。
2)利用 client.dispatcher().enqueue(this) 來進行實際執行,dispatcher 是剛才看到的 OkHttpClient.Builder 的成員之一
3)AsyncCall是RealCall的一個內部類並且繼承NamedRunnable,那麼首先看NamedRunnable類是什麼樣的,如下:
public abstract class NamedRunnable implements Runnable {
......
@Override
public final void run() {
......
try {
execute();
}
......
}
protected abstract void execute();
}
可以看到NamedRunnable實現了Runnbale介面並且是個抽象類,其抽象方法是execute(),該方法是在run方法中被呼叫的,這也就意味著NamedRunnable是一個任務,並且其子類應該實現execute方法。下面再看AsyncCall的實現:
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
......
final class RealCall implements Call {
@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) {
......
responseCallback.onFailure(RealCall.this, e);
} finally {
client.dispatcher().finished(this);
}
}
AsyncCall實現了execute方法,首先是呼叫getResponseWithInterceptorChain()方法獲取響應,然後獲取成功後,就呼叫回撥的onReponse方法,如果失敗,就呼叫回撥的onFailure方法。最後,呼叫Dispatcher的finished方法。
關鍵程式碼:
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
和
responseCallback.onResponse(RealCall.this, response);
走完這兩句程式碼會進行回撥到剛剛我們初始化Okhttp的地方,如下:
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
核心重點類Dispatcher執行緒池介紹
public final class Dispatcher {
/** 最大併發請求數為64 */
private int maxRequests = 64;
/** 每個主機最大請求數為5 */
private int maxRequestsPerHost = 5;
/** 執行緒池 */
private ExecutorService executorService;
/** 準備執行的請求 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** 正在執行的非同步請求,包含已經取消但未執行完的請求 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在執行的同步請求,包含已經取消單未執行完的請求 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
在OkHttp,使用如下構造了單例執行緒池
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;
}
構造一個執行緒池ExecutorService:
executorService = new ThreadPoolExecutor(
//corePoolSize 最小併發執行緒數,如果是0的話,空閒一段時間後所有執行緒將全部被銷燬
0,
//maximumPoolSize: 最大執行緒數,當任務進來時可以擴充的執行緒最大值,當大於了這個值就會根據丟棄處理機制來處理
Integer.MAX_VALUE,
//keepAliveTime: 當執行緒數大於corePoolSize時,多餘的空閒執行緒的最大存活時間
60,
//單位秒
TimeUnit.SECONDS,
//工作佇列,先進先出
new SynchronousQueue<Runnable>(),
//單個執行緒的工廠
Util.threadFactory("OkHttp Dispatcher", false));
可以看出,在Okhttp中,構建了一個核心為[0, Integer.MAX_VALUE]的執行緒池,它不保留任何最小執行緒數,隨時建立更多的執行緒數,當執行緒空閒時只能活60秒,它使用了一個不儲存元素的阻塞工作佇列,一個叫做"OkHttp Dispatcher"的執行緒工廠。
也就是說,在實際執行中,當收到10個併發請求時,執行緒池會建立十個執行緒,當工作完成後,執行緒池會在60s後相繼關閉所有執行緒。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
從上述原始碼分析,如果當前還能執行一個併發請求,則加入 runningAsyncCalls ,立即執行,否則加入 readyAsyncCalls 佇列。
Dispatcher執行緒池總結
1)排程執行緒池Disptcher實現了高併發,低阻塞的實現
2)採用Deque作為快取,先進先出的順序執行
3)任務在try/finally中呼叫了finished函式,控制任務佇列的執行順序,而不是採用鎖,減少了編碼複雜性提高效能
這裡是分析OkHttp原始碼,並不詳細講執行緒池原理,如對執行緒池不瞭解請參考如下連結
點我,執行緒池原理,在文章效能優化最後有視訊對執行緒池原理講解
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} finally {
client.dispatcher().finished(this);
}
當任務執行完成後,無論是否有異常,finally程式碼段總會被執行,也就是會呼叫Dispatcher的finished函式
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
從上面的程式碼可以看出,第一個引數傳入的是正在執行的非同步佇列,第三個引數為true,下面再看有是三個引數的finished方法:
private <T> void finished(Deque<T> 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從佇列runningAsyncCalls中移除後,獲取執行數量判斷是否進入了Idle狀態,接著執行promoteCalls()函式,下面是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.
}
}
主要就是遍歷等待佇列,並且需要滿足同一主機的請求小於maxRequestsPerHost時,就移到執行佇列中並交給執行緒池執行。就主動的把快取佇列向前走了一步,而沒有使用互斥鎖等複雜編碼
核心重點getResponseWithInterceptorChain方法
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);
return chain.proceed(originalRequest);
}
1)在配置 OkHttpClient 時設定的 interceptors;
2)負責失敗重試以及重定向的 RetryAndFollowUpInterceptor;
3)負責把使用者構造的請求轉換為傳送到伺服器的請求、把伺服器返回的響應轉換為使用者友好的響應的 BridgeInterceptor;
4)負責讀取快取直接返回、更新快取的 CacheInterceptor;
5)負責和伺服器建立連線的 ConnectInterceptor;
6)配置 OkHttpClient 時設定的 networkInterceptors;
7)負責向伺服器傳送請求資料、從伺服器讀取響應資料的 CallServerInterceptor。
OkHttp的這種攔截器鏈採用的是責任鏈模式,這樣的好處是將請求的傳送和處理分開,並且可以動態新增中間的處理方實現對請求的處理、短路等操作。
從上述原始碼得知,不管okhttp有多少攔截器最後都會走,如下方法:
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
從方法名字基本可以猜到是幹嘛的,呼叫 chain.proceed(originalRequest); 將request傳遞進來,從攔截器鏈裡拿到返回結果。那麼攔截器Interceptor是幹嘛的,Chain是幹嘛的呢?繼續往下看RealInterceptorChain
RealInterceptorChain類
下面是RealInterceptorChain的定義,該類實現了Chain介面,在getResponseWithInterceptorChain呼叫時好幾個引數都傳的null。
public final class RealInterceptorChain implements Interceptor.Chain {
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
......
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
......
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
......
return response;
}
protected abstract void execute();
}
主要看proceed方法,proceed方法中判斷index(此時為0)是否大於或者等於client.interceptors(List )的大小。由於httpStream為null,所以首先建立next攔截器鏈,主需要把索引置為index+1即可;然後獲取第一個攔截器,呼叫其intercept方法。
Interceptor 程式碼如下:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}
BridgeInterceptor
BridgeInterceptor從使用者的請求構建網路請求,然後提交給網路,最後從網路響應中提取出使用者響應。從最上面的圖可以看出,BridgeInterceptor實現了適配的功能。下面是其intercept方法:
public final class BridgeInterceptor implements Interceptor {
......
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
//如果存在請求主體部分,那麼需要新增Content-Type、Content-Length首部
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
/** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
}
從上面的程式碼可以看出,首先獲取原請求,然後在請求中新增頭,比如Host、Connection、Accept-Encoding引數等,然後根據看是否需要填充Cookie,在對原始請求做出處理後,使用chain的procced方法得到響應,接下來對響應做處理得到使用者響應,最後返回響應。接下來再看下一個攔截器ConnectInterceptor的處理。
public final class ConnectInterceptor implements Interceptor {
......
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
實際上建立連線就是建立了一個 HttpCodec 物件,它利用 Okio 對 Socket 的讀寫操作進行封裝,Okio 以後有機會再進行分析,現在讓我們對它們保持一個簡單地認識:它對 java.io 和 java.nio 進行了封裝,讓我們更便捷高效的進行 IO 操作。
CallServerInterceptor
CallServerInterceptor是攔截器鏈中最後一個攔截器,負責將網路請求提交給伺服器。它的intercept方法實現如下:
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
// being reused. Otherwise we're still obligated to transmit the request body to leave the
// connection in a consistent state.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
從上面的程式碼中可以看出,首先獲取HttpStream物件,然後呼叫writeRequestHeaders方法寫入請求的頭部,然後判斷是否需要寫入請求的body部分,最後呼叫finishRequest()方法將所有資料重新整理給底層的Socket,接下來嘗試呼叫readResponseHeaders()方法讀取響應的頭部,然後再呼叫openResponseBody()方法得到響應的body部分,最後返回響應。
最後總結
OkHttp的底層是通過Java的Socket傳送HTTP請求與接受響應的(這也好理解,HTTP就是基於TCP協議的),但是OkHttp實現了連線池的概念,即對於同一主機的多個請求,其實可以公用一個Socket連線,而不是每次傳送完HTTP請求就關閉底層的Socket,這樣就實現了連線池的概念。而OkHttp對Socket的讀寫操作使用的OkIo庫進行了一層封裝。
相關文章
- OkHttp簡單剖析HTTP
- 深入剖析框架之OkHttp篇框架HTTP
- 深入剖析OkHttp系列(一) 來自官方的OkHttp設計思想(中英互譯)HTTP
- Linux 核心剖析Linux
- Laravel核心概念剖析Laravel
- Android 核心剖析Android
- OkHttp 原始碼剖析系列(四)——連線建立概述HTTP原始碼
- OkHttp 原始碼剖析系列(三)——快取機制HTTP原始碼快取
- OkHttp 原始碼剖析系列(五)——代理路由選擇HTTP原始碼路由
- 深入剖析OkHttp系列(四) 來自官方的HTTPSHTTP
- 深入剖析OkHttp系列(五) 來自官方的事件機制HTTP事件
- spark核心原始碼深度剖析Spark原始碼
- OkHttp 原始碼剖析系列(二)——攔截器整體流程分析HTTP原始碼
- Java執行緒池核心原理剖析Java執行緒
- Android小知識-剖析OkHttp中的五個攔截器(下篇)AndroidHTTP
- Android小知識-剖析OkHttp中的五個攔截器(上篇)AndroidHTTP
- Android小知識-剖析OkHttp中的五個攔截器(中篇)AndroidHTTP
- Laravel 核心--深入剖析 Laravel 框架門面模式Laravel框架模式
- OkHttp 原始碼剖析系列(七)——請求的發起及響應的讀取HTTP原始碼
- OkHttp 原始碼剖析系列(六)——連線複用機制及連線的建立HTTP原始碼
- 深入剖析OkHttp系列(二) 來自官方的開發使用手冊(中英互譯)HTTP
- 深入剖析OkHttp系列(三) 來自官方的攔截器設計(中英互譯)HTTP
- okhttpHTTP
- 為什麼寫《Tomcat核心設計剖析》Tomcat
- (PHP7核心剖析-11) 模組擴充套件PHP套件
- jQuery 原始碼剖析(一) - 核心功能函式jQuery原始碼函式
- Flink 核心元件 內部原理 多圖剖析元件
- 【Visual Leak Detector】核心原始碼剖析(VLD 2.5.1)原始碼
- 【Visual Leak Detector】核心原始碼剖析(VLD 1.0)原始碼
- ZStack原始碼剖析之核心庫鑑賞——Defer原始碼
- Qt核心剖析: 尋找 QObject 的原始碼薦QTObject原始碼
- OkHttp 原始碼剖析系列(一)——請求的發起及攔截器機制概述HTTP原始碼
- 徹底理解OkHttp - OkHttp 原始碼解析及OkHttp的設計思想HTTP原始碼
- Linux(核心剖析):19---中斷總體概述Linux
- 萬字剖析Ribbon核心元件以及執行原理元件
- 端智慧研發核心套件:MNN 工作臺深度剖析套件
- Guava Cache:核心引數深度剖析和原始碼分析Guava原始碼
- OkHttp解析HTTP