Android技能樹 — 網(wǎng)絡(luò)小結(jié)(6)之 OkHttp超超超超超超超詳細(xì)解析

前言:

本文也做了一次標(biāo)題黨歌憨,哈哈着憨,其實寫的還是很水,各位原諒我O(∩_∩)O务嫡。

介于自己的網(wǎng)絡(luò)方面知識爛的一塌糊涂甲抖,所以準(zhǔn)備寫相關(guān)網(wǎng)絡(luò)的文章漆改,但是考慮全部寫在一篇太長了,所以分開寫准谚,希望大家能仔細(xì)看挫剑,最好可以指出我的錯誤,讓我也能糾正柱衔。

1.講解相關(guān)的整個網(wǎng)絡(luò)體系結(jié)構(gòu):

Android技能樹 — 網(wǎng)絡(luò)小結(jié)(1)之網(wǎng)絡(luò)體系結(jié)構(gòu)

2.講解相關(guān)網(wǎng)絡(luò)的重要知識點樊破,比如很多人都聽過相關(guān)網(wǎng)絡(luò)方面的名詞,但是僅限于聽過而已唆铐,什么tcp ,udp ,socket ,websocket, http ,https ,然后webservice是啥哲戚,跟websocket很像,socket和websocket啥關(guān)系長的也很像艾岂,session,token,cookie又是啥顺少。

Android技能樹 — 網(wǎng)絡(luò)小結(jié)(2)之TCP/UDP

Android技能樹 — 網(wǎng)絡(luò)小結(jié)(3)之HTTP/HTTPS

Android技能樹 — 網(wǎng)絡(luò)小結(jié)(4)之socket/websocket/webservice

相關(guān)網(wǎng)絡(luò)知識點小結(jié)- cookie/session/token(待寫)

3.相關(guān)的第三方框架的源碼解析,畢竟現(xiàn)在面試個大點的公司澳盐,okhttp和retrofit源碼是必問的祈纯。

Android技能樹 — 網(wǎng)絡(luò)小結(jié)(6)之 OkHttp超超超超超超超詳細(xì)解析

Android技能樹 — 網(wǎng)絡(luò)小結(jié)(7)之 Retrofit源碼詳細(xì)解析


這里提一個本文無關(guān)的小知識點,很多文章開頭都會提到叼耙,我們以okhttp3.xxx版本來講解,那怎么看當(dāng)前最新的已經(jīng)是幾了呢粒没?(主要以前也有人問過我在哪里查看xxx第三方庫最新的版本筛婉,所以想到提一下這個)其實很簡單,我們以okhttp為例:

  1. Android Studio直接查看:


  2. JCenter上查看:
    JCenter上搜索Okhttp版本
  3. Maven上查看:
    Maven上搜索Okhttp版本
  4. ........其他方式

正文

看不清楚的癞松,可以右鍵爽撒,選擇新標(biāo)簽頁中打開,然后點擊圖片放大

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

  1. okhttp相關(guān)參數(shù)配置响蓉,比如設(shè)置超時時間硕勿,網(wǎng)絡(luò)路徑等等等等等.......
  2. 我們知道在使用okhttp的時候可以使用同步請求,也可以使用異步請求枫甲,所以肯定不同的請求源武,在分發(fā)的時候有不同的處理。
  3. 我們以前網(wǎng)絡(luò)系列的文章提過想幻,發(fā)送到后臺粱栖,肯定是一個完整的請求包,但是我們使用okhttp的時候脏毯,只是轉(zhuǎn)入了我們需要給后臺的參數(shù)闹究,甚至我們?nèi)绻莋et請求,只是傳入了相應(yīng)的url網(wǎng)絡(luò)地址就能拿到數(shù)據(jù)食店,說明okhttp幫我們把簡單的參數(shù)輸入渣淤,然后通過一系列的添加封裝赏寇,然后變成一個完整的網(wǎng)絡(luò)請求包出去,然后我們在使用okhttp的時候价认,拿到返回的數(shù)據(jù)也已經(jīng)是我們可以直接用的對象蹋订,說明接受的時候,已經(jīng)幫我們把拿到的返回網(wǎng)絡(luò)包刻伊,解析成我們直接用的對象了露戒。<font color = "red">所以在一系列幫我們發(fā)送的時候添加參數(shù)變成完整網(wǎng)絡(luò)請求包,收到時候幫我們解析返回請求包的過程捶箱,是Okhttp的一個個攔截器們所處理智什,它攔截到我們的數(shù)據(jù),然后進(jìn)行處理丁屎,比如添加一些數(shù)據(jù)荠锭,變成完整的網(wǎng)絡(luò)請求包等操作</font>。

