OKHTTP 源碼解析

參考
徹底理解OkHttp - OkHttp 源碼解析及OkHttp的設(shè)計思想
Okhttp3源碼分析

image.png
  OkHttpClient okHttpClient = new OkHttpClient();
        OkHttpClient.Builder builder = okHttpClient.newBuilder();
        builder.addInterceptor(new HttpLoggingInterceptor());

        Request request = new Request.Builder()
                .url(url)
                .get()
                .build()
                ;

        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println(e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                    System.out.println(response.body().toString());
            }
        });
final class RealCall implements Call {
 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
}

public final class Dispatcher {
//TODO 同時能進行的最大請求數(shù)
  private int maxRequests = 64;
 //TODO 同時請求的相同HOST的最大個數(shù) SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
    //TODO 如 https://restapi.amap.com  restapi.amap.com - host
  private int maxRequestsPerHost = 5;

  /** Ready async calls in the order they'll be run. */
  /**
     * Ready async calls in the order they'll be run.
     * TODO 雙端隊列,支持首尾兩端 雙向開口可進可出觅够,方便移除
     * 異步等待隊列
     *
     */
  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<>();

 synchronized void enqueue(AsyncCall call) {
    // //TODO 同時請求不能超過并發(fā)數(shù)(64,可配置調(diào)度器調(diào)整)
        //TODO okhttp會使用共享主機即 地址相同的會共享socket
        //TODO 同一個host最多允許5條線程通知執(zhí)行請求
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //  //TODO 加入運行隊列 并交給線程池執(zhí)行
      runningAsyncCalls.add(call);
 //TODO AsyncCall 是一個runnable跪呈,放到線程池中去執(zhí)行碉输,查看其execute實現(xiàn)
      executorService().execute(call);
    } else {
//TODO 加入等候隊列
      readyAsyncCalls.add(call);
    }
  }
}
   //TODO 沒有核心線程 秫筏,非核心線程數(shù)量沒有限制隘冲,閑置60秒回收 任務(wù)隊列抵赢,
//這個線程池跟Android中的CachedThreadPool非常類似欺劳,這種類型的線程池唧取,
//適用于大量的耗時較短的異步任務(wù)
 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;
  }

call放到了線程池中那么它是如何執(zhí)行的呢?注意這里的call是AsyncCall划提。

我們看一下AsyncCall的實現(xiàn):

final class RealCall implements Call {
 //public abstract class NamedRunnable implements Runnable 
 final class AsyncCall extends NamedRunnable {
   private final Callback responseCallback;

   AsyncCall(Callback responseCallback) {
     super("OkHttp %s", redactedUrl());
     this.responseCallback = responseCallback;
   }

   //線程池實際上就是執(zhí)行了execute()
   @Override protected void execute() {
     boolean signalledCallback = false;
     try {
       //
       Response response = getResponseWithInterceptorChain();
       if (retryAndFollowUpInterceptor.isCanceled()) {
         signalledCallback = true;
         responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
       } else {
         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 {
      //TODO 移除隊列
       client.dispatcher().finished(this);
     }
   }
 }
}

值得注意的finally 執(zhí)行了client.dispatcher().finished(this); 通過調(diào)度器移除隊列枫弟,并且判斷是否存在等待隊列,如果存在鹏往,檢查執(zhí)行隊列是否達到最大值淡诗,如果沒有將等待隊列變?yōu)閳?zhí)行隊列。這樣也就確保了等待隊列被執(zhí)行伊履。

public final class Dispatcher {
  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

 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!");
      //promoteCalls=true,即異步調(diào)用
      if (promoteCalls) promoteCalls();
     //TODO 運行隊列的數(shù)量
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
      //閑置調(diào)用
    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }


  private void promoteCalls() {
    //正在執(zhí)行的異步隊列大于最大請求隊列 韩容,不執(zhí)行下面代碼
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    //等待隊列為空,不執(zhí)行下面代碼
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
      //  //TODO  相同host的請求沒有達到最大唐瀑,加入運行隊列
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        //將等待隊列加入到運行隊列中
        i.remove();
        runningAsyncCalls.add(call);
        //交給線程池來執(zhí)行
        executorService().execute(call);
      }
    //當(dāng)runningAsyncCalls滿了群凶,直接退出迭代
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }


}

真正的執(zhí)行網(wǎng)絡(luò)請求和返回響應(yīng)結(jié)果:getResponseWithInterceptorChain(),下面我們著重分析一下這個方法:

