如需轉(zhuǎn)載請評論或簡信,并注明出處,未經(jīng)允許不得轉(zhuǎn)載

目錄

前言
okhttp是square開源的輕量級網(wǎng)絡(luò)框架
- 官網(wǎng):https://square.github.io/okhttp/
- github地址:https://github.com/square/okhttp
依賴方式
"com.squareup.okhttp3:okhttp:4.2.1"
使用步驟
先來看看okhttp的基本使用方法。本文主要介紹原理,更多的使用方式可參考o(jì)khttp官網(wǎng)
GET請求
//1.創(chuàng)建OKHttpClient對象
OkHttpClient client = new OkHttpClient();
String get(String url) throws IOException {
//2.創(chuàng)建Request對象
Request request = new Request.Builder()
.url(url)
.build();
//3.創(chuàng)建NewCall對象
//4.執(zhí)行newCall.execute(),生成response對象
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
POST請求
//1.創(chuàng)建OKHttpClient對象
OkHttpClient client = new OkHttpClient();
//2.設(shè)置mediaType
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
String post(String url, String json) throws IOException {
//3.創(chuàng)建請求體
RequestBody body = RequestBody.create(JSON, json);
//4.創(chuàng)建Request對象
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
//5.創(chuàng)建NewCall對象
//6.執(zhí)行newCall.execute(),生成response對象
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
可以看出GET請求和POST請求的步驟其實(shí)都大同小異,主要分為如下幾步
- 創(chuàng)建okHttpClient對象
OkHttpClient對象有兩種創(chuàng)建方式,一種是直接new OKHttpClient(),還有一種可以通過建造者模式new OkHttpClient.Builder()添加其他屬性。實(shí)際上okhttp在默認(rèn)的構(gòu)造方法中就已經(jīng)賦值了很多屬性的默認(rèn)值
- 創(chuàng)建request對象
每一個http請求都包含請求url、請求方法(GET/POST)、請求頭(Header),同時也可能包含請求體(Body)
- 創(chuàng)建response對象
response就是對request請求的一個回復(fù),通過執(zhí)行newCall中的execute()生成
源碼分析
okhttp支持同步請求和異步請求,execute()是同步請求,enqueue()是異步請求。下面我們通過源碼來看一下response是如何創(chuàng)建的(本人當(dāng)前使用的okhttp版本的源碼是基于kotlin)
同步請求
RealCall.kt
override fun execute(): Response {
synchronized(this) {
//1.先檢查newcall是否被執(zhí)行過,被執(zhí)行過會拋出異常
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
//2.通過dispatcher調(diào)度器執(zhí)行
client.dispatcher.executed(this)
//3.通過執(zhí)行一系列攔截器鏈,最終返回response
//☆關(guān)于攔截器的內(nèi)容下文會重點(diǎn)分析
return getResponseWithInterceptorChain()
} finally {
//4.執(zhí)行完請求后,將請求從dispatcher調(diào)度器移除
client.dispatcher.finished(this)
}
}
Dispather.kt
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//正在運(yùn)行的同步請求隊列
private val runningSyncCalls = ArrayDeque<RealCall>()
@Synchronized internal fun executed(call: RealCall) {
//將call請求放到雙向隊列中
runningSyncCalls.add(call)
}
異步請求
RealCall.kt
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
//1.和同步請求一樣,先檢查newcall是否被執(zhí)行過,被執(zhí)行過會拋出異常
check(!executed) { "Already Executed" }
executed = true
}
transmitter.callStart()
//2.執(zhí)行dispatcher.enqueue()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Dispather.kt
/** Ready async calls in the order they'll be run. */
//緩存等待的異步請求隊列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//正在執(zhí)行的異步請求隊列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
//執(zhí)行異步任務(wù)的線程池創(chuàng)建
//SynchronousQueue是一個內(nèi)部只能包含一個元素的隊列。
//插入元素到隊列的線程被阻塞,直到另一個線程從隊列中獲取了隊列中存儲的元素。
//同樣,如果線程嘗試獲取元素并且當(dāng)前不存在任何元素,則該線程將被阻塞,直到線程將元素插入隊列。
//類似生產(chǎn)者和消費(fèi)者模型
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("OkHttp Dispatcher", false))
}
return executorServiceOrNull!!
}
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//先將請求添加到緩存等待隊列
readyAsyncCalls.add(call)
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//執(zhí)行請求
promoteAndExecute()
}
private fun promoteAndExecute(): Boolean {
assert(!Thread.holdsLock(this))
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//請求最大運(yùn)行數(shù)不能超過64(maxRequests=64),最大主機(jī)連接數(shù)不能超過5(maxRequestsPerHost=5)
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // Host max capacity.
//從緩存等待隊列中移除
i.remove()
asyncCall.callsPerHost().incrementAndGet()
executableCalls.add(asyncCall)
//添加到正在執(zhí)行的異步請求隊列
runningAsyncCalls.add(asyncCall)
}
//運(yùn)行數(shù)>0說明正在運(yùn)行
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//executorService是一個線程池ThreadPoolExecutor對象
asyncCall.executeOn(executorService)
}
return isRunning
}
Dispather功能
- 維護(hù)請求的狀態(tài)
一個Call只能被執(zhí)行一次,否則拋出異常
- 維護(hù)請求隊列
-
runningSyncCalls:正在運(yùn)行的同步請求隊列 -
readyAsyncCalls:正在執(zhí)行的異步請求隊列,請求最大運(yùn)行數(shù)不能超過64,最大主機(jī)連接數(shù)不能超過5 -
runningAsyncCalls:緩存等待的異步請求隊列
- 維護(hù)線程池
創(chuàng)建了一個閥值是Integer.MAX_VALUE的線程池,它不保留任何最小線程,隨時創(chuàng)建更多的線程數(shù),而且如果線程空閑后,只能多活60秒。所以也就說如果收到20個并發(fā)請求,線程池會創(chuàng)建20個線程,當(dāng)完成后的60秒后會自動關(guān)閉所有20個線程。他這樣設(shè)計成不設(shè)上限的線程,以保證I/O任務(wù)中高阻塞低占用的過程,不會長時間卡在阻塞上
攔截器
通過response的創(chuàng)建過程的分析,我們發(fā)現(xiàn)okhttp在返回response的過程中,會經(jīng)過一系列的攔截器
攔截器是okhttp提供的一種強(qiáng)大的機(jī)制,可以監(jiān)視,重寫和重試網(wǎng)絡(luò)請求。下面是一個簡單的攔截器,用于記錄請求和相應(yīng)的日志
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
//1.請求前--打印請求信息
long t1 = System.nanoTime();
Log.i(TAG, String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
//網(wǎng)絡(luò)請求,并繼續(xù)執(zhí)行攔截器鏈
Response response = chain.proceed(request);
//3.網(wǎng)絡(luò)響應(yīng)后--打印響應(yīng)信息
long t2 = System.nanoTime();
Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
攔截器可以鏈接。假設(shè)同時具有壓縮攔截器和校驗攔截器,則確定是先壓縮數(shù)據(jù)然后對校驗和進(jìn)行校驗,還是先對數(shù)據(jù)進(jìn)行校驗和然后進(jìn)行壓縮。okhttp使用ArrayList來管理攔截器列表,并通過責(zé)任鏈模式按順序執(zhí)行攔截器。用戶可傳入的 interceptor 分為兩類:Application Intercetor和Network Interceptor

Application interceptors 應(yīng)用攔截器
okClient.addInterceptor(new LoggingInterceptor())
Application Interceptor 是第一個 Interceptor 因此它會被第一個執(zhí)行,因此這里的 request 還是最原始的。而對于 response 而言呢,因為整個過程是遞歸的調(diào)用過程,因此它會在 CallServerInterceptor 執(zhí)行完畢之后才會將 response 進(jìn)行返回,因此在 Application Interceptor 這里得到的 response 就是最終的響應(yīng),雖然中間有重定向,但是這里只關(guān)心最終的 response
- 不需要去關(guān)心中發(fā)生的重定向和重試操作。因為它處于第一個攔截器,會獲取到最終的響應(yīng)
- 只會被調(diào)用一次,即使這個響應(yīng)是從緩存中獲取的
- 只關(guān)注最原始的請求,不去關(guān)系請求的資源是否發(fā)生了改變,我只關(guān)注最后的 response 結(jié)果而已
- 因為是第一個被執(zhí)行的攔截器,因此它有權(quán)決定了是否要調(diào)用其他攔截,也就是 Chain.proceed() 方法是否要被執(zhí)行
- 因為是第一個被執(zhí)行的攔截器,因此它有可以多次調(diào)用
Chain.proceed()方法,其實(shí)也就是相當(dāng)與重新請求的作用了
Network Interceptors 網(wǎng)絡(luò)攔截器
okClient.addNetworkInterceptor(new LoggingInterceptor())
NetwrokInterceptor 處于第 6 個攔截器中,它會經(jīng)過 RetryAndFollowIntercptor 進(jìn)行重定向并且也會通過 BridgeInterceptor 進(jìn)行 request 請求頭和 響應(yīng) resposne 的處理,因此這里可以得到的是更多的信息。在打印結(jié)果可以看到它內(nèi)部是發(fā)生了一次重定向操作,所以NetworkInterceptor 可以比 Application Interceptor 得到更多的信息了
- 因為
NetworkInterceptor是排在第 6 個攔截器中,因此可以操作經(jīng)過RetryAndFollowup進(jìn)行失敗重試或者重定向之后得到的resposne - 為響應(yīng)直接從
CacheInterceptor返回了 - 觀察數(shù)據(jù)在網(wǎng)絡(luò)中的傳輸
- 可以獲得裝載請求的連接。
注意事項
- 推薦讓
OkHttpClient保持單例,用同一個OkHttpClient實(shí)例來執(zhí)行你的所有請求,因為每一個OkHttpClient實(shí)例都擁有自己的連接池和線程池,重用這些資源可以減少延時和節(jié)省資源,如果為每個請求創(chuàng)建一個OkHttpClient實(shí)例,顯然就是一種資源的浪費(fèi)。 -
response.body().string()只調(diào)用一次 - 每一個
Call(RealCall)只能執(zhí)行一次,否則會報異常 - 子線程加載數(shù)據(jù)后,主線程刷新數(shù)據(jù)
總結(jié)
