okHttp 源碼解析

OkHttp 源碼詳解

OkHttp應(yīng)該是目前Android平臺上使用最為廣泛的開源網(wǎng)絡(luò)庫了处面,Android 在6.0之后也將內(nèi)部的HttpUrlConnection的默認實現(xiàn)替換成了OkHttp玉转。

這篇文章的目的俗慈,了解okhttp的框架原理柑营,以及責(zé)任鏈模式的使用烁竭。

1源梭、發(fā)送請求

首先看一下稳吮,怎么發(fā)出一個同步/異步請求缎谷。

        /**
     * 同步發(fā)起請求
     *
     * @throws IOException
     */
    private void execute() throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("your url").build();
        Response syncResponse = client.newCall(request).execute();
    }

    /**
     * 異步發(fā)起請求
     *
     * @throws IOException
     */
    private void enqueue() {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("your url").build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            }
        });
    }

這一段代碼就是日常使用OkHttp最常見的用法,跟進源碼后盖高,可以得到一張更為詳細的流程圖慎陵,通過這張圖來看下內(nèi)部的邏輯是如何流動的。

809143-20180816162138233-707076029.png

其實很簡單喻奥,只有幾個核心類席纽,我們一個個來看一下。

  1. OkHttpClient
  2. Request 和 Response
  3. RealCall

OkHttpClient:這個是整個OkHttp的核心管理類撞蚕,所有的內(nèi)部邏輯和對象歸OkHttpClient統(tǒng)一來管理润梯,它通過Builder構(gòu)造器生成,構(gòu)造參數(shù)和類成員很多甥厦,這里先不做具體的分析纺铭。

Request 和Response:Request是我們發(fā)送請求封裝類,內(nèi)部有url, header , method刀疙,body等常見的參數(shù)舶赔,Response是請求的結(jié)果,包含code, message, header,body 谦秧;這兩個類的定義是完全符合Http協(xié)議所定義的請求內(nèi)容和響應(yīng)內(nèi)容竟纳。

RealCall:負責(zé)請求的調(diào)度(同步的話走當(dāng)前線程發(fā)送請求,異步的話則使用OkHttp內(nèi)部的線程池進行)疚鲤;同時負責(zé)構(gòu)造內(nèi)部邏輯責(zé)任鏈锥累,并執(zhí)行責(zé)任鏈相關(guān)的邏輯,直到獲取結(jié)果集歇。雖然OkHttpClient是整個OkHttp的核心管理類桶略,但是真正發(fā)出請求并且組織邏輯的是RealCall類,它同時肩負了調(diào)度和責(zé)任鏈組織的兩大重任,接下來我們來著重分析下RealCall類的邏輯际歼。

大致總結(jié)就是惶翻,OkHttpClient 和 Request 都是使用了 Builder 設(shè)計模式,然后蹬挺,Request 通過 OkHttpClient 把 同步/異步 請求發(fā)送出去.

2维贺、同步/異步

我們跟進源碼它掂,看一下同步/異步的請求原理

public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到最終的請求處理是 dispatcher 來完成的巴帮,接下來看下 dispatcher

 //最大并發(fā)請求書
 private int maxRequests = 64;
 //每個主機的最大請求數(shù)
 private int maxRequestsPerHost = 5;
 private Runnable idleCallback;

 /** 執(zhí)行的線程池. Created lazily. */
 private ExecutorService executorService;

//將要運行的異步請求隊列
 /** Ready async calls in the order they'll be run. */
 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

//正在執(zhí)行的異步請求隊列
 /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在執(zhí)行的同步請求隊列
 /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();


 public Dispatcher(ExecutorService executorService) {
   this.executorService = executorService;
 }

 public Dispatcher() {
 }

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

Dispatcher 有兩個構(gòu)造方法,可以自己指定線程池虐秋, 如果沒有指定榕茧, 則會默認創(chuàng)建默認線程池,可以看到核心數(shù)為0客给,緩存數(shù)可以是很大用押, 比較適合執(zhí)行大量的耗時比較少的任務(wù)。

接著看 enqueue是如何實現(xiàn)的

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

當(dāng)正在運行的異步請求隊列中的數(shù)量小于64靶剑, 并且 正在運行的請求主機數(shù)小于5蜻拨,把請求加載到runningAsyncCalls 中并在線程池中執(zhí)行, 否則就加入到 readyAsyncCalls 進行緩存等待桩引。

上面可以看到傳遞進來的是 AsyncCall 然后 execute 那我們看下 AsyncCall方法

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

看到 NamedRunnable 實現(xiàn)了 Runnable缎讼,AsyncCall 中的 execute 是對網(wǎng)絡(luò)請求的具體處理。

Response response = getResponseWithInterceptorChain();

能明顯看出這就是對請求的處理坑匠,在看它的具體實現(xiàn)之前先看下 client.dispatcher().finished 的方法實現(xiàn)血崭。

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

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