所以我們大概就知道了okhttp一般的主要內(nèi)容為這三大塊晨川。

1.okhttp基礎(chǔ)使用:

講解源碼前证九,先寫上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. 如果要執(zhí)行同步請求:'
try {
    call.execute();
} catch (IOException e) {
    e.printStackTrace();
}
//'5. 如果要執(zhí)行異步請求:'
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

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

2. 初始化相關(guān)參數(shù)解析:

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

然后配合上面第一步的okhttp基本使用共虑,發(fā)現(xiàn)在執(zhí)行同步和異步前愧怜,我們要先準(zhǔn)備好OkhttpClientRequest妈拌、Call對象拥坛。我們一步步來看相關(guān)源碼:

2.1 OkHttpClient相關(guān):

我們上面的代碼實例化OkHttpClient對象的代碼是:

OkHttpClient okHttpClient = new OkHttpClient();

我們進(jìn)入查看:


發(fā)現(xiàn)OkHttpClient除了空參數(shù)的構(gòu)造函數(shù),還有一個傳入Builder的構(gòu)造函數(shù)尘分,而我們的new OkHttpClient()最終也是調(diào)用了傳入Builder的構(gòu)造函數(shù)猜惋,只不過傳入默認(rèn)的Builder對象值,如下圖所示:

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

......
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
......

默認(rèn)的連接超時培愁,讀取超時著摔,寫入超時,都為10秒定续,然后還有其他等默認(rèn)屬性谍咆,那我們加入想要改變這些屬性值呢,比如超時時間改為20秒香罐,很簡單卧波。我們不使用默認(rèn)的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的構(gòu)造函數(shù),因為該構(gòu)造函數(shù)的方法不是public的
OkHttpClient okHttpClient = new OkHttpClient(builder);//這樣是錯誤的
builder.build();的源碼是:
public OkHttpClient build() {
    return new OkHttpClient(this);
}

我們再回過頭來看看OkHttpClient里面設(shè)置的屬性值都有什么用:

  • Dispatch:分發(fā)器庇茫,后面會提到

  • Proxy:設(shè)置代理港粱,通常為類型(http、socks)和套接字地址。參考文章:直接使用Proxy創(chuàng)建連接

  • ProxySelector: 設(shè)置全局的代理查坪,通過繼承該類寸宏,設(shè)置具體代理的類型、地址和端口偿曙。參考文章:Java代理 通過ProxySelector設(shè)置全局代理

  • Protocol: 網(wǎng)絡(luò)協(xié)議類氮凝,比如我們經(jīng)常聽到的http1.0、http1.1望忆、http2.0協(xié)議等罩阵。

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

  • Interceptor:攔截器稿壁,后面會提到

  • EventListener:指標(biāo)事件的監(jiān)聽器。擴(kuò)展此類以監(jiān)視應(yīng)用程序的HTTP調(diào)用的數(shù)量歉备,大小和持續(xù)時間傅是。所有start/connect/acquire事件最終都會收到匹配的end /release事件,要么成功(非null參數(shù))要么失斃傺颉(非null throwable)喧笔。每個事件對的第一個公共參數(shù)用于在并發(fā)或重復(fù)事件的情況下鏈接事件,例如dnsStart(call龟再,domainName);和dnsEnd(call书闸,domainName,inetAddressList); 我們可以看到一系列的xxxStart和xxxEnd方法:

  • CookieJar:向傳出的HTTP請求添加cookie,收到的HTTP返回數(shù)據(jù)的cookie處理吸申。

    參考文章:okhttp3帶cookie請求

  • Cache:網(wǎng)絡(luò)緩存梗劫,okhttp默認(rèn)只能設(shè)置緩存GET請求,不緩存POST請求截碴,畢竟POST請求很多都是交互的,緩存下來也沒有什么意義蛉威。


    我們看到Cache的構(gòu)造函數(shù)日丹,可以看到的是需要設(shè)置緩存文件夾,緩存的大小蚯嫌,還有一個是緩存內(nèi)部的操作方式哲虾,因為緩存是需要寫入文件的蜂嗽,默認(rèn)操作使用的是Okio來操作匿辩。

    參考文章:</br>教你如何使用okhttp緩存</br>OKHTTP之緩存配置詳解

  • InternalCache:Okhttp內(nèi)部緩存的接口,我們直接使用的時候不需要去實現(xiàn)這個接口富稻,而是直接去使用上面的Cache類栅盲。

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

  • SSLSocketFactory:Android自帶的SSLSocket的工廠類。
    參考文章:Java SSLSocket的使用 </br> 用SSLSocketFactory 連接https的地址

  • CertificateChainCleaner:不是很了解扒寄,所以還是老樣子鱼鼓,通過谷歌翻譯,翻譯該類的頂部備注說明:

  • HostnameVerifier:字面意思该编,Host name 驗證迄本,這個一個基礎(chǔ)接口,而且只有一個方法:

