Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

青蛙要fly發表於2018-11-13

前言:

本文也做了一次標題黨,哈哈,其實寫的還是很水,各位原諒我O(∩_∩)O。

介於自己的網路方面知識爛的一塌糊塗,所以準備寫相關網路的文章,但是考慮全部寫在一篇太長了,所以分開寫,希望大家能仔細看,最好可以指出我的錯誤,讓我也能糾正。

1.講解相關的整個網路體系結構:

Android技能樹 — 網路小結(1)之網路體系結構

2.講解相關網路的重要知識點,比如很多人都聽過相關網路方面的名詞,但是僅限於聽過而已,什麼tcp ,udp ,socket ,websocket, http ,https ,然後webservice是啥,跟websocket很像,socket和websocket啥關係長的也很像,session,token,cookie又是啥。

Android技能樹 — 網路小結(2)之TCP/UDP

Android技能樹 — 網路小結(3)之HTTP/HTTPS

Android技能樹 — 網路小結(4)之socket/websocket/webservice

相關網路知識點小結- cookie/session/token(待寫)

3.相關的第三方框架的原始碼解析,畢竟現在面試個大點的公司,okhttp和retrofit原始碼是必問的。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

Android技能樹 — 網路小結(7)之 Retrofit原始碼詳細解析


這裡提一個本文無關的小知識點,很多文章開頭都會提到,我們以okhttp3.xxx版本來講解,那怎麼看當前最新的已經是幾了呢?(主要以前也有人問過我在哪裡檢視xxx第三方庫最新的版本,所以想到提一下這個)其實很簡單,我們以okhttp為例:

  1. Android Studio直接檢視:
    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
  2. JCenter上檢視: JCenter上搜尋Okhttp版本
    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
  3. Maven上檢視: Maven上搜尋Okhttp版本
    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
  4. ........其他方式

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

正文

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

看不清楚的,可以右鍵,選擇新標籤頁中開啟,然後點選圖片放大

首先我們來確定總體大綱:

  1. okhttp相關引數配置,比如設定超時時間,網路路徑等等等等等.......
  2. 我們知道在使用okhttp的時候可以使用同步請求,也可以使用非同步請求,所以肯定不同的請求,在分發的時候有不同的處理。
  3. 我們以前網路系列的文章提過,傳送到後臺,肯定是一個完整的請求包,但是我們使用okhttp的時候,只是轉入了我們需要給後臺的引數,甚至我們如果是get請求,只是傳入了相應的url網路地址就能拿到資料,說明okhttp幫我們把簡單的引數輸入,然後通過一系列的新增封裝,然後變成一個完整的網路請求包出去,然後我們在使用okhttp的時候,拿到返回的資料也已經是我們可以直接用的物件,說明接受的時候,已經幫我們把拿到的返回網路包,解析成我們直接用的物件了所以在一系列幫我們傳送的時候新增引數變成完整網路請求包,收到時候幫我們解析返回請求包的過程,是Okhttp的一個個攔截器們所處理,它攔截到我們的資料,然後進行處理,比如新增一些資料,變成完整的網路請求包等操作

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

所以我們大概就知道了okhttp一般的主要內容為這三大塊。

1.okhttp基礎使用:

講解原始碼前,先寫上okhttp基本使用,這樣才更方便講解原始碼:

String url = "http://www.baidu.com";
//'1. 生成OkHttpClient例項物件'
OkHttpClient okHttpClient = new OkHttpClient();
//'2. 生成Request物件'
Request request = new Request.Builder().url(url).build();
//'3. 生成Call物件'
Call call = okHttpClient.newCall(request);
//'4. 如果要執行同步請求:'
try {
    call.execute();
} catch (IOException e) {
    e.printStackTrace();
}
//'5. 如果要執行非同步請求:'
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
    }
});
複製程式碼

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

2. 初始化相關引數解析:

我們來看我們最剛開始的完整流程圖:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

然後配合上面第一步的okhttp基本使用,發現在執行同步和非同步前,我們要先準備好OkhttpClientRequestCall物件。我們一步步來看相關原始碼:

2.1 OkHttpClient相關:

我們上面的程式碼例項化OkHttpClient物件的程式碼是:

OkHttpClient okHttpClient = new OkHttpClient();
複製程式碼

我們進入檢視:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
發現OkHttpClient除了空引數的建構函式,還有一個傳入Builder的建構函式,而我們的new OkHttpClient()最終也是呼叫了傳入Builder的建構函式,只不過傳入預設的Builder物件值,如下圖所示:
Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

我們可以看到最後幾個值:

......
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
......
複製程式碼