// 最后調(diào)用這個
  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;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

由于 promoteCalls 是true 我們看下 promoteCalls 的方法實現(xiàn)

private void promoteCalls() {
  if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
  if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

  for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
    AsyncCall call = i.next();

    if (runningCallsForHost(call) < maxRequestsPerHost) {
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
    }

    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

根據(jù)代碼可以明顯看出 , 當(dāng)一個請求結(jié)束了調(diào)用 finished 方法厘灼,最終到promoteCalls就是把 異步等待隊列中的請求夹纫,取出放到 異步執(zhí)行隊列中。

  • 如果異步執(zhí)行隊列已經(jīng)是滿的狀態(tài)就不加了设凹,return
  • 如果 異步等待隊列中 沒有需要執(zhí)行的網(wǎng)絡(luò)請求 也就沒有必要進行下一步了 return
  • 上面的兩條都沒遇到舰讹,遍歷 異步等待隊列,取出隊首的請求闪朱,如果這個請求的 host 符合 (正在執(zhí)行的網(wǎng)絡(luò)請求中 同一個host最多只能是5個)的這個條件月匣, 把 等待隊列的這個請求移除, 加入到 正在執(zhí)行的隊列中监透, 線程開始執(zhí)行桶错。 如果不符合繼續(xù) 遍歷操作。

3胀蛮、interceptors 攔截器

接著看 RealCall 的 getResponseWithInterceptorChain 方法

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //用戶自己定義的攔截器
    interceptors.addAll(client.interceptors());
    //系統(tǒng)提供的重試攔截器院刁,失敗后的重試和重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //負責(zé)把用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送到服務(wù)器的請求 、把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶友好的響應(yīng) 處理 配置請求頭等信息
    //從應(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()));
    //處理 緩存配置 根據(jù)條件(存在響應(yīng)緩存并被設(shè)置為不變的或者響應(yīng)在有效期內(nèi))返回緩存響應(yīng)
    //設(shè)置請求頭(If-None-Match、If-Modified-Since等) 服務(wù)器可能返回304(未修改)
    //可配置用戶自己設(shè)置的緩存攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //連接攔截器 這里才是真正的請求網(wǎng)絡(luò)
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        //配置okhttpClient 時設(shè)置的networkInterceptors
       //返回觀察單個網(wǎng)絡(luò)請求和響應(yīng)的不可變攔截器列表嗅蔬。
      interceptors.addAll(client.networkInterceptors());
    }
    //執(zhí)行流操作(寫出請求體剑按、獲得響應(yīng)數(shù)據(jù)) 負責(zé)向服務(wù)器發(fā)送請求數(shù)據(jù)、從服務(wù)器讀取響應(yīng)數(shù)據(jù)
    //進行http請求報文的封裝與請求報文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //創(chuàng)建責(zé)任鏈
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
        //執(zhí)行 責(zé)任鏈
    return chain.proceed(originalRequest);
  }

看下 RealInterceptorChain 的實現(xiàn)

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
    Connection connection) throws IOException {
 
  if (index >= interceptors.size()) throw new AssertionError();

  calls++;
  //創(chuàng)建新的攔截鏈澜术,鏈中的攔截器集合index+1
  RealInterceptorChain next = new RealInterceptorChain(
      interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //  執(zhí)行當(dāng)前的攔截器
  Interceptor interceptor = interceptors.get(index);
  // 執(zhí)行攔截器
  Response response = interceptor.intercept(next);

     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");
  }
  
  return response;
}

根據(jù)上面的代碼 我們可以看出艺蝴,新建了一個RealInterceptorChain 責(zé)任鏈 并且 index+1,然后 執(zhí)行interceptors.get(index); 返回Response鸟废。

責(zé)任鏈中每個攔截器都會執(zhí)行chain.proceed()方法之前的代碼猜敢,等責(zé)任鏈最后一個攔截器執(zhí)行完畢后會返回最終的響應(yīng)數(shù)據(jù),而chain.proceed() 方法會得到最終的響應(yīng)數(shù)據(jù)盒延,這時就會執(zhí)行每個攔截器的chain.proceed()方法之后的代碼缩擂,其實就是對響應(yīng)數(shù)據(jù)的一些操作。

結(jié)合源碼添寺,可以得到如下結(jié)論:

攔截器按照添加順序依次執(zhí)行
攔截器的執(zhí)行從RealInterceptorChain.proceed()開始胯盯,進入到第一個攔截器的執(zhí)行邏輯
每個攔截器在執(zhí)行之前,會將剩余尚未執(zhí)行的攔截器組成新的RealInterceptorChain
攔截器的邏輯被新的責(zé)任鏈調(diào)用next.proceed()切分為start畦贸、next.proceed陨闹、end這三個部分依次執(zhí)行
next.proceed() 所代表的其實就是剩余所有攔截器的執(zhí)行邏輯
所有攔截器最終形成一個層層內(nèi)嵌的嵌套結(jié)構(gòu)

