okhttp源碼分析

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

目錄

前言

okhttp是square開源的輕量級網(wǎng)絡(luò)框架

依賴方式

"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í)都大同小異,主要分為如下幾步

  1. 創(chuàng)建okHttpClient對象

OkHttpClient對象有兩種創(chuàng)建方式,一種是直接new OKHttpClient(),還有一種可以通過建造者模式new OkHttpClient.Builder()添加其他屬性。實(shí)際上okhttp在默認(rèn)的構(gòu)造方法中就已經(jīng)賦值了很多屬性的默認(rèn)值

  1. 創(chuàng)建request對象

每一個http請求都包含請求url、請求方法(GET/POST)、請求頭(Header),同時也可能包含請求體(Body

  1. 創(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功能

  1. 維護(hù)請求的狀態(tài)

一個Call只能被執(zhí)行一次,否則拋出異常

  1. 維護(hù)請求隊列
  • runningSyncCalls:正在運(yùn)行的同步請求隊列
  • readyAsyncCalls:正在執(zhí)行的異步請求隊列,請求最大運(yùn)行數(shù)不能超過64,最大主機(jī)連接數(shù)不能超過5
  • runningAsyncCalls:緩存等待的異步請求隊列
  1. 維護(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 IntercetorNetwork Interceptor

攔截器.png

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

  1. 不需要去關(guān)心中發(fā)生的重定向和重試操作。因為它處于第一個攔截器,會獲取到最終的響應(yīng)
  2. 只會被調(diào)用一次,即使這個響應(yīng)是從緩存中獲取的
  3. 只關(guān)注最原始的請求,不去關(guān)系請求的資源是否發(fā)生了改變,我只關(guān)注最后的 response 結(jié)果而已
  4. 因為是第一個被執(zhí)行的攔截器,因此它有權(quán)決定了是否要調(diào)用其他攔截,也就是 Chain.proceed() 方法是否要被執(zhí)行
  5. 因為是第一個被執(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 得到更多的信息了

  1. 因為 NetworkInterceptor 是排在第 6 個攔截器中,因此可以操作經(jīng)過 RetryAndFollowup 進(jìn)行失敗重試或者重定向之后得到的resposne
  2. 為響應(yīng)直接從 CacheInterceptor 返回了
  3. 觀察數(shù)據(jù)在網(wǎng)絡(luò)中的傳輸
  4. 可以獲得裝載請求的連接。

注意事項

  1. 推薦讓 OkHttpClient 保持單例,用同一個 OkHttpClient 實(shí)例來執(zhí)行你的所有請求,因為每一個 OkHttpClient 實(shí)例都擁有自己的連接池和線程池,重用這些資源可以減少延時和節(jié)省資源,如果為每個請求創(chuàng)建一個 OkHttpClient 實(shí)例,顯然就是一種資源的浪費(fèi)。
  2. response.body().string()只調(diào)用一次
  3. 每一個CallRealCall)只能執(zhí)行一次,否則會報異常
  4. 子線程加載數(shù)據(jù)后,主線程刷新數(shù)據(jù)

總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容