final class RealCall implements Call {
//TODO 核心代碼 開始真正的執(zhí)行網(wǎng)絡(luò)請求
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //TODO 責(zé)任鏈
    List<Interceptor> interceptors = new ArrayList<>();
    //TODO 在配置okhttpClient 時設(shè)置的intercept 由用戶自己設(shè)置
     ////首先添加的是用戶添加的全局?jǐn)r截器
    interceptors.addAll(client.interceptors());
    //TODO 負(fù)責(zé)處理失敗后的重試與重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //TODO 負(fù)責(zé)把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送到服務(wù)器的請求 哄辣、把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶友好的響應(yīng) 處理 配置請求頭等信息
    //TODO 從應(yīng)用程序代碼到網(wǎng)絡(luò)代碼的橋梁座掘。首先,它根據(jù)用戶請求構(gòu)建網(wǎng)絡(luò)請求柔滔。然后它繼續(xù)呼叫網(wǎng)絡(luò)。最后萍虽,它根據(jù)網(wǎng)絡(luò)響應(yīng)構(gòu)建用戶響應(yīng)睛廊。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //TODO 處理 緩存配置 根據(jù)條件(存在響應(yīng)緩存并被設(shè)置為不變的或者響應(yīng)在有效期內(nèi))返回緩存響應(yīng)
    //TODO 設(shè)置請求頭(If-None-Match、If-Modified-Since等) 服務(wù)器可能返回304(未修改)
    //TODO 可配置用戶自己設(shè)置的緩存攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //TODO 連接服務(wù)器 負(fù)責(zé)和服務(wù)器建立連接 這里才是真正的請求網(wǎng)絡(luò)
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //TODO 配置okhttpClient 時設(shè)置的networkInterceptors
      //TODO 返回觀察單個網(wǎng)絡(luò)請求和響應(yīng)的不可變攔截器列表杉编。
      interceptors.addAll(client.networkInterceptors());
    }
    //TODO 執(zhí)行流操作(寫出請求體超全、獲得響應(yīng)數(shù)據(jù)) 負(fù)責(zé)向服務(wù)器發(fā)送請求數(shù)據(jù)、從服務(wù)器讀取響應(yīng)數(shù)據(jù)
    //TODO 進行http請求報文的封裝與請求報文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //TODO 創(chuàng)建責(zé)任鏈
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    //TODO 執(zhí)行責(zé)任鏈
    return chain.proceed(originalRequest);
  }

}

從上述代碼中邓馒,可以看出都實現(xiàn)了Interceptor接口嘶朱,這是okhttp最核心的部分,采用責(zé)任鏈的模式來使每個功能分開光酣,每個Interceptor自行完成自己的任務(wù)疏遏,并且將不屬于自己的任務(wù)交給下一個,簡化了各自的責(zé)任和邏輯救军。

責(zé)任鏈模式是設(shè)計模式中的一種也相當(dāng)簡單參考鏈接财异,這里不在復(fù)述。

我們著重分析一下唱遭,okhttp的設(shè)計實現(xiàn)戳寸,如何通過責(zé)任鏈來進行傳遞返回數(shù)據(jù)的。

上述代碼中可以看出interceptors拷泽,是傳遞到了RealInterceptorChain該類實現(xiàn)了Interceptor.Chain疫鹊,并且執(zhí)行了chain.proceed(originalRequest)袖瞻。

其實核心代碼就是chain.proceed() 通過該方法進行責(zé)任鏈的執(zhí)行


public final class RealInterceptorChain implements Interceptor.Chain {
 @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 {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    return response;
  }
}

從上述代碼,我們可以知道拆吆,新建了一個RealInterceptorChain 責(zé)任鏈 并且 index+1聋迎,然后 執(zhí)行interceptors.get(index); 返回Response。

其實就是按順序執(zhí)行了攔截器锈拨,這里我畫了一個簡圖:

image.png

攔截器的執(zhí)行順序便是如上圖這樣執(zhí)行的砌庄。
這樣設(shè)計的一個好處就是,責(zé)任鏈中每個攔截器都會執(zhí)行chain.proceed()方法之前的代碼奕枢,等責(zé)任鏈最后一個攔截器執(zhí)行完畢后會返回最終的響應(yīng)數(shù)據(jù)娄昆,而chain.proceed() 方法會得到最終的響應(yīng)數(shù)據(jù),這時就會執(zhí)行每個攔截器的chain.proceed()方法之后的代碼缝彬,其實就是對響應(yīng)數(shù)據(jù)的一些操作萌焰。

CacheInterceptor 緩存攔截器就是很好的證明,我們來通過CacheInterceptor 緩存攔截器來進行分析谷浅,大家就會明白了扒俯。

/** Serves requests from the cache and writes responses to the cache. */
public final class CacheInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
//TODO 獲取request對應(yīng)緩存的Response 如果用戶沒有配置緩存攔截器 cacheCandidate == null
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
 //TODO 獲取緩存中(CacheStrategy)的Response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
   //TODO 緩存無效 關(guān)閉資源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
