okhttp介紹
OkHttp是一個非常優秀的網絡請求架構,已被谷歌加入到Android的源碼中。目前比較流行的Retrofit也是預設使用OkHttp的。是以OkHttp的源碼是一個不容錯過的學習資源,學習源碼之前,務必熟練使用這個架構,否則就是跟自己過不去。
okhttp優點
- 支援HTTP2/SPDY黑科技
- socket自動選擇最好路線,并支援自動重連
- 擁有自動維護的socket連接配接池,減少握手次數
- 擁有隊列線程池,輕松寫并發
-
擁有Interceptors輕松處理請求與響應(比如透明GZIP壓-
縮,LOGGING)
- 實作基于Headers的緩存政策
至于為什麼有這麼多優點,各位看官老爺在下面的源碼解析中慢慢體會吧!
okhttp簡單用法
既然是網絡架構,那麼先來看看它的post和get請求吧。總的來說,分為三步:
- 執行個體化一個OkHttpClient 對象;
- 構造Request請求體;
- 發請求,同步調用okHttpClient.newCall(request).execute();異步調用 okHttpClient.newCall(request).enqueue(new Callback())。
- get(異步)請求
OkHttpClient okHttpClient = new OkHttpClient();
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 {
String dataStr=response.body().string();
Log.e("info",dataStr);
}
});
- get(同步)請求
OkHttpClient okHttpClient = new OkHttpClient();
Response response = okHttpClient.newCall(request).execute();
String dataStr=response.body().string();
- post(異步)請求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody
.Builder()
.add("name", "張士超")
.add("password", "123456")
.build();
Request request = new Request
.Builder()
.url(URL)
.post(requestBody)
.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 {
String dataStr=response.body().string();
Log.e("info",dataStr);
}
});
- post(同步)請求
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody
.Builder()
.add("name", "張士超")
.add("password", "123456")
.build();
Request request = new Request
.Builder()
.url(URL)
.post(requestBody)
.build();
try {
Response response = okHttpClient.newCall(request).execute();
String dataStr=response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
- 總結:post與get請求差別在于需要構造一個RequestBody對象,同步和異步差別在于執行的是execute()還是enqueue(new Callback()),這也很好了解,異步需要回調接口回報的請求資料對吧。
okhttp的源碼深入
- 好啦,步入正題,看看我們的okhttp同學是怎麼樣完成一個個請求的!
首先我們看看okhttp源碼涉及到的幾個類的源碼:
- Request請求類:
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
這裡面比較重要的是url、method、header和RequestBody,分别代表request請求的請求URL、請求方法、請求頭和請求體。
- Response響應類
public final class Response implements Closeable {
final Request request;
final Protocol protocol;
final int code;
final String message;
final @Nullable Handshake handshake;
final Headers headers;
final @Nullable ResponseBody body;
final @Nullable Response networkResponse;
final @Nullable Response cacheResponse;
final @Nullable Response priorResponse;
final long sentRequestAtMillis;
final long receivedResponseAtMillis;
這裡面比較重要的是code響應碼、message響應資訊、headers響應頭和body響應體。
- Okhttp類
final Dispatcher dispatcher;
final @Nullable Proxy proxy;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final @Nullable Cache cache;
final @Nullable InternalCache internalCache;
final SocketFactory socketFactory;
final @Nullable SSLSocketFactory sslSocketFactory;
final @Nullable CertificateChainCleaner certificateChainCleaner;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
final Authenticator proxyAuthenticator;
final Authenticator authenticator;
final ConnectionPool connectionPool;
final Dns dns;
final boolean followSslRedirects;
final boolean followRedirects;
final boolean retryOnConnectionFailure;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
Okhttp包含的東西很多,這裡我們需要重點關注的是dispatcher排程器,interceptors自定義應用攔截器和networkInterceptors自定義網絡攔截器。
-
ok,比較重要的三個類介紹完了,接下來我們看一看同步和異步請求的源碼解析,先放一張圖(盜的别人的圖hhhh),讓看官老爺門大緻上有一個印象,後面比較好了解。
整個請求過程
整個過程大緻說一下吧,首先不管啥請求okHttpClient.newCall(request)這玩意實際上傳回一個RealCall類,然後同步請求調用execute(),異步調用enqueue()之後給排程器,其實同步和異步都給了排程器,隻是異步調用了排程器的execute()進行請求排程處理,下面給一張圖就了解啦:
排程器
處理完之後通過一系列花裡胡哨的攔截器之後傳回一個response響應,就獲得資料啦!
請求過程源碼解析
以下面代碼為例,看看源碼都幹了啥:
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
okHttpClient.newCall(request).enqueue(new Callback()/execute()
前面兩句話就不說了,就是OkHttpClient和Request 對象的執行個體化,我們看看okHttpClient.newCall(request)這裡都幹了些啥:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false );
}
進去newRealCall,看看:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
總之就是工廠模式将request封裝成一個RealCall對象,接着看okHttpClient.newCall(request).execute(),同步請求;
@Override public Response execute() throws IOException {
synchronized (this) {//1
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);//2
Response result = getResponseWithInterceptorChain();//3
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);//3
}
}
比較重要的是上面三處注釋:
- 首先看這個請求有沒有被執行,每個請求隻能被執行一次;
- 若沒有被執行,則将這個請求任務給排程器dispatcher,簡單看看這個排程器做了些什麼:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
将這個請求加入到runningSyncCalls隊列中,後面詳細說說這個排程器。
- 經過一系列花裡胡哨的攔截器得到響應,這具體後再說怎麼做的。
- 執行完之後排程器dispatcher結束這個請求任務,看看具體怎麼做的:
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
将請求任務從runningSyncCalls隊列中移除。
-
這樣整個同步請求流程就實作了,看下面這個圖會清晰很多:
同步請求任務.png
- 上面可以知道,請求處理都交給排程器了,那我們來看看這個排程器是個什麼鬼:
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
private @Nullable ExecutorService executorService;
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
- 解釋一下這幾個參數啥子意思,第一個maxRequests最大請求數,表示最大并發數 ;maxRequestsPerHost 單個域名最大請求數 預設為 5 個。是以極端情況下,才會開啟 64 個線程。這個場景非常罕見;readyAsyncCalls 表示異步請求的等待隊列,runningAsyncCalls 正在執行的異步請求隊列,AsyncCall隊列;runningSyncCalls 正在執行的同步請求隊列,因為是同步的,這也好了解,是以不需要等待隊列。executorService這個就是排程器的線程池,java線程池我們都知道,我們看看這裡的線程池怎麼定義的:
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;
}
- 可以知道,排程器的核心線程為0,非核心線程無數個,每個空線程的回收時間為60s。
異步請求
異步請求以下面這個為例,看看源碼都怎麼做的:
OkHttpClient okHttpClient = new OkHttpClient();
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 {
String dataStr=response.body().string();
Log.e("info",dataStr);
}
});
- 與同步請求的差距主要在于調用的是enqueue()函數,點進去看看與execute的差別
@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));
}
- 先判斷有沒有執行過,如果沒有,這執行排程器dispatcher的enqueue()函數,點進去看看吧:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
- 這裡就與我們之前講的排程器隊列有關系了,如果正在執行的異步隊列runningAsyncCalls小于最大并發數,而且單域名的正在執行異步隊列小于最大單域名最大請求數,則将這個請求放到正在執行的異步請求隊列,并調用排程器的execute()方法,否則就放入等待異步隊列。
- 排程器線程池總結
- 排程線程池Disptcher實作了高并發,低阻塞的實作
- 采用Deque作為緩存,先進先出的順序執行
- 任務在try/finally中調用了finished函數,控制任務隊列的執行順序,而不是采用鎖,減少了編碼複雜性提高性能
攔截器
- 主要是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的這種攔截器鍊采用的是責任鍊模式,這樣的好處是将請求的發送和處理分開,并且可以動态添加中間的處理方實作對請求的處理、短路等操作。給個圖加強了解:
攔截器.png
- 不多說了,結合圖和源碼了解吧,創作不易,給個小心心吧!