介紹
OkHttp 是一套處理 HTTP 網路請求的依賴庫,由 Square 公司設計研發并開源,目前可以在 Java 和 Kotlin 中使用,對于 Android App 來說,OkHttp 現在幾乎已經占據了所有的網路請求操作,RetroFit + OkHttp 實作網路請求似乎成了一種標配,因此它也是每一個 Android 開發工程師的必備技能,了解其內部實作原理可以更好地進行功能擴展、封裝以及優化,
適用于 Android 和 Java 應用程式的 HTTP 和 HTTP/2 客戶端,
OkHttp的4.0.x版本已經全部由java替換到了Kotlin,API的一些使用也會有些不同,
Github傳送門
檔案和 API
要求
支持的版本
4.0.x :Android 5.0+(API 級別 21+)和 Java 8+,
3.12.x :Android 2.3+(API 級別 9+)和 Java 7+,平臺可能不支持 TLSv1.2,(2021-12-31不再支持)
OkHttp有一個庫的依賴Okio,用于高性能I/O一個小型library,它適用于 Okio 1.x(用 Java 實作)或 Okio 2.x(升級到 Kotlin),
本文使用的OkHttp的版本為3.14.2,不是不會接入高版本,主要是4.0.x版本已經全部由java替換到了Kotlin,Kotlin不太熟怕理解錯了,誤匯入民群眾,
dependencies {
//本文使用
implementation 'com.squareup.okio:okio:1.15.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.2'
//高版本
// define a BOM and its version
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))
// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
}
網路請求流程分析
OkHttp 經過幾次迭代后,已經發生了很多變化,更好的 WebSocket 支持、更多的 Interceptor 責任鏈,甚至連最核心的 HttpEngine 也變成了 HttpCodec,本文會重新梳理整個網路請求的流程,以及實作機制,
先看下 OkHttp 的基本使用:
public void okHttp(String url){
//創建OkHttpClient物件
OkHttpClient client = new OkHttpClient();
//創建Request
Request request = new Request.Builder()
.url(url)
.build();
//創建Call物件client.newCall(request)
//通過execute()方法獲得請求回應的Response物件
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
String result = response.body().string();
//處理UI需要切換到UI執行緒處理
}
}
});
}
除了直接 new OkHttpClient 之外,還可以使用內部工廠類 Builder 來設定 OkHttpClient,如下所示:
public void buildHttp(String url){
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(15, TimeUnit.SECONDS)//設定超時
.addInterceptor(interceptor) //攔截器
.proxy(proxy) //設定代理
.cache(cache); //設定快取
Request request = new Request.Builder()
.url(url)
.build();
builder.build().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {}
});
}
請求操作的起點從 OkHttpClient.newCall().enqueue() 方法開始
OkHttpClient.newCall
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall.newRealCall.java
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.transmitter = new Transmitter(client, call);
return call;
}
這個方法會回傳一個 RealCall 物件,通過它將網路請求操作添加到請求佇列中,
RealCall.enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
client.dispatcher()回傳Dispatcher,呼叫 Dispatcher 的 enqueue 方法,執行一個異步網路請求的操作,
Dispatcher 是 OkHttpClient 的調度器,是一種門戶模式,主要用來實作執行、取消異步請求操作,本質上是內部維護了一個執行緒池去執行異步操作,并且在 Dispatcher 內部根據一定的策略,保證最大并發個數、同一 host 主機允許執行請求的執行緒個數等,
Dispatcher.enqueue
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();
}
實際上就是使用執行緒池執行了一個 AsyncCall,而 AsyncCall 繼承了 NamedRunnable,NamedRunnable 實作了 Runnable 介面,因此整個操作會在一個子執行緒(非 UI 執行緒)中執行,
NamedRunnable
/**
* Runnable implementation which always sets its thread name.
*/
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();
}
在 run 方法中執行了 一個抽象方法 execute 這個抽象方法被 AsyncCall 實作,
AsyncCall.execute
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();
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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
從上面看出而真正獲取請求結果的方法是在 getResponseWithInterceptorChain 方法中,從名字也能看出其內部是一個攔截器的呼叫鏈,
RealCall.getResponseWithInterceptorChain
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
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, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
Interceptor:攔截器是一種強大的機制,可以監視、重寫和重試呼叫,
每一個攔截器的作用如下:
-
BridgeInterceptor:主要對 Request 中的 Head 設定默認值,比如 Content-Type、Keep-Alive、Cookie 等,
-
CacheInterceptor:負責 HTTP 請求的快取處理,
-
ConnectInterceptor:負責建立與服務器地址之間的連接,也就是 TCP 鏈接,
-
CallServerInterceptor:負責向服務器發送請求,并從服務器拿到遠端資料結果,
-
RetryAndFollowUpInterceptor:此攔截器從故障中恢復,并根據需要執行重定向,如果呼叫被取消,它可能會引發IOException,
在添加上述幾個攔截器之前,會呼叫 client.interceptors 將開發人員設定的攔截器添加到串列當中,
對于 Request 的 Head 以及 TCP 鏈接,我們能控制修改的成分不是很多,所以咱們了解 CacheInterceptor 和 CallServerInterceptor,
CacheInterceptor 快取攔截器
CacheInterceptor 主要做以下幾件事情:
1、根據 Request 獲取當前已有快取的 Response(有可能為 null),并根據獲取到的快取 Response,創建 CacheStrategy 物件,
2、 通過 CacheStrategy 判斷當前快取中的 Response 是否有效(比如是否過期),如果快取 Response 可用則直接回傳,否則呼叫 chain.proceed() 繼續執行下一個攔截器,也就是發送網路請求從服務器獲取遠端 Response,
3、如果從服務器端成功獲取 Response,再判斷是否將此 Response 進行快取操作,
CacheInterceptor.intercept
@Override public Response intercept(Chain chain) throws IOException {
//根據 Request 獲取當前已有快取的 Response(有可能為 null),并根據獲取到的快取 Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
//獲取當前時間
long now = System.currentTimeMillis();
//創建 CacheStrategy 物件
//通過 CacheStrategy 來判斷快取是否有效
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//如果我們被禁止使用網路,并且快取不足,則失敗,回傳空相應(Util.EMPTY_RESPONSE)
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();
}
// 如果快取有效,快取 Response 可用則直接回傳
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//沒有快取或者快取失敗,則發送網路請求從服務器獲取Response
Response networkResponse = null;
try {
//執行下一個攔截器,networkRequest
//發起網路請求
networkResponse = chain.proceed(networkRequest);
} finally {
//如果我們在I/O或其他方面崩潰,請不要泄漏cache body,
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
,,,
//通過網路獲取最新的Response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果開發人員有設定自定義cache,則將最新response快取
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//回傳response(快取或網路)
return response;
}
通過 Cache 實作快取功能
通過上面快取攔截器的流程可以看出,OkHttp 只是規范了一套快取策略,但是具體使用何種方式將資料快取到本地,以及如何從本地快取中取出資料,都是由開發人員自己定義并實作,并通過 OkHttpClient.Builder 的 cache 方法設定,
OkHttp 提供了一個默認的快取類 Cache.java,我們可以在構建 OkHttpClient 時,直接使用 Cache 來實作快取功能,只需要指定快取的路徑,以及最大可用空間即可,如下所示:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(15, TimeUnit.SECONDS)//設定超時
攔截器
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
return null;
}
})
//設定代理
.proxy(new Proxy(Proxy.Type.HTTP,null))
//設定快取
//AppGlobalUtils.getApplication() 通過反射得到Application實體
//getCacheDir內置 cache 目錄作為快取路徑
//maxSize 10*1024*1024 設定最大快取10MB
.cache(new Cache(AppGlobalUtils.getApplication().getCacheDir(),
10*1024*1024));
Cache 內部使用了 DiskLruCach 來實作具體的快取功能,如下所示:
/**
* Create a cache of at most {@code maxSize} bytes in {@code directory}.
*/
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
DiskLruCache 最侄訓將需要快取的資料保存在本地,如果感覺 OkHttp 自帶的這套快取策略太過復雜,我們可以設定使用 DiskLruCache 自己實作快取機制,
LRU:是近期最少使用的演算法(快取淘汰演算法),它的核心思想是當快取滿時,會優先淘汰那些近期最少使用的快取物件,采用LRU演算法的快取有兩種:LrhCache和DisLruCache,分別用于實作記憶體快取和硬碟快取,其核心思想都是LRU快取演算法,
CallServerInterceptor 詳解
CallServerInterceptor 是 OkHttp 中最后一個攔截器,也是 OkHttp 中最核心的網路請求部分,
CallServerInterceptor.intercept
@Override public Response intercept(Chain chain) throws IOException {
//獲取RealInterceptorChain
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//獲取Exchange
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
if (request.body().isDuplex()) {
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.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.
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
上面是向服務器端發送請求資料
-----強大的分割線----------
下面是從服務端獲取相應資料
并構建 Response 物件
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(response);
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(exchange.openResponseBody(response))
.build();
}
,,,
return response;
}
小結
首先 OkHttp 內部是一個門戶模式,所有的下發作業都是通過一個門戶 Dispatcher 來進行分發,
然后在網路請求階段通過責任鏈模式,鏈式的呼叫各個攔截器的 intercept 方法,重點介紹了 2 個比較重要的攔截器:CacheInterceptor 和 CallServerInterceptor,它們分別用來做請求快取和執行網路請求操作,
往期回顧
RecyclerView 繪制流程及Recycler快取
Glide 快取機制及原始碼(二)
Glide 的簡單使用(一)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/298694.html
標籤:其他