// If we're forbidden from using the network and the cache is insufficient, fail.
 //TODO networkRequest == null 不實用網(wǎng)路請求 且沒有緩存 cacheResponse == null  返回失敗
    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();
    }
  //TODO 不使用網(wǎng)絡(luò)請求 且存在緩存 直接返回響應(yīng)
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
}

上述的代碼,主要做了幾件事:

如果用戶自己配置了緩存攔截器一疯,cacheCandidate = cache.Response 獲取用戶自己存儲的Response,否則 cacheCandidate = null;同時從CacheStrategy 獲取cacheResponse 和 networkRequest
如果cacheCandidate 撼玄!= null 而 cacheResponse == null 說明緩存無效清楚cacheCandidate緩存陨闹。
如果networkRequest == null 說明沒有網(wǎng)絡(luò)鳍征,cacheResponse == null 沒有緩存,返回失敗的信息融虽,責(zé)任鏈此時也就終止眉睹,不會在往下繼續(xù)執(zhí)行荔茬。
如果networkRequest == null 說明沒有網(wǎng)絡(luò),cacheResponse != null 有緩存竹海,返回緩存的信息慕蔚,責(zé)任鏈此時也就終止,不會在往下繼續(xù)執(zhí)行斋配。

上部分代碼孔飒,其實就是沒有網(wǎng)絡(luò)的時候的處理。

/** Serves requests from the cache and writes responses to the cache. */
public final class CacheInterceptor implements Interceptor {
    //TODO 執(zhí)行下一個攔截器
    Response networkResponse = null;
    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());
      }
    }

 //TODO 網(wǎng)絡(luò)請求 回來 更新緩存
    // If we have a cache response too, then we're doing a conditional get.
    //TODO 如果存在緩存 更新
    if (cacheResponse != null) {
      //TODO 304響應(yīng)碼 自從上次請求后许起,請求需要響應(yīng)的內(nèi)容未發(fā)生改變
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    //TODO 緩存Response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    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);
      }

   if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;

}

下部分代碼主要做了這幾件事:

執(zhí)行下一個攔截器十偶,也就是請求網(wǎng)絡(luò)
責(zé)任鏈執(zhí)行完畢后,會返回最終響應(yīng)數(shù)據(jù)园细,如果緩存存在更新緩存惦积,如果緩存不存在加入到緩存中去。

這樣就體現(xiàn)出了猛频,責(zé)任鏈這樣實現(xiàn)的好處了狮崩,當(dāng)責(zé)任鏈執(zhí)行完畢蛛勉,如果攔截器想要拿到最終的數(shù)據(jù)做其他的邏輯處理等,這樣就不用在做其他的調(diào)用方法邏輯了睦柴,直接在當(dāng)前的攔截器就可以拿到最終的數(shù)據(jù)诽凌。
這也是okhttp設(shè)計的最優(yōu)雅最核心的功能。

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坦敌,一起剝皮案震驚了整個濱河市侣诵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狱窘,老刑警劉巖杜顺,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蘸炸,居然都是意外死亡躬络,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門搭儒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穷当,“玉大人,你說我怎么就攤上這事淹禾∧俨耍” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵铃岔,是天一觀的道長火邓。 經(jīng)常有香客問我,道長德撬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任躲胳,我火速辦了婚禮蜓洪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坯苹。我一直安慰自己隆檀,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布粹湃。 她就那樣靜靜地躺著恐仑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪为鳄。 梳的紋絲不亂的頭發(fā)上裳仆,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音孤钦,去河邊找鬼歧斟。 笑死纯丸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的静袖。 我是一名探鬼主播觉鼻,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼队橙!你這毒婦竟也來了坠陈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤捐康,失蹤者是張志新(化名)和其女友劉穎仇矾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吹由,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡若未,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了倾鲫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粗合。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖乌昔,靈堂內(nèi)的尸體忽然破棺而出隙疚,到底是詐尸還是另有隱情,我是刑警寧澤磕道,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布供屉,位于F島的核電站,受9級特大地震影響溺蕉,放射性物質(zhì)發(fā)生泄漏伶丐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一疯特、第九天 我趴在偏房一處隱蔽的房頂上張望哗魂。 院中可真熱鬧,春花似錦漓雅、人聲如沸录别。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽组题。三九已至,卻和暖如春抱冷,著一層夾襖步出監(jiān)牢的瞬間崔列,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工旺遮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留峻呕,地道東北人利职。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像瘦癌,于是被迫代替她去往敵國和親猪贪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容