OkHttp
做用
- 處理網絡請求
- 平時處理Http請求更加快速
- 流量更加節省
1、流程圖
(1)建立OkHttpClient 對象;只會建立一次並做爲全局進行保存的單例對象通常。
(2)建立Request 對象;封裝一些請求報文信息,好比請求的頭信息,url地址,請求的類型方法等
ps:經過build 構建者模式 鏈式生成Request對象
(3)獲得RealCall對象; 經過Client.newCall()方法獲取call對象,readcall表明一個實際的http請求
ps: 鏈接Request 和 Response 的一個橋樑;並將請求添加到dispatcher中
(4)Dispatcher 分發器類 ***;-確認同步 or 異步
ps:在這個類內部維護了一個線程池,這個線程池用於執行網絡請求(同步/異步);當中經過三個隊列維護池中的同步/異步請求
不斷的從Request請求隊列當中獲取須要的RealCall,根據是否調用緩存(Cache)來選擇是直接Connect攔截器+CallServer仍是 Cache 攔截器
(5)interceptors 攔截器 ***;進行服務器數據的獲取,構建一個攔截器列,經過依次執行這個攔截器列中的每一個攔截器,將服務器得到的數據返回
ps:不論同步操做仍是異步操做最終都會經過攔截器進行分發
a、RetryAndFollow interceptors攔截器 ;網絡請求失敗後的重試,服務器返回當前request請求須要重定向的時候
b、Bridge interceptors攔截器 ;設置request請求前的操做主要好比內容長度,編碼,設置gzip壓縮等內容攔截器同時能夠添加cookie
c、Cache interceptors攔截器 ; 負責緩存的管理,能夠直接返回cache給客戶端
d、Connect interceptors攔截器 ;爲當前request請求找到一個合適的鏈接(能夠複用已有的連接)
e、CallServer interceptors攔截器 ;向服務器發起真正的網絡請求,接受服務器返回數據並讀取
——————————————————————————————————————
2、同步請求方法
簡單步驟:
//一、建立OkHttpClient 請求客戶端對象--單例對象建立
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
public void synRequest() { //同步基本方法
//二、建立攜帶請求信息的Request對象
.get().build();
//三、建立RealCall對象經過Client對象和Request對象構建實際的call對象;鏈接Request、Response橋樑
Call call = client.newCall(request);//同步異步分水嶺
//四、經過Call對象的execute獲取Response對象
try { //RealCall其實是Call的具體實現類
Response response = call.execute();//響應報文信息 -同步
System.out.println("body:" + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
同步 發送請求後,就會進入阻塞狀態,直到收到數據響應(與異步最大的區別)
核心類:
在getResponseWithInterceptorChian()方法中,構建一個攔截器的鏈,經過依次執行攔截器的鏈當中的每個不一樣做用的攔截器來獲取服務器的數據返回。
源碼分析 核心類:
(1)new OkHttpClient.Builder():
Builder() :須要不少參數的設置的時候考慮的方法
public Builder() {
dispatcher= new Dispatcher(); (1)
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool= new ConnectionPool();(2)
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
(1)Http請求重要的分發器,尤爲肯定異步請求是緩存等待仍是直接處理;針對同步請求就是直接把request放入隊列當中;
(2)connectionPool,鏈接池;
尤爲進行統一管理客戶端和服務器端的鏈接,當請求的URL是相同的時候能夠選擇複用;
實現哪些網絡鏈接是打開狀態,哪些是複用的策略的設置;
(2)new Request.Builder(). …. .build():
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
//建立一個Request對象 把當前的build對象傳遞回去(把以前配置好的信息賦值給這個Request對象
好比url, method,headers,body,tag)
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
(3)client.newCall(request): //不論同步仍是異步都會調用該方法
Call是個接口 其實現都在newRealcall() 這個實際的實現類當中
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
(4)newRealcall():
//建立一個RealCall對象 並配置監聽事件 後返回該對象
static RealCall newRealCall(OkHttpClient client,
Request originalRequest,
boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
(5)RealCall():
一、持有了Client 和Request對象 二、賦值了一個重定向攔截器
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor= new RetryAndFollowUpInterceptor(client, forWebSocket);
}
(6)Realcall的execute():
@Override
public Response execute() throws IOException {
//一、在同步代碼塊 當中首先判斷executed 標誌位 是否爲 true
同一個http請求只能執行一次,沒有執行過就會把executed設置爲true,若執行過拋異常
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//二、捕捉異常信息
captureCallStackTrace();
//三、每當調用call或是enqueue 就會開啓這個監聽器
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 {
client.dispatcher().finished(this);
}
}
dispatcher的做用:
一、維持call請求發給它的狀態
二、維護一個線程池用於執行網絡請求
call請求在執行任務的時候經過上面的dispatcher分發器類,將任務推到執行隊列當中進行操做,操做完成後,再執行下面等待隊列的任務
ps:
(7)client.dispatcher().executed(this):————>將call請求添加到request請求隊列當中
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
在這裏要先提一下 ,在dispatcher類中定義了3個隊列
/** 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<>();
(8)getResponseWithInterceptorChain():攔截器鏈 方法 在內部依次調用攔截器進行操做
後面會注重解析該方法
finally {
(9) client.dispatcher().finished(this);
}
主動回收一些請求
(10)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;
}
//正在執行的請求數量爲0,同時知足idleCallback不爲空
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
——————————————————————————————————————
3、異步請求方法
簡單步驟:
public void asynRequest() {
.get().build();
//將Request封裝成Call對象
Call call_ays = client.newCall(request_ays);
call_ays.enqueue(new Callback() {//開啓一個工做線程執行下面的方法
@Override
public void onFailure(Call call, IOException e) {
切記更新UI操做要在主線程而不是在如今的子線程中執行
}
@Override
public void onResponse(Call call, Response response) throws IOException {
切記更新UI操做要在主線程而不是在如今的子線程中執行
}
});
}
Dispatcher 分發器類,有分發器來選擇是異步請求執行仍是就緒準備
源碼分析 核心類:
前三步沒有真正的發起網絡請求,步驟同理 - 同步請求
call_ays.enqueue(new Callback() :
//四、傳遞一個Callback對象,請求結束之後的接口回調
@Override public void enqueue(Callback responseCallback) {
//一、鎖住當前的RealCall對象, 並對executed進行判斷,有沒有執行過,執行過就拋出異常
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
//二、將responseCallback對象封裝成AsyncCall對象
實際上AsyncCall 由於繼承自Runnable,因此能夠把其想象成就是Runnable
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
//三、調用攔截器的enqueue()方法進行異步網絡請求
client.dispatcher().enqueue():
dispatcher已經在構建client的時候進行了初始化賦值操做!!!
重要方法!!!!!!!!!! -———————————>
synchronized void enqueue(AsyncCallcall) {
//a、runningAsyncCalls的數量是否小於最大請求數量(64)
同時 正在運行的主機的請求是否小於設定好的主機請求的最大值(5)
if (runningAsyncCalls.size() < maxRequests
&& runningCallsForHost(call) < maxRequestsPerHost) {
//b、知足條件把當前的異步請求添加到正在執行的異步請求隊列當中
runningAsyncCalls.add(call);
//c、經過線程池執行異步請求
executorService().execute(call);
} else {
//d、添加到等待就緒隊列當中
readyAsyncCalls.add(call);
}
}
executorService():線程池
//使用synchronized關鍵字鎖對象,保證ExecutorService線程池是單例的
public synchronized ExecutorService executorService() {
if (executorService == null) {
//ThreadPoolExecutor (corePoolSize,最大線程池數量,)
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
AsyncCall():
前面提到AsyncCall繼承自NamedRunnable,那這個方法作了什麼呢?
asyncCall自身沒有run()方法,是在Runnable()方法中的execute()方法中執行的,因此具體的實現應該在AsyncCall的execute()方法中實現的
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();
}
execute():
@Override protected void execute() {
boolean signalledCallback = false;
try {
//一、構建一個攔截器的鏈返回一個respone對象,每個攔截器中的鏈做用不一樣。
Response response = getResponseWithInterceptorChain();
//二、判斷 重定向重試攔截器是否被取消了-在子線程執行!
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
//2.一、取消了 ,則調用call.callback的onFailure方法 回調失敗的提示
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//2.2 、沒有取消則調用onResponse()的回調,真正發送網絡請求的就是onResponse()
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分發器
client.dispatcher().finished(this);
}
}
finished(runningAsyncCalls, call, true); ******* 重要方法
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//一、經過call.remove把這個請求從正在執行的異步請求隊列中刪除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!」);
//二、調整整個異步請求的任務隊列,出於線程安全考慮用sy
if (promoteCalls) promoteCalls();
//三、 從新計算線程數量並賦值
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
——————————————————————————————————————
4、同步/異步請求方法差別 及任務調度dispatcher
- 同步是execute()
- 異步是enqueue(),傳入callback對象
任務調度dispatcher
思考1:okHttp如何實現同步異步請求?
答: 發送的同步/異步 請求 最後都會在dispatcher中 管理其請求隊列狀態!
思考2:什麼是dispatcher
答:dispatcher 的做用爲了
- 維護請求的狀態(同 or 異)
- 維護一個線程池,用於執行請求;把請求推送到請求隊列
在dispatcher類中定義了3個隊列
維護了一個線程池
private @Nullable ExecutorService executorService;
維護一個等待就緒的異步執行隊列1 ,不知足條件的時候請求進入該隊列進行緩存
/** Ready async calls in the order they'll be run. */ 異步的就緒隊列
private final Deque<AsyncCall> readyAsyncCalls= new ArrayDeque<>();
維護一個正在執行的異步請求隊列2 ,包含了已經取消但沒有執行完的請求
/** 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<>();
思考3:異步請求爲何須要兩個隊列-想象成生產和消費者模型
dispatcher :生產者 默認運行在主線程
ExecutorService :消費者池
每當有新的異步請求經過call.enqueue的時候,經過2個條件判斷是否進入正在執行隊列仍是等待隊列;
條件:runningAsyncCalls的數量是否小於最大請求數量(64)同時 正在運行的主機的請求是否小於設定好的主機請求的最大值(5)
promoteCalls() 手動清除緩存區
===============================dispatch核心
A、同步——請求的executed():
每當有新的請求來了就直接加入 同步請求隊列
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
B、異步——請求的executed(): 判斷條件
核心:
這個call實際上就是一個runnable對象,不知足條件就放在就緒異步等待隊列當中。正在請求的異步隊列有空閒位置的時候會從就緒緩存等待中取出優先級高的請求加入隊列進行執行
條件知足 將封裝好的call添加到正在執行的異步隊列中而後交給線程池管理執行
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests
&& runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
executorService():線程池
//使用synchronized關鍵字鎖對象,保證ExecutorService線程池是單例的
public synchronized ExecutorService executorService() {
if (executorService == null) {
//ThreadPoolExecutor (corePoolSize,最大線程池數量,)
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
ThreadPoolExecutor(參數)
corePoolSize:0 的意義
若是空閒一段時間後,就會自動銷燬全部線程池中的線程!
Integer.MAX_VALUE:最大線程數
當請求到來時候能夠無限擴充請求建立線程,這個無限不大於64
keepAliveTime
當線程數量大於核心線程數的時候,多於的線程最大的存活時間!!
這個方法的意義:
在程序中開啓了多個併發的請求,線程池會建立N個線程。當工做完成後,線程池會在60秒後相繼關閉全部無用的線程
思考4: readyAsyncCalls隊列中的線程在何時纔會被執行呢?
finally {
client.dispatcher().finished(this);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
...
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();
}
}
promoteCalls():對等待的異步請求緩存隊列進行調度
private void promoteCalls() {
// Already running max capacity.
if (runningAsyncCalls.size() >= maxRequests) return;
// No ready calls to promote.
if (readyAsyncCalls.isEmpty()) return;
//一、對原來的等待緩存的異步請求隊列進行遍歷
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//二、把它最後一個元素取出並移除後,加入到runningAsyncCalls 中
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
——————————————————————————————————————
5、攔截器 Interceptor
攔截器是OkHttp中提供一種強大的機制,它能夠實現 網絡監聽、請求 以及響應重寫、請求失敗重試等功能。(不區分同步/異步)
內部攔截器:
- 定向攔截器
- 橋接適配攔截器
- 緩存攔截器
- 鏈接管理攔截器
- 網絡IO流數據讀寫攔截器
外部攔截器:
- App攔截器client.interceptors
- 網絡攔截器networkInterceptors
getResponseWithInterceptorChain() :返回網絡響應
ps:在方法內構成一個攔截器的鏈,經過依次執行每一個不一樣功能的攔截器來獲取服務器的響應返回:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//一、建立一系列攔截器,而後將建立的攔截器放入到interceptors 集合當中
interceptors.addAll(client.interceptors()); //添加app 攔截器
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));
//二、將集合封裝到RealIntercptorChain 鏈當中
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis() );
//依次執行攔截器鏈的proceed()方法
return chain.proceed(originalRequest);
}
//三、核心功能:建立下一個攔截器鏈
@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 {
…
// Call the next interceptor in the chain.
訪問的話須要從下一個攔截器進行訪問而不是從當前的攔截器
RealInterceptorChainnext = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
將獲取的攔截器鏈傳入,構成一個完整的鏈條
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
ps:簡單的說把一個OkHttp的網絡請求看做是一個一個攔截器執行Interceptor的方法的過程
小結:
一、在發起請求前對request 進行處理
二、調用下一個攔截器,獲取response (持有鏈條的概念)
三、對response進行處理,返回給上一個攔截器
——————————————————————
A、 RetryFollowUpInterceptor 重定向攔截器
流程:
(1)StreamAllocation
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
StreamAllocation對象,創建Http請求所須要的全部網絡組件;分配stream
做用:
獲取連接服務器端的connection連接和鏈接數據傳輸的輸入輸出流
ps:真正使用該對象的是ConnectInterceptor
(2) 調用RealInterceptorChain.proceed()進行實際的網絡請求
如何進行攔截操做的呢?
(3)根據異常結果或是響應結果判斷是否要進行從新請求
核心操做:
一、對重試的次數進行判斷 超過20次就不會再請求而是釋放
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throws ...
} ——20次
(4)調用下一個攔截器,對response進行處理,返回給上一個攔截器
++++++++++++++++++++++++++++++++++++++++
B、BridgeInterceptor 橋接攔截器
做用:
- 設置內容長度、編碼方式、壓縮方式等的頭部信息添加;
- keep-Alive操做保持鏈接複用的基礎
keep-Alive:當開啓一個TCP鏈接以後,他不會關閉它的鏈接,而是在必定時間內保持它的鏈接狀態
//調用攔截器鏈的proceed()方法,得到服務器發回的響應以後把該響應返回給客戶端
Response networkResponse = chain.proceed(requestBuilder.build());
//將從服務器返回的網絡請求響應Response轉化爲用戶能夠讀取並使用的response
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
//核心轉化功能:轉化Response
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
...
}
a、transparentGzip 爲true 表示請求的客戶端支持Gzip壓縮
b、返回的Gzip是否符合頭部信息的編碼
c、判斷頭部信息是否含有body體
//Response的body體的輸入流轉化爲GzipSource類型,爲了使用者能夠直接以解壓的方式讀取流數據
GzipSourceresponseBody = new GzipSource(networkResponse.body().source());
核心功能:
一、負責將用戶構建的一個Request請求轉化爲可以進行網絡訪問的請求
二、將這個符合網絡請求的Request進行網絡請求
三、將網絡請求回來的響應Response 轉化爲用戶能夠讀取的Response
++++++++++++++++++++++++++++++++++++++++
C、CacheInterceptor 緩存攔截器
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(new File("cache"),24*1024*1024))
.readTimeout(5, TimeUnit.SECONDS).build();
param1 : 緩存目錄
param2: 緩存目錄大小
internalCache全部的實現都是經過上面的cache類實現的
final InternalCache internalCache= new InternalCache() {
//從緩存中讀取響應體Response
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
…
}
CacheRequest put(Response response) {
//一、經過Response響應信息獲取對應Response對應的Request請求,再調用method方法獲取requestMethod
String requestMethod = response.request().method();
// 二、若是不是get方法的話,就不進行緩存
if (!requestMethod.equals("GET")) {…}
//三、寫入緩存的部分 Entry:OkHttp所須要的全部屬性被封裝成Entry類
Entry entry = new Entry(response);
//四、最後緩存都會交給DisLruCahce算法來實現實際寫入
DiskLruCache.Editor editor = null;
ps:OkHttp內部因爲維護着一個清理的線程池,由這個清理的線程池來實現對緩存文件的自動的清理和管理工做
//五、建立真正的editor ,經過傳入key值獲取完成的editor
editor = cache.edit(key(response.request().url()));
key:將網絡請求的url作MD5 加密處理 獲得16進製表示形式 並轉化爲key
//六、真正的開始進行網絡緩存操做,將緩存寫入磁盤
entry.writeTo(editor);
public void writeTo(DiskLruCache.Editor editor) throws IOException {
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
//緩存一些請求的頭部信息
sink.writeUtf8(url)
.writeByte('\n');
sink.writeUtf8(requestMethod)
.writeByte('\n');
sink.writeDecimalLong(varyHeaders.size())
.writeByte('\n');
for (int i = 0, size = varyHeaders.size(); i < size; i++) {
sink.writeUtf8(varyHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(varyHeaders.value(i))
.writeByte('\n');
}
…
//緩存http的響應行 等信息
sink.writeUtf8(new StatusLine(protocol, code, message).toString())
.writeByte('\n');
sink.writeDecimalLong(responseHeaders.size() + 2)
.writeByte('\n');
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
sink.writeUtf8(responseHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(responseHeaders.value(i))
.writeByte('\n');
}
…
//判斷是不是Https請求
if (isHttps()) {
sink.writeByte('\n');
...
}
sink.close();
}
//七、實際的響應主體 CahceInterceptor的實現類,經過該實現類實現數據的緩存和更新
return new CacheRequestImpl(editor);
經過源碼能夠看到它是經過DisLruCache書寫body的
CacheRequestImpl(final DiskLruCache.Editor editor) {}
——————
//從緩存中讀取響應體Response
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
DiskLruCache.Snapshot snapshot;
//一、目標緩存快照:記錄緩存在某一個特定時間包含的內容
//MD5加密計算獲得的Key值
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
//二、經過key值將緩存的值保存到snapshot緩存快照中
//三、根據緩存快照建立entry緩存對象用於緩存數據
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
//四、根據entry 獲取響應的response對象
Response response = entry.response(snapshot);
//五、進行響應的匹配工做,若是不是一對一的話就返回null並關閉當前流
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
public Response response(DiskLruCache.Snapshot snapshot) {
…
//一、根據頭部信息建立cacheRequest請求
Request cacheRequest = new Request
.Builder()
.url(url)
.method(requestMethod, null)
.headers(varyHeaders)
.build();
//二、將響應的1步信息保存到entry當中
CacheResponseBody:響應體讀取
return new Response.Builder()
.request(cacheRequest)
.protocol(protocol)
.code(code)
.message(message)
.headers(responseHeaders)
.body(new CacheResponseBody(snapshot, contentType, contentLength))
.handshake(handshake)
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(receivedResponseMillis)
.build();
============================================
CacheInterceptor 緩存攔截器
@Override public Response intercept(Chain chain) throws IOException {
//一、經過get方法嘗試獲取緩存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
…
//二、經過緩存策略的工廠類獲取具體的緩存策略
CacheStrategystrategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//三、根據緩存策略獲取其內部維護的response和request
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//四、當有緩存的時候,更新一下緩存的命中率
if (cache != null) {
cache.trackResponse(strategy);
}
…
//五、如當前不能使用網絡又沒有找到響應的緩存對象,經過構建者模式構建一個response 這會拋出一個504錯誤
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//六、如有緩存且不能使用網絡時候,直接返回緩存的結果
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//七、經過調用攔截器鏈的preceed方法來進行網絡響應的獲取,裏面的工做交給下一個攔截器(Connectinterpreter)
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
//八、判斷緩存響應是否爲null 從緩存中讀取數據
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
…
}
//九、網絡緩存保存判斷
if (cache != null) {
//判斷http頭部有沒有響應體,而且緩存策略選擇爲能夠被緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 將網絡響應寫入put到緩存當中
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//十、這個請求request是不是無效的緩存方法,若是是刪除該request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
CacheStrategy 緩存策略
//維護了一個網絡請求networkRequest 和一個緩存響應 cacheResponse,內部經過制定以上兩者來描述是經過何種方式獲取Response
public final class CacheStrategy{
/** The request to send on the network, or null if this call doesn't use the network. */
public final @Nullable Request networkRequest;
/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
}
實際建立CacheStrategy對象的核心類:
private CacheStrategy getCandidate() {
// 判斷是否有緩存響應對象,若無建立新的對象傳入請求request
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 判斷請求是不是https,是否經歷過握手操做
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//判斷緩存是否該存儲
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//請求當中制定不使用緩存響應或是進行了可選擇的httpget請求,就從新創建請求
acheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
…
//比較緩存的response,添加緩存頭操做
Response.Builder builder = cacheResponse.newBuilder();
…
//返回一個CacheStrategy對象
return new CacheStrategy (null, builder.build());
…
}
++++++++++++++++++++++++++++++++++++++++
C、ConnectInterceptor 鏈接攔截器
做用:打開與服務器的鏈接,正式開網絡啓請求
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
(1) 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, chain, doExtensiveHealthChecks);
RealConnectionconnection = streamAllocation.connection();
(2) return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
StreamAllocation:用來創建執行Http請求所須要的全部的網絡的組件(初始化在重定向攔截器中)
HttpCodec:經過建立好的StreamAllocation建立HttpCodec的對象
ps:HttpCodec 是用來編碼request 和解碼 response
RealConnection:實際用來進行網絡IO傳輸的對象
(1) ConnectInterceptor 從攔截器鏈中獲取到前面的Interceptor傳過來給StreamAllocaiton這個對象,而後執行streamAllocation.newStream方法(),初始化HttpCodec對象。該對象用於處理Request和Response對象
(2) 將剛纔建立的用於網絡IO的RealConnection對象,以及對於與服務器交互最爲關鍵的HttpCodec等對象傳遞給後面的攔截器
streamAllocation.newStream():
public HttpCodec newStream(OkHttpClient client,
Interceptor.Chain chain, boolean doExtensiveHealthChecks){
...
try {
//一、生成一個realConnection對象進行實際的網絡鏈接
RealConnectionresultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//二、經過獲取到的realConnection對象生成一個HttpCodec對象
HttpCodecresultCodec = resultConnection.newCodec(client, chain, this);
//同步代碼塊 返回一個HttpCodec對象
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
}
…
}
findHealthyConnection():
開啓一個while循環 調用findConnection進行二次封裝繼續獲取RealConnection
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
//若是realConnection的對象成功數量==0就返回,意味着整個網絡鏈接結束
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
//若是這個candidate是不健康的就銷燬資源操做
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue; //循環findConnection()獲取對象
}
findConnection():
//一、嘗試獲取這個連接,若是能夠複用connection 直接賦值給releasedConnection,而後判斷這個可複用的connection是否爲空,
不爲空就把這個連接connection賦值給RealConnection對象
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
二、若是RealConnection對象爲空,就從鏈接池中獲取一個實際的RealConnection
if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
…
//三、調用connect方法進行實際的網絡鏈接
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
ps:賦值操做!判斷這個鏈接是否已經創建了,若是已經創建就拋出異常,這個鏈接與否的標誌是protocol標誌位協議,表示在整個鏈接創建和可能的協議過程當中所要用到的協議
if (protocol != null) throw new IllegalStateException("already connected」);
if (route.requiresTunnel()) {
判斷是否創建Tunnerl鏈接
routeDatabase().connected(result.route());
...
// Pool the connection. 四、將這個實際的RealConnection鏈接放到鏈接池中
Internal.instance.put(connectionPool, result);
小結 ConnectInterceptor
一、創建RealConnection對象
二、選擇不一樣的鏈接方式
三、CallServerInterceptor
鏈接池
定義
OhHttp將客戶端和服務器端的鏈接抽象成Connection類,RealConnection是它的實現類,爲了管理這些鏈接的複用,因此有了ConnectionPool類。
- 當他們共享相同的地址就能夠複用鏈接!
- 在必定時間內,維護哪些鏈接是打開狀態,已被之後複用的策略
ps:http1.0 keep-alive機制 http2.0 多路複用機制
本章重點:
- 對鏈接池的Get、Put鏈接操做
- 對鏈接池的自動回收操做
鏈接池的 Get()
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
//一、for循環遍歷鏈接池當中的Connections,獲取可用的connection
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
streamAllocation.acquire():
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
//二、把從鏈接池當中獲取到的RealConnection對象賦值給StreamAllocation的成員變量
this.connection = connection;
this.reportedAcquired = reportedAcquired;
//三、將StreamAllocationReference的弱引用添加到RealConnection的allocation集合當中
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
ps:根據allocations這個ArrayList集合來判斷當前鏈接對象所持有的StreamAllocation的數目,
經過這個集合大小判斷一個網絡鏈接的負載量是否已經超過了他的最大值
鏈接池的 put()
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
//在添加之前 進行一個異步的清理任務
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);//如何回收connection的類
}
connections.add(connection);
}
小結
一、不論哪一個攔截器中,請求都會產生一個SteramAllocation對象
二、StreamAllocation對象的弱引用添加到RealConnection對象的allocations集合當中
三、從鏈接池中獲取連接
對鏈接池的自動回收操做
cleanupRunnable
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
//一、首次清理的時候必定返回下一次清理的間隔時間;cleanup具體GC回收算法
long waitNanos= cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
//二、等待釋放鎖和時間片,等待時間結束後再次執行runnbale
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
...
};
cleanup():相似Java裏的標記清除算法
首先標記出最不活躍的connection連接,或是叫泄漏連接,空閒連接
long cleanup(long now) {
…
//在同步代碼塊中標記泄漏的連接
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
…
//若是被標記的連接,好比空閒的Socket連接超過5個,就會從池子中刪除而後關閉連接
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
//當所有都是活躍連接的時候會返回
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
//若是目前還能塞進去連接 有可能有泄漏的連接也會返回keepAliveDurationNs下次使用已被
return keepAliveDurationNs;
} else { //若是沒有任何可用連接跳出死循環
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
思考:若是找到最不活躍的連接呢?
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
pruneAndGetAllocationCount():根據弱引用是否爲null來判斷
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
//一、遍歷維護弱引用的list
for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i);
//二、查看streamAllocation對象是否爲空,爲空表示沒有代碼引用該對象則進行刪除
if (reference.get() != null) {
i++;
continue;
}
…
references.remove(i);
connection.noNewStreams = true;
//三、當references對列爲空時候,表示整個維護的隊列已經被刪空了因此返回0,表示這個連接已經沒有引用了
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//四、不然返回非0的具體值
return references.size();
}
小結
一、OkHttp使用了GC回收算法
二、StreamAllocation的數量會漸漸減小爲0
三、被線程池檢測到並回收,這樣就能夠保持多個健康的Keep-alive鏈接
++++++++++++++++++++++++++++++++++++++++
D、CallServerInterceptor 鏈接攔截器
做用:
- 向服務器發起真正請求,
- 並接收服務器返回給的讀取響應並返回
//一、建立OkHttp攔截器的鏈,全部的請求正是經過鏈(proceed() )依次完成其任務
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//建立HttpCodec 對象,因此的流對象封裝成HttpCodec對象,用來編碼解碼的請求和響應
HttpCodec httpCodec = realChain.httpStream();
//建立Http請求所須要的其餘網絡設施組件,用來分配Stream流
StreamAllocation streamAllocation = realChain.streamAllocation();
//抽象客戶端與服務器端的鏈接的具體實現
RealConnection connection = (RealConnection) realChain.connection();
//網絡請求
Request request = realChain.request();
...
//二、利用httpCodec對象向Sokcet當中寫入請求的Header頭信息
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
…
//三、特殊狀況:詢問服務器是否能夠發送帶有請求體的信息,若是能接收這個規則的信息就返回給100的標示的響應嗎,
會跳過寫入body操做直接獲取響應信息
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
…
//四、調用body的writeTo()方法向Socket寫入body信息
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
…
//五、運行到這兒代表已經完成的整個網絡請求的寫入操做
httpCodec.finishRequest();
...
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
//六、讀取網絡響應信息中的頭部信息
responseBuilder = httpCodec.readResponseHeaders(false);
}
…
//七、讀取網絡響應的body信息
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(); //八、當已經創建好鏈接的時候,禁止新的流建立
}
//九、當響應嗎是20四、 205的時候拋出異常
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
//十、返回Response響應信息
return response;
}
小結
一、Call對象對請求的封裝
二、dispatcher分發器對Request請求的分發
三、getResponseWithInterceptors()方法進行攔截器鏈的構造
- RetryAndFollowUpInterceptor
- CacheInterceptor
- BridgeInterceptor
- ConnectionInterceptor
- CallServerInterceptor