Okhttp特性
Okhttp是一個高效的,請求速度更快,更節(jié)省流量的http庫。擁有以下特性。
- 支持SPDY和http2,對同一服務(wù)器的所有請求共享同一個socket。
- 擁有自動維護(hù)的socket連接池,減少握手次數(shù)。
- socket自動選擇最好路線,并支持自動重連。
- 擁有隊列線程池,輕松寫并發(fā)。
- 擁有Interceptors輕松處理請求與響應(yīng)(比如透明GZIP壓縮,LOGGING)。
- 無縫的支持GZIP來減少數(shù)據(jù)流量。
- 支持基于Headers的緩存策略,緩存響應(yīng)數(shù)據(jù)來減少重復(fù)的網(wǎng)絡(luò)請求。
- 支持服務(wù)器多IP重連,支持從常用的連接問題中自動恢復(fù),還處理了代理服務(wù)器問題和SSL握手失敗問題。
注:SPDY是什么?
SPDY(讀作“SPeeDY”)是Google開發(fā)的基于TCP的傳輸層協(xié)議,用以最小化網(wǎng)絡(luò)延遲,提升網(wǎng)絡(luò)速度,優(yōu)化用戶的網(wǎng)絡(luò)使用體驗。
SPDY并不是一種用于替代HTTP的協(xié)議,而是對HTTP協(xié)議的增強(qiáng)。新協(xié)議的功能包括數(shù)據(jù)流的多路復(fù)用、請求優(yōu)先級以及HTTP報頭壓縮。
谷歌表示,引入SPDY協(xié)議后,在實驗室測試中頁面加載速度比原先快64%。
OkHttpClient分析版本
Okhttp3(3.2.0版本)
OkHttpClient的簡單調(diào)用
Get請求
public void doGet(String url){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
Log.i("輸出:" + response.body().string());
}
Post請求
public void doPost(String url){
MediaType json = MediaType.parse("application/json; charset=utf-8")
RequestBody body = RequestBody.create(JSON, json);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).post(body).build();
Response response = client.newCall(request).execute();
Log.i("輸出:" + response.body().string());
}
OkHttpClient介紹
OkHttpClient顧名思義是一個http請求的客戶端,實現(xiàn)了Call.Factory接口,提供newCall方法用于創(chuàng)建一個請求調(diào)用Call。
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
OkHttpClient包含很多模塊,包括Dispatcher(請求分發(fā)器),ProxySelector(代理服務(wù)器選擇),InternalCache(內(nèi)部緩存)等其他的模塊,由它對外提供這些模塊的訪問,是典型的外觀模式,同時OkHttpClient設(shè)計為建造者模式,提供Builder方便為不同模塊進(jìn)行配置。
Request,Response,Call,RealCall,AsynCall介紹
- Request作為請求信息的封裝,內(nèi)部包含了請求url,請求方法method,請求頭headers,請求體RequestBody,tag標(biāo)簽等。
- Response作為相應(yīng)信息的封裝,內(nèi)部包含對應(yīng)的請求信息request,http協(xié)議protocol,響應(yīng)碼code,響應(yīng)頭headers,響應(yīng)消息message,響應(yīng)體ResponseBody等。
- Call作為一個請求的接口,提供獲取對應(yīng)請求信息息request,執(zhí)行請求execute,異步請求入隊enqueue,取消請求cancel等方法的定義。
- RealCall是Call請求的具體實現(xiàn),實現(xiàn)了同步請求執(zhí)行,異步請求入隊,請求取消等操作。同時內(nèi)部提供ApplicationInterceptorChain負(fù)責(zé)請求和響應(yīng)的攔截處理。
- AsynCall是一個Runnable異步請求任務(wù),分發(fā)器Dispatcher負(fù)責(zé)管理這些異步請求,在可請求數(shù)量滿足的情況下會交給線程池執(zhí)行它的execute方法。
Dispatcher請求調(diào)用分發(fā)器
Dispatcher是請求Call的分發(fā)器,用于管理請求的等待,執(zhí)行,取消。分為同步請求RealCall和異步請求AsyncCall的管理。
- 針對同步請求,用runningSyncCalls隊列記錄正在執(zhí)行的同步請求。
- 針對異步請求,用readyAsyncCalls(待執(zhí)行的異步請求隊列)和runningAsyncCalls(正在執(zhí)行的異步請求隊列)記錄異步請求。
- 內(nèi)部提供線程池,負(fù)責(zé)執(zhí)行異步請求,查看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;
}
這個線程池,我們看參數(shù)解析:
1. 參數(shù)0,代表核心線程的數(shù)量,即線程池中最少線程時的數(shù)量(當(dāng)所有線程都空閑時),為0的話,說明線程空閑時,不保留任何線程,做到線程低占用。
2. 參數(shù)Integer.MAX_VALUE,代表最大線程數(shù),即線程池中最多線程時的數(shù)量,為Integer.MAX_VALUE的話,說明可以開啟無限多的線程進(jìn)行工作,線程工作完了再關(guān)閉。
3. 參數(shù)60,和參數(shù)TimeUnit.SECONDS,表示當(dāng)線程池的數(shù)量比核心線程的數(shù)量大時,等待60秒之后就會去關(guān)閉空閑線程,使總的線程數(shù)量不會大于核心線程數(shù)量。
4. 參數(shù)new SynchronousQueue<Runnable>(), 代表這個線程等待隊列是同步隊列,當(dāng)有一個線程進(jìn)來時,就同時會有一個線程出去,也就是說線程不會停留在其中,一進(jìn)入就立馬出去執(zhí)行,這種方式在高頻請求時是很合適的。
5. 參數(shù)Util.threadFactory("OkHttp Dispatcher", false),表示提供一個線程工廠,用于創(chuàng)建新的線程。
這個線程池的設(shè)計,是需要時可以創(chuàng)建無限多的線程,不需要時不保留任何線程,空閑線程60秒后如果還是空閑就會回收,以保證高阻塞低占用的使用。
- 設(shè)置有maxRequests(最大異步請求的數(shù)量)和maxRequestsPerHost(單個服務(wù)器的最大異步請求數(shù)量),這是為了限制同時執(zhí)行的總的請求數(shù)量和針對同一個服務(wù)器訪問的請求數(shù)量,如果有超過了這兩個的限制,就將異步請求AsyncCall添加到readyAsyncCalls(待執(zhí)行的異步請求隊列)去等待執(zhí)行。這兩個參數(shù)可以配置。
- enqueue方法,AsyncCall入隊操作
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//未超過限制,執(zhí)行加入到線程池執(zhí)行異步請求
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//超過限制,將行異步請求加入等待隊列,等待執(zhí)行
readyAsyncCalls.add(call);
}
}
- finished方法,標(biāo)記當(dāng)前AsyncCall已經(jīng)完成,這時會考慮從異步等待隊列取出請求去執(zhí)行。
//標(biāo)記請求完成
synchronized void finished(AsyncCall call) {
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
promoteCalls();
}
//從異步等待隊列取出請求去執(zhí)行,同時記錄到正在執(zhí)行的異步隊列中
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.
}
}
- cancelAll方法,取消所有請求,包括所有同步請求,所有正在執(zhí)行的異步請求,所有等待執(zhí)行的異步請求。
public synchronized void cancelAll() {
for (AsyncCall call : readyAsyncCalls) {
call.cancel();
}
for (AsyncCall call : runningAsyncCalls) {
call.cancel();
}
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
RealCall 真正執(zhí)行請求調(diào)用的地方
RealCall是真正執(zhí)行請求調(diào)用的入口,無論是同步請求的execute,還是異步請求的enqueue(由Dispatcher進(jìn)行分發(fā)),最終都會到RealCall的getResponseWithInterceptorChain。
getResponseWithInterceptorChain里創(chuàng)建了一個ApplicationInterceptorChain攔截鏈來處理當(dāng)前RealCall中的Request請求數(shù)據(jù)。接下來講講Okhttp中攔截器是這樣的一種運行模式。
Interceptors攔截器原理
Interceptors攔截器采用了責(zé)任鏈模式一層一層的處理請求響應(yīng)信息。這里我們從同步請求去分析添加了HttpLoggingInterceptor日志打印攔截器的運行原理。
final class RealCall implements Call {
//執(zhí)行請求,這里創(chuàng)建了ApplicationInterceptorChain攔截鏈,負(fù)責(zé)對所有攔截器進(jìn)行調(diào)用,調(diào)用proceed開始處理攔截
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
class ApplicationInterceptorChain implements Interceptor.Chain {
private final int index;
private final Request request;
private final boolean forWebSocket;
ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
this.index = index;
this.request = request;
this.forWebSocket = forWebSocket;
}
@Override public Connection connection() {
return null;
}
@Override public Request request() {
return request;
}
//這里遍歷攔截器,通過index在攔截鏈中找到對應(yīng)的攔截器,然后調(diào)用intercept進(jìn)行攔截處理,返回加工后的Response響應(yīng)信息
@Override public Response proceed(Request request) throws IOException {
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
//當(dāng)所有攔截器都遍歷處理后,開始執(zhí)行真正請求,返回Response,這里才是真正產(chǎn)生Response響應(yīng)的地方。
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
}
}
看了以上代碼之后,我們知道是通過index一個一個遍歷攔截鏈的攔截器,去攔截處理,這是一個遞歸的過程,當(dāng)所有的攔截器處理了Request請求信息之后(也就是真正請求之前的預(yù)處理,比如打日志,修改請求頭什么的),才真正的交給網(wǎng)絡(luò)請求引擎去執(zhí)行請求,返回響應(yīng)信息,然后又按相反順序?qū)esponse響應(yīng)信息進(jìn)行攔截處理(也就是對響應(yīng)信息的再加工,比如打印響應(yīng)信息等)。那么我們分析HttpLoggingInterceptor日志打印攔截器會更加的的清晰這個過程。
public final class HttpLoggingInterceptor implements Interceptor {
//攔截鏈上的
@Override public Response intercept(Chain chain) throws IOException {
Level level = this.level;
//獲取請求信息
Request request = chain.request();
//如果不打印日志,那就直接調(diào)用chain.proceed執(zhí)行下一個攔截操作,不做其他操作。
if (level == Level.NONE) {
return chain.proceed(request);
}
//這部分是打印請求信息,對請求進(jìn)行預(yù)處理(這里可以對請求信息進(jìn)行修改或做其他操作)
---------------------------------------
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logger.log(name + ": " + headers.value(i));
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
}
}
//請求預(yù)處理完成
----------------------------------
//這里調(diào)用chain.proceed執(zhí)行下一個攔截操作,返回Response響應(yīng)信息,結(jié)合ApplicationInterceptorChain的proceed方法,很容易看出是一個責(zé)任鏈的遞歸調(diào)用模式
long startNs = System.nanoTime();
Response response = chain.proceed(request);
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
//接下來打印響應(yīng)信息,對Response響應(yīng)進(jìn)行再加工(這里可以對響應(yīng)信息進(jìn)行修改或做其他操作)
---------------------------------------
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpEngine.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyEncoded(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
logger.log("");
logger.log("Couldn't decode the response body; charset is likely malformed.");
logger.log("<-- END HTTP");
return response;
}
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
//響應(yīng)再加工完成
----------------------------------
//這里返回響應(yīng)信息
return response;
}
}
明白攔截器的運行模式之后,我們知道真正的請求是在RealCall的getResponse方法中開始的。
/**
* Performs the request and returns the response. May return null if this call was canceled.
*/
Response getResponse(Request request, boolean forWebSocket) throws IOException {
//這里負(fù)責(zé)執(zhí)行請求,然后返回響應(yīng)數(shù)據(jù)
...
}
具體請求我們下節(jié)分析。