預設的連線超時,讀取超時,寫入超時,都為10秒,然後還有其他等預設屬性,那我們加入想要改變這些屬性值呢,比如超時時間改為20秒,很簡單。我們不使用預設的Builder物件即可:

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(20,TimeUnit.SECONDS);
builder.readTimeout(20,TimeUnit.SECONDS);
builder.writeTimeout(20,TimeUnit.SECONDS);
OkHttpClient okHttpClient = builder.build();

//這裡不能直接使用那個傳入Builder物件的OkHttpClient的建構函式,因為該建構函式的方法不是public的
OkHttpClient okHttpClient = new OkHttpClient(builder);//這樣是錯誤的
builder.build();的原始碼是:
public OkHttpClient build() {
    return new OkHttpClient(this);
}
複製程式碼

我們再回過頭來看看OkHttpClient裡面設定的屬性值都有什麼用:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

  • Dispatch:分發器,後面會提到

  • Proxy:設定代理,通常為型別(http、socks)和套接字地址。參考文章:直接使用Proxy建立連線

  • ProxySelector: 設定全域性的代理,通過繼承該類,設定具體代理的型別、地址和埠。參考文章:Java代理 通過ProxySelector設定全域性代理

  • Protocol: 網路協議類,比如我們經常聽到的http1.0、http1.1、http2.0協議等。

    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

  • ConnectionSpec: 指定HTTP流量通過的套接字連線的配置。我們直接可以翻譯該類頭部的英文介紹,具體的內容原諒我也不是很懂:

    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

  • Interceptor:攔截器,後面會提到

  • EventListener:指標事件的監聽器。擴充套件此類以監視應用程式的HTTP呼叫的數量,大小和持續時間。所有start/connect/acquire事件最終都會收到匹配的end /release事件,要麼成功(非null引數)要麼失敗(非null throwable)。每個事件對的第一個公共引數用於在併發或重複事件的情況下連結事件,例如dnsStart(call,domainName);和dnsEnd(call,domainName,inetAddressList); 我們可以看到一系列的xxxStart和xxxEnd方法:

    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

  • CookieJar:向傳出的HTTP請求新增cookie,收到的HTTP返回資料的cookie處理。

    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
    參考文章:okhttp3帶cookie請求

  • Cache:網路快取,okhttp預設只能設定快取GET請求,不快取POST請求,畢竟POST請求很多都是互動的,快取下來也沒有什麼意義。

    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
    我們看到Cache的建構函式,可以看到的是需要設定快取資料夾,快取的大小,還有一個是快取內部的操作方式,因為快取是需要寫入檔案的,預設操作使用的是Okio來操作。
    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析
    參考文章:
    教你如何使用okhttp快取
    OKHTTP之快取配置詳解

  • InternalCache:Okhttp內部快取的介面,我們直接使用的時候不需要去實現這個介面,而是直接去使用上面的Cache類。

  • SocketFactory:從字面意思就看的出來,Android 自帶的Socket的工廠類。
    參考文章: 類SocketFactory

  • SSLSocketFactory:Android自帶的SSLSocket的工廠類。
    參考文章:Java SSLSocket的使用
    用SSLSocketFactory 連線https的地址

  • CertificateChainCleaner:不是很瞭解,所以還是老樣子,通過谷歌翻譯,翻譯該類的頂部備註說明:

    Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

  • HostnameVerifier:字面意思,Host name 驗證,這個一個基礎介面,而且只有一個方法:

/**
 * Verify that the host name is an acceptable match with
 * the server ‘s authentication scheme.
 *
 * @param hostname the host name
 * @param session SSLSession used on the connection to host
 * @return true if the host name is acceptable
 */
public boolean verify(String hostname, SSLSession session);
複製程式碼
  • Dns:DNS(Domain Name System,域名系統),dns用於將域名解析解析為ip地址。
    參考文章:Android DNS更新與DNS-Prefetch
  • 還有其他等等......

2.2 Request相關

我們檢視Request程式碼:

public final class Request {
  final HttpUrl url; //網路請求路徑
  final String method; //get、post.....
  final Headers headers;//請求頭
  final @Nullable RequestBody body;//請求體
  /**
  你可以通過tags來同時取消多個請求。
  當你構建一請求時,使用RequestBuilder.tag(tag)來分配一個標籤。
  之後你就可以用OkHttpClient.cancel(tag)來取消所有帶有這個tag的call。.
  */
  final Map<Class<?>, Object> tags;

  .......
  .......
  .......
  
}
複製程式碼

這個估計很多人都清楚,如果對請求頭請求體等不清楚的,可以看下以前我們這個系列的文章:Android技能樹 — 網路小結(3)之HTTP/HTTPS