了解了上面攔截器的構(gòu)造過程,我們再來一個個的分析每個攔截器的功能和作用薄坏。
從代碼來看趋厉,總共添加了五個攔截器(不包含自定義的攔截器如client.interceptors和client.networkInterceptors,這兩個后面再解釋)胶坠。
我們本次分享君账,只做整體框架的解讀,具體每個攔截器的處理不在此做出贅述沈善。

  • retryAndFollowUpInterceptor——失敗和重定向攔截器
  • BridgeInterceptor——封裝request和response攔截器
  • CacheInterceptor——緩存相關(guān)的過濾器乡数,負責(zé)讀取緩存直接返回、更新緩存
  • ConnectInterceptor——連接服務(wù)闻牡,負責(zé)和服務(wù)器建立連接 這里才是真正的請求網(wǎng)絡(luò)
  • CallServerInterceptor——執(zhí)行流操作(寫出請求體净赴、獲得響應(yīng)數(shù)據(jù)) 負責(zé)向服務(wù)器發(fā)送請求數(shù)據(jù)、從服務(wù)器讀取響應(yīng)數(shù)據(jù) 進行http請求報文的封裝與請求報文的解析

4罩润、攔截器demo

個人覺得玖翅,okhttp的精髓之一,就是攔截器的理念,針對本理念金度,寫了個簡單易懂的demo应媚,請笑納。

Interceptor攔截器接口

public interface Interceptor {
    String intercept(OkHttpChain chain);
}

Interceptor攔截器鏈

public class OkHttpChain {
    private int index = 0;
    private List<Interceptor> interceptors;

    public OkHttpChain(List<Interceptor> interceptors, int index) {
        this.interceptors = interceptors;
        this.index = index;
    }

    public String process() {
        if (index >= interceptors.size()) {
            return "";
        }
        OkHttpChain next = new OkHttpChain(interceptors, index + 1);
        Interceptor interceptor = interceptors.get(index);
        return interceptor.intercept(next);
    }

    public void add(Interceptor interceptor) {
        interceptors.add(interceptor);
    }
}

三個Interceptor攔截器

public class AInterceptor implements Interceptor {
    @Override
    public String intercept(OkHttpChain chain) {
        System.out.println("Interceptor_A");
        return "A" + chain.process();
    }
}
public class BInterceptor implements Interceptor {
    @Override
    public String intercept(OkHttpChain chain) {
        System.out.println("Interceptor_B");
        return "_B" + chain.process();
    }
}
public class CInterceptor implements Interceptor {
    @Override
    public String intercept(OkHttpChain chain) {
        System.out.println("Interceptor_C");
        return "_C" + chain.process();
    }
}

執(zhí)行

    public static void main(String[] args) {
        List<Interceptor> interceptors = new ArrayList<>();
        OkHttpChain chain = new OkHttpChain(interceptors, 0);
        interceptors.add(new AInterceptor());
        interceptors.add(new BInterceptor());
        interceptors.add(new CInterceptor());
        String result = chain.process();
        System.out.println("result = " + result);
    }

執(zhí)行結(jié)果為:

Interceptor_A
Interceptor_B
Interceptor_C
result = A_B_C

參考博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猜极,一起剝皮案震驚了整個濱河市中姜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跟伏,老刑警劉巖丢胚,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酬姆,居然都是意外死亡嗜桌,警方通過查閱死者的電腦和手機奥溺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門辞色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浮定,你說我怎么就攤上這事相满。” “怎么了桦卒?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵立美,是天一觀的道長。 經(jīng)常有香客問我方灾,道長建蹄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任裕偿,我火速辦了婚禮洞慎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嘿棘。我一直安慰自己劲腿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布鸟妙。 她就那樣靜靜地躺著焦人,像睡著了一般。 火紅的嫁衣襯著肌膚如雪重父。 梳的紋絲不亂的頭發(fā)上花椭,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音房午,去河邊找鬼矿辽。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嗦锐。 我是一名探鬼主播嫌松,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奕污!你這毒婦竟也來了萎羔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤碳默,失蹤者是張志新(化名)和其女友劉穎贾陷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘱根,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡髓废,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了该抒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慌洪。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凑保,靈堂內(nèi)的尸體忽然破棺而出冈爹,到底是詐尸還是另有隱情,我是刑警寧澤欧引,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布频伤,位于F島的核電站,受9級特大地震影響芝此,放射性物質(zhì)發(fā)生泄漏憋肖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一婚苹、第九天 我趴在偏房一處隱蔽的房頂上張望岸更。 院中可真熱鬧,春花似錦租副、人聲如沸坐慰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽结胀。三九已至,卻和暖如春责循,著一層夾襖步出監(jiān)牢的瞬間糟港,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工院仿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秸抚,地道東北人速和。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像剥汤,于是被迫代替她去往敵國和親颠放。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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