/**
 * 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课竣,域名系統(tǒng))嘉赎,dns用于將域名解析解析為ip地址。
    參考文章:Android DNS更新與DNS-Prefetch
  • 還有其他等等......

2.2 Request相關(guān)

我們查看Request代碼:

public final class Request {
  final HttpUrl url; //網(wǎng)絡(luò)請求路徑
  final String method; //get于樟、post.....
  final Headers headers;//請求頭
  final @Nullable RequestBody body;//請求體
  /**
  你可以通過tags來同時取消多個請求公条。
  當(dāng)你構(gòu)建一請求時,使用RequestBuilder.tag(tag)來分配一個標(biāo)簽隔披。
  之后你就可以用OkHttpClient.cancel(tag)來取消所有帶有這個tag的call赃份。.
  */
  final Map<Class<?>, Object> tags;

  .......
  .......
  .......
  
}

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

2.3 Call相關(guān)

我們可以看到我們生成的Request實例抓韩,會傳給OkHttpClient實例的new?all方法:

Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.execute();或者 call.enqueue(....);

我們Request和OkHttpClient大致都了解過了,我們來具體看下newCall執(zhí)行了什么和Call的具體內(nèi)容鬓长。

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的實例,同時把我們各種參數(shù)都配置好的OkHttpClient和Request都傳入了涉波。

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

恭喜你啤覆,下次別人考你Okhttp前面的相關(guān)參數(shù)配置方面的代碼你已經(jīng)都理解了苍日。

3.請求分發(fā)Dispatcher

我們繼續(xù)看我們的流程圖下面的內(nèi)容:

3.1 Dispatcher 同步操作

我們先來講同步執(zhí)行:

@Override 
public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      //'1. 執(zhí)行了dispatcher的executed方法'
      client.dispatcher().executed(this);
      //'2. 調(diào)用了getResponseWithInterceptorChain方法'
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //'3. 最后一定會執(zhí)行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中窗声,然后中間調(diào)用了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. 調(diào)用Dispatcher里面的enqueue方法'
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

我們一步步來看拦耐,第一個同步鎖拋異常的操作,我們知道一個Call應(yīng)對一個網(wǎng)絡(luò)請求见剩,加入你這么寫是錯誤的:

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對象再次發(fā)起請求'
call.enqueue(new Callback() {
   @Override
   public void onFailure(Call call, IOException e) {}

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

同一個Call對象杀糯,同時請求了二次。這時候就會進(jìn)入我們的同步鎖判斷苍苞,只要一個執(zhí)行過了固翰,里面 executed會為true,也就會拋出異常。

我們再來看第二步操作:

我們知道異步請求倦挂,肯定會代表很多請求都在各自的線程中去執(zhí)行畸颅,那么我們在不看OkHttp源碼前,讓你去實現(xiàn)方援,你怎么實現(xiàn)没炒,是不是第一個反應(yīng)是使用線程池。

Java/Android線程池框架的結(jié)構(gòu)主要包括3個部分

1.任務(wù):包括被執(zhí)行任務(wù)需要實現(xiàn)的接口類:Runnable 或 Callable

2.任務(wù)的執(zhí)行器:包括任務(wù)執(zhí)行機(jī)制的核心接口類Executor犯戏,以及繼承自Executor的EexcutorService接口送火。

3.執(zhí)行器的創(chuàng)建者,工廠類Executors

具體可以參考:Android 線程池框架先匪、Executor种吸、ThreadPoolExecutor詳解

client.dispatcher().enqueue(new AsyncCall(responseCallback));,不再是像同步操作一樣,直接把RealCall傳入呀非,而是傳入一個AsyncCall對象坚俗。沒錯,按照我們上面提到的線程池架構(gòu)岸裙,任務(wù)是使用Runnable 或 Callable接口猖败,我們查看AsyncCall的代碼:

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

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

果然如我們預(yù)計,是使用了Runnable接口降允。

client.dispatcher().enqueue(new AsyncCall(responseCallback));,不再是像同步操作一樣恩闻,直接把RealCall傳入,而是傳入一個AsyncCall對象剧董。

調(diào)用Dispatcher里面的enqueue方法:

synchronized void enqueue(AsyncCall call) {
    //'1. 判斷當(dāng)前異步隊列里面的數(shù)量是否小于最大值幢尚,當(dāng)前請求數(shù)是否小于最大值'
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //'2. 如果沒有大于最大值,則將call加入到異步請求隊列中'
      runningAsyncCalls.add(call);
      //'3. 并且運行call的任務(wù)'
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
}

我么直接看第三步翅楼,按照我們上面提到過的Java/Android線程池框架的結(jié)構(gòu)主要包括3個部分尉剩,可以看到執(zhí)行我們的Runnable對象的,說明他是一個任務(wù)執(zhí)行器毅臊,也就是Executor的繼承類边涕。說明executorService()返回了一個Executor的實現(xiàn)類,我們點進(jìn)去查看:

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

果然創(chuàng)建一個可緩存線程池褂微,線程池的最大長度無限制,但如果線程池長度超過處理需要园爷,可靈活回收空閑線程宠蚂,若無可回收,則新建線程童社。

那我們知道是線程池執(zhí)行了Runnable的任務(wù)求厕,那我們只要具體看我們的okhttp的Runnable到底執(zhí)行了什么即可:

  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. 我們可以發(fā)現(xiàn)最后線程池執(zhí)行的任務(wù)就是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);
      }
    }
  }

我們發(fā)現(xiàn)不管是異步還是同步,都是一樣的三部曲:1.加入到Dispatcher里面的同步(或異步)隊列,2.執(zhí)行g(shù)etResponseWithInterceptorChain方法呀癣,3.從Dispatcher里面的同步(或異步)隊列移除美浦。(只不過同步操作是直接運行了getResponseWithInterceptorChain方法,而異步是通過線程池執(zhí)行Runnable再去執(zhí)行g(shù)etResponseWithInterceptorChain方法)

4 Okhttp攔截

我們在前面已經(jīng)知道了不管是異步請求還是同步請求项栏,都會去執(zhí)行
RealCallgetResponseWithInterceptorChain操作:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //'1. 創(chuàng)建一個攔截器List'
    List<Interceptor> interceptors = new ArrayList<>();
    //'2. 添加用戶自己創(chuàng)建的應(yīng)用攔截器'
    interceptors.addAll(client.interceptors());
    //'3. 添加重試與重定向攔截器'
    interceptors.add(retryAndFollowUpInterceptor);
    //'4. 添加內(nèi)容攔截器'
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //'4. 添加緩存攔截器'
    interceptors.add(new CacheInterceptor(client.internalCache()));
    /'5. 添加連接攔截器'
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //'6. 添加用戶自己創(chuàng)建的網(wǎng)絡(luò)攔截器'
      interceptors.addAll(client.networkInterceptors());
    }
    //'7. 添加請求服務(wù)攔截器'
    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.然后執(zhí)行鏈條的proceed方法'
    return chain.proceed(originalRequest);
  }

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

public final class RealInterceptorChain implements Interceptor.Chain {
   
   //'我們剛才建立的放攔截器的隊列'
   private final List<Interceptor> interceptors;
   //'當(dāng)前執(zhí)行的第幾個攔截器序號'
   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);
   //'獲取當(dāng)前index對應(yīng)的攔截器里面的具體的某個攔截器,
   Interceptor interceptor = interceptors.get(index);
   //然后執(zhí)行攔截器的intercept方法,同時傳入新的RealInterceptorChain對象(主要的區(qū)別在于index+1了)'
   Response response = interceptor.intercept(next);
   
   ......
   ......
   ......

   return response;
 }
}

我們可以看到在RealInterceptorChain類的proceed的方法里面列另,又去實例化了一個RealInterceptorChain類芽腾。很多人可能看著比較繞,沒關(guān)系页衙,我們舉個例子簡單說下就可以了:

我的寫法還是按照它的寫法,寫了二個Interceptor,一個用來填充地址AddAddressInterceptor摊滔,一個中來填充電話AddTelephoneInterceptor,然后也建立一個攔截鏈條InterceptorChain店乐。這樣我只需要傳進(jìn)去一個字符串艰躺,然后會自動按照每個攔截器的功能,自動幫我填充了地址和電話號碼响巢。

Interceptor只負(fù)責(zé)處理自己的業(yè)務(wù)功能描滔,比如我們這里是填充地址和手機(jī)號碼,然后自己的任務(wù)結(jié)束就會調(diào)用攔截器鏈條踪古,執(zhí)行鏈條接下去的任務(wù)含长,其他跟Interceptor無關(guān)。

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

電話攔截器:

地址攔截器:

攔截器鏈:

Activity.java:

最后我們可以看到我們的result結(jié)果為:

這里額外提下: 里面的攔截器里面的二個大步驟是可以交換順序的伏穆,我先執(zhí)行攔截鏈的方法拘泞,讓它提前去執(zhí)行下一個攔截器的操作,再拿相應(yīng)的返回值做我這個攔截器的操作枕扫。比如還是剛才那個電話攔截器陪腌,我們調(diào)換了二個的順序:

這樣就會去先執(zhí)行地址攔截器,然后拿到結(jié)果后再去處理電話攔截器的邏輯烟瞧,所以最后的輸出結(jié)果為:

這里我們懂了以后诗鸭,我們再去看Okhttp前面提到的攔截器添加,攔截鏈的相關(guān)代碼参滴,是不是簡單的一比强岸,它的鏈接鏈的操作跟我們的基本架構(gòu)一致,然后各自的攔截器無非就是處理各自的邏輯砾赔,對參數(shù)進(jìn)行更改蝌箍,發(fā)起請求等青灼。所以我們的核心變成了OkHttp的各個攔截器到底做了什么邏輯。(也就是我們提到的攔截器中的二個大操作的其中一步妓盲,自己的處理邏輯杂拨。)


<font color = "red">本來想一步步的來寫每個單獨的攔截器的作用,后來想了下悯衬,單獨攔截器的代碼分析的文章真的太多太多了弹沽。而且每個攔截器寫的很簡單,其實沒啥大的意義甚亭,寫的仔細(xì)贷币,一個攔截器就可以是一篇文章,而我們本文也側(cè)重于總體的源碼架構(gòu)亏狰,所以我后面如果可以的役纹,都直接引用別人的文章了。</font>


4.1 RetryAndFollowUpInterceptor

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

大家可以參考本文:

OKhttp源碼解析---攔截器之RetryAndFollowUpInterceptor

4.2 BridgeInterceptor

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

什么促脉?看不懂英文,谷歌翻譯走起:

簡單來說策州,我們自己在Okhttp里面建立了一個Request請求對象瘸味,但是這個對象并不是直接就可以用來馬上發(fā)送網(wǎng)絡(luò)請求的,畢竟我們剛開始實例化Request的時候就簡單的放入了Url够挂,body等旁仿,很多參數(shù)都是沒有設(shè)置的,所以我們還需要補(bǔ)充很多參數(shù)孽糖,然后發(fā)起網(wǎng)絡(luò)請求枯冈,然后網(wǎng)絡(luò)返回的參數(shù),我們再把它封裝成Okhttp可以直接使用的對象办悟。

一句話概括: 將客戶端構(gòu)建的Request對象信息構(gòu)建成真正的網(wǎng)絡(luò)請求;然后發(fā)起網(wǎng)絡(luò)請求尘奏,最后就是將服務(wù)器返回的消息封裝成一個Response對象

參考文章:

OkHttp之BridgeInterceptor簡單分析

4.3 CacheInterceptor

緩存攔截器,簡單來說就是有緩存就使用緩存病蛉。

參考文章:

Okhttp之CacheInterceptor簡單分析

4.4 ConnectInterceptor

連接攔截器炫加,顧名思義打開了與服務(wù)器的鏈接,正式開啟了網(wǎng)絡(luò)請求铺然。

因為以前在文章:
Android技能樹 — 網(wǎng)絡(luò)小結(jié)(4)之socket/websocket/webservice
提到過俗孝,我們的請求是通過Socket去訪問的。

所以最終這個ConnectInterceptor也會去發(fā)起一個Socket連接請求魄健。

參考文章:

OkHttp之ConnectInterceptor簡單分析

4.5 CallServerInterceptor

我們曾經(jīng)在文章 Android技能樹 — 網(wǎng)絡(luò)小結(jié)(2)之TCP/UDP 提過:

TCP要先建立通道驹针,然后再發(fā)送數(shù)據(jù)柬甥。


上面的攔截器ConnectInterceptor已經(jīng)幫我們把通道建立好了其垄,所以在這個CallServerInterceptor攔截器里面,我們的任務(wù)就是發(fā)送相關(guān)的數(shù)據(jù)臂外,

參考文章:

Okhttp之CallServerInterceptor簡單分析

4.6 自定義攔截器

我們在流程圖中看到了喇颁,除了OKHttp源碼里面自帶的攔截器,還有二種自定義攔截器橘霎,應(yīng)用攔截器和網(wǎng)絡(luò)攔截器。

使用代碼:

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

我們知道網(wǎng)絡(luò)請求中間一定要經(jīng)過一系列的攔截器姐叁,我們也可以自己寫攔截器瓦盛,然后對里面的參數(shù)做處理,比如我們對Request在攔截器中做某個寫參數(shù)變更外潜,然后再交給下一個攔截器原环。

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

  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));
    
    '在最終跟服務(wù)器交互數(shù)據(jù)的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基本使用(五)自定義攔截器

結(jié)語:

OkHttp源碼寫的也比較倉促滔驾,特別后面的各個攔截器的源碼分析就偷懶了谒麦,因為不然會引出一大段一大段的內(nèi)容,就直接引用其他大佬的文章嵌灰。如果哪里不對弄匕,歡迎大家指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沽瞭,一起剝皮案震驚了整個濱河市迁匠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驹溃,老刑警劉巖城丧,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豌鹤,居然都是意外死亡亡哄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門布疙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚊惯,“玉大人趴荸,你說我怎么就攤上這事发钝≡秃溃” “怎么了孵淘?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長痛悯。 經(jīng)常有香客問我载萌,道長扭仁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任熊泵,我火速辦了婚禮顽分,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缸沃。我一直安慰自己,他們只是感情好村缸,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布仇箱。 她就那樣靜靜地躺著忠烛,像睡著了一般美尸。 火紅的嫁衣襯著肌膚如雪师坎。 梳的紋絲不亂的頭發(fā)上胯陋,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音盟萨,去河邊找鬼捻激。 笑死,一個胖子當(dāng)著我的面吹牛残炮,可吹牛的內(nèi)容都是我干的泉瞻。 我是一名探鬼主播袖牙,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼司忱,長吁一口氣:“原來是場噩夢啊……” “哼坦仍!你這毒婦竟也來了繁扎?” 一聲冷哼從身側(cè)響起梳玫,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虱朵,沒想到半個月后碴犬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體服协,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年跳纳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斗塘。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡于置,死狀恐怖八毯,靈堂內(nèi)的尸體忽然破棺而出宪彩,到底是詐尸還是另有隱情,我是刑警寧澤筹麸,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站酵紫,受9級特大地震影響奖地,放射性物質(zhì)發(fā)生泄漏参歹。R本人自食惡果不足惜犬庇,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望欢峰。 院中可真熱鬧闯狱,春花似錦抛计、人聲如沸瘦陈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趁窃。三九已至醒陆,卻和暖如春刨摩,著一層夾襖步出監(jiān)牢的瞬間澡刹,已是汗流浹背像屋。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工己莺, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胜蛉。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓誊册,卻偏偏與公主長得像案怯,于是被迫代替她去往敵國和親金砍。 傳聞我的和親對象是個殘疾皇子麦锯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料昆著? 從這篇文章中你...
    hw1212閱讀 12,693評論 2 59
  • "坑"驰徊,是一個成長的機(jī)會棍厂! 前一段時間同事離職牺弹,原因是公司不能給予他快樂晶默,不能給予他想要的發(fā)展機(jī)會磺陡。和我多年前的思...
    宛若清風(fēng)R閱讀 523評論 0 0
  • 第三課,愛自己的智慧盆驹,課后感受:世界上沒有任何一個人有義務(wù)為你的幸福買單躯喇,我們要成長自己倦微,不斷向內(nèi)觀欣福,為自己的情緒...
    王瑤燕行閱讀 416評論 0 0
  • 有沒有人…… 和你說過 平庸不是一件好事 你緩慢慢的淌過 雨天水滴的滑落 風(fēng)中草裙的搖潑 土里花生的結(jié)果 有沒有人...
    夢想家的二余閱讀 201評論 0 0