2.3 Call相關

我們可以看到我們生成的Request例項,會傳給OkHttpClient例項的newÇall方法:

Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.execute();或者 call.enqueue(....);
複製程式碼

我們Request和OkHttpClient大致都瞭解過了,我們來具體看下newCall執行了什麼和Call的具體內容。

Call類程式碼:

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}

RealCall類程式碼:
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.eventListener = client.eventListenerFactory().create(call);
    return call;
}

複製程式碼

我們可以看到,最後獲取到的是RealCall的例項,同時把我們各種引數都配置好的OkHttpClient和Request都傳入了。

所以後面call.execute()/call.enqueue()都是執行的RealCall的相對應的方法。但目前位置我們上面的圖已經講解好了,我這裡再貼一次:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

恭喜你,下次別人考你Okhttp前面的相關引數配置方面的程式碼你已經都理解了。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

3.請求分發Dispatcher

我們繼續看我們的流程圖下面的內容:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

3.1 Dispatcher 同步操作

我們先來講同步執行:

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      //'1. 執行了dispatcher的executed方法'
      client.dispatcher().executed(this);
      //'2. 呼叫了getResponseWithInterceptorChain方法'
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //'3. 最後一定會執行dispatcher的finished方法'
      client.dispatcher().finished(this);
    }
}
複製程式碼

我們一步步來具體看,第一步看Dispatcher類中的executed方法了:

/** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}
複製程式碼

可以看到把我們的RealCall加入到了一個同步執行緒runningSyncCalls中,然後中間呼叫了getResponseWithInterceptorChain方法*(這個第二個操作我們會放在後面很具體的講解),我們既然加入到了一個同步執行緒中,肯定用完了要移除,然後第三步finished方法會做處理:

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

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
    
      //'if語句裡面我們可以看到這裡把我們的佇列中移除了call物件'
      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();
    }
}
複製程式碼

3.2 Dispatcher 非同步操作

我們先來看RealCall裡面的enqueue程式碼:

@Override public void enqueue(Callback responseCallback) {
    //'1. 這裡有個同步鎖的拋異常操作'
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //'2. 呼叫Dispatcher裡面的enqueue方法'
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製程式碼

我們一步步來看,第一個同步鎖拋異常的操作,我們知道一個Call應對一個網路請求,加入你這麼寫是錯誤的:

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

   @Override
   public void onResponse(Call call, Response response) throws IOException {}
});
//'同一個call物件再次發起請求'
call.enqueue(new Callback() {
   @Override
   public void onFailure(Call call, IOException e) {}

   @Override
   public void onResponse(Call call, Response response) throws IOException {}
});
複製程式碼

同一個Call物件,同時請求了二次。這時候就會進入我們的同步鎖判斷,只要一個執行過了,裡面 executed會為true,也就會丟擲異常。

我們再來看第二步操作:

我們知道非同步請求,肯定會代表很多請求都在各自的執行緒中去執行,那麼我們在不看OkHttp原始碼前,讓你去實現,你怎麼實現,是不是第一個反應是使用執行緒池。

Java/Android執行緒池框架的結構主要包括3個部分

1.任務:包括被執行任務需要實現的介面類:Runnable 或 Callable

2.任務的執行器:包括任務執行機制的核心介面類Executor,以及繼承自Executor的EexcutorService介面。

3.執行器的建立者,工廠類Executors
複製程式碼

具體可以參考:Android 執行緒池框架、Executor、ThreadPoolExecutor詳解

client.dispatcher().enqueue(new AsyncCall(responseCallback));,不再是像同步操作一樣,直接把RealCall傳入,而是傳入一個AsyncCall物件。沒錯,按照我們上面提到的執行緒池架構,任務是使用Runnable 或 Callable介面,我們檢視AsyncCall的程式碼:

final class AsyncCall extends NamedRunnable {
    ......
    ......
}

public abstract class NamedRunnable implements Runnable {
    .......
    .......
}

複製程式碼

果然如我們預計,是使用了Runnable介面。

client.dispatcher().enqueue(new AsyncCall(responseCallback));,不再是像同步操作一樣,直接把RealCall傳入,而是傳入一個AsyncCall物件。

呼叫Dispatcher裡面的enqueue方法:

synchronized void enqueue(AsyncCall call) {
    //'1. 判斷當前非同步佇列裡面的數量是否小於最大值,當前請求數是否小於最大值'
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //'2. 如果沒有大於最大值,則將call加入到非同步請求佇列中'
      runningAsyncCalls.add(call);
      //'3. 並且執行call的任務'
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
}
複製程式碼

我麼直接看第三步,按照我們上面提到過的Java/Android執行緒池框架的結構主要包括3個部分,可以看到執行我們的Runnable物件的,說明他是一個任務執行器,也就是Executor的繼承類。說明executorService()返回了一個Executor的實現類,我們點進去檢視:

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

複製程式碼

果然建立一個可快取執行緒池,執行緒池的最大長度無限制,但如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。

那我們知道是執行緒池執行了Runnable的任務,那我們只要具體看我們的okhttp的Runnable到底執行了什麼即可:

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    
    ......
    ......
    ......
    
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
      
        //'1. 我們可以發現最後執行緒池執行的任務就是getResponseWithInterceptorChain方法'
        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 {
        //'2. 最後再從Dispatcher裡面的非同步佇列中移除'
        client.dispatcher().finished(this);
      }
    }
  }
複製程式碼

我們發現不管是非同步還是同步,都是一樣的三部曲:1.加入到Dispatcher裡面的同步(或非同步)佇列,2.執行getResponseWithInterceptorChain方法,3.從Dispatcher裡面的同步(或非同步)佇列移除。(只不過同步操作是直接執行了getResponseWithInterceptorChain方法,而非同步是通過執行緒池執行Runnable再去執行getResponseWithInterceptorChain方法)

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

4 Okhttp攔截

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

我們在前面已經知道了不管是非同步請求還是同步請求,都會去執行 RealCallgetResponseWithInterceptorChain操作:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //'1. 建立一個攔截器List'
    List<Interceptor> interceptors = new ArrayList<>();
    //'2. 新增使用者自己建立的應用攔截器'
    interceptors.addAll(client.interceptors());
    //'3. 新增重試與重定向攔截器'
    interceptors.add(retryAndFollowUpInterceptor);
    //'4. 新增內容攔截器'
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //'4. 新增快取攔截器'
    interceptors.add(new CacheInterceptor(client.internalCache()));
    /'5. 新增連線攔截器'
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //'6. 新增使用者自己建立的網路攔截器'
      interceptors.addAll(client.networkInterceptors());
    }
    //'7. 新增請求服務攔截器'
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //'8.把這些攔截器們一起封裝在一個攔截器鏈條上面(RealInterceptorChain)'
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    
    //'9.然後執行鏈條的proceed方法'
    return chain.proceed(originalRequest);
  }
複製程式碼

我們先不管具體的攔截器的功能,我們先來看整體的執行方式,所以我們直接來看攔截器鏈條的工作模式:

public final class RealInterceptorChain implements Interceptor.Chain {
   
   //'我們剛才建立的放攔截器的佇列'
   private final List<Interceptor> interceptors;
   //'當前執行的第幾個攔截器序號'
   private final int index;
   ......
   ......
   ......
  

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

   //'例項化了一個新的RealInterceptorChain物件,並且傳入相同的攔截器List,只不過傳入的index值+1'
   RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
       connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
       writeTimeout);
   //'獲取當前index對應的攔截器裡面的具體的某個攔截器,
   Interceptor interceptor = interceptors.get(index);
   //然後執行攔截器的intercept方法,同時傳入新的RealInterceptorChain物件(主要的區別在於index+1了)'
   Response response = interceptor.intercept(next);
   
   ......
   ......
   ......

   return response;
 }
}
複製程式碼

我們可以看到在RealInterceptorChain類的proceed的方法裡面,又去例項化了一個RealInterceptorChain類。很多人可能看著比較繞,沒關係,我們舉個例子簡單說下就可以了:

我的寫法還是按照它的寫法,寫了二個Interceptor,一個用來填充地址AddAddressInterceptor,一箇中來填充電話AddTelephoneInterceptor,然後也建立一個攔截鏈條InterceptorChain。這樣我只需要傳進去一個字串,然後會自動按照每個攔截器的功能,自動幫我填充了地址和電話號碼。

Interceptor只負責處理自己的業務功能,比如我們這裡是填充地址和手機號碼,然後自己的任務結束就會呼叫攔截器鏈條,執行鏈條接下去的任務,其他跟Interceptor無關。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

我們來看我們的攔截器和攔截器鏈條:

電話攔截器:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

地址攔截器:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

攔截器鏈:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

Activity.java:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

最後我們可以看到我們的result結果為:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

這裡額外提下: 裡面的攔截器裡面的二個大步驟是可以交換順序的,我先執行攔截鏈的方法,讓它提前去執行下一個攔截器的操作,再拿相應的返回值做我這個攔截器的操作。比如還是剛才那個電話攔截器,我們調換了二個的順序:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

這樣就會去先執行地址攔截器,然後拿到結果後再去處理電話攔截器的邏輯,所以最後的輸出結果為:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

這裡我們懂了以後,我們再去看Okhttp前面提到的攔截器新增,攔截鏈的相關程式碼,是不是簡單的一比,它的連結鏈的操作跟我們的基本架構一致,然後各自的攔截器無非就是處理各自的邏輯,對引數進行更改,發起請求等。所以我們的核心變成了OkHttp的各個攔截器到底做了什麼邏輯。(也就是我們提到的攔截器中的二個大操作的其中一步,自己的處理邏輯。)


本來想一步步的來寫每個單獨的攔截器的作用,後來想了下,單獨攔截器的程式碼分析的文章真的太多太多了。而且每個攔截器寫的很簡單,其實沒啥大的意義,寫的仔細,一個攔截器就可以是一篇文章,而我們本文也側重於總體的原始碼架構,所以我後面如果可以的,都直接引用別人的文章了。


4.1 RetryAndFollowUpInterceptor

看名字就知道這個攔截器的作用是重試和重定向的。

大家可以參考本文:

OKhttp原始碼解析---攔截器之RetryAndFollowUpInterceptor

4.2 BridgeInterceptor

我們來看BridgeInterceptor類的說明備註:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

什麼?看不懂英文,谷歌翻譯走起:

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

簡單來說,我們自己在Okhttp裡面建立了一個Request請求物件,但是這個物件並不是直接就可以用來馬上傳送網路請求的,畢竟我們剛開始例項化Request的時候就簡單的放入了Url,body等,很多引數都是沒有設定的,所以我們還需要補充很多引數,然後發起網路請求,然後網路返回的引數,我們再把它封裝成Okhttp可以直接使用的物件。

一句話概括: 將客戶端構建的Request物件資訊構建成真正的網路請求;然後發起網路請求,最後就是將伺服器返回的訊息封裝成一個Response物件

參考文章:

OkHttp之BridgeInterceptor簡單分析

4.3 CacheInterceptor

快取攔截器,簡單來說就是有快取就使用快取。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

參考文章:

Okhttp之CacheInterceptor簡單分析

4.4 ConnectInterceptor

連線攔截器,顧名思義開啟了與伺服器的連結,正式開啟了網路請求。

因為以前在文章: Android技能樹 — 網路小結(4)之socket/websocket/webservice 提到過,我們的請求是通過Socket去訪問的。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

所以最終這個ConnectInterceptor也會去發起一個Socket連線請求。

參考文章:

OkHttp之ConnectInterceptor簡單分析

4.5 CallServerInterceptor

我們曾經在文章 Android技能樹 — 網路小結(2)之TCP/UDP 提過:

TCP要先建立通道,然後再傳送資料。

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

上面的攔截器ConnectInterceptor已經幫我們把通道建立好了,所以在這個CallServerInterceptor攔截器裡面,我們的任務就是傳送相關的資料,

參考文章:

Okhttp之CallServerInterceptor簡單分析

4.6 自定義攔截器

我們在流程圖中看到了,除了OKHttp原始碼裡面自帶的攔截器,還有二種自定義攔截器,應用攔截器和網路攔截器。

使用程式碼:

okHttpClient = new OkHttpClient
                .Builder()
                .addInterceptor(appInterceptor)//Application攔截器
                .addNetworkInterceptor(networkInterceptor)//Network攔截器
                .build();
複製程式碼

我們知道網路請求中間一定要經過一系列的攔截器,我們也可以自己寫攔截器,然後對裡面的引數做處理,比如我們對Request在攔截器中做某個寫引數變更,然後再交給下一個攔截器。

而這二個自定義攔截器的位置,在我們前面分析獲取攔截鏈的方法getResponseWithInterceptorChain中就提過了,現在再拿出來重新說一遍:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    '最先新增使用者的自定義APPlication攔截器'
    interceptors.addAll(client.interceptors());
    
    '然後是一系列的Okhttp自帶的攔截器'
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    
    '在最終跟伺服器互動資料的CallServerInterceptor前,新增使用者自定義的NetWork攔截器'
    '因為如果放在最後就沒什麼意義了。'
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
複製程式碼

參考文章:

OkHttp基本使用(五)自定義攔截器

Android技能樹 — 網路小結(6)之 OkHttp超超超超超超超詳細解析

結語:

OkHttp原始碼寫的也比較倉促,特別後面的各個攔截器的原始碼分析就偷懶了,因為不然會引出一大段一大段的內容,就直接引用其他大佬的文章。如果哪裡不對,歡迎大家指出。

相關文章