Android 網絡框架之OkHttp源碼解析

前言:OkHttp框架是Android的網絡請求框架,無數的項目都在使用着這個框架,重要性不言而喻;html

  • 本文會將OKHTTP的源碼進行拆解,每一個部分來單獨學習,由簡入深,按部就班,篇幅較長,建議收藏,慢慢觀看,若是以爲內容不錯的話,點贊關注來一波,感謝!

源碼基於okhttp3 java版本:3.14.9java

  • OkHttp3的簡單使用:
public void request() {
        String url = "http://wwww.baidu.com";
        OkHttpClient okHttpClient = new OkHttpClient();
        final Request request = new Request.Builder()
                .url(url)
                .get() //默認就是GET請求,能夠不寫
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: ");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "onResponse: " + response.body().string());
            }
        });
    }
複製代碼

咱們將使用到的這些類拆分出來,逐個講解;android

概要:

一、OkHttpClient

OkHttpClient是什麼?git

顧名思義,咱們能夠理解爲是OkHttp的客戶端;github

咱們來看一下這個類裏面有啥東西?編程

首先,從源碼裏面能夠看到聲明瞭一大堆成員變量,咋一看,還有點看不懂,涉及的東西太多了,可是不要緊,咱們的目的是爲了瞭解這個類的職責;設計模式

下面咱們先來看兩張圖:api

圖一:緩存

圖二:服務器

怎樣,看了上面兩張圖,是否是有種熟悉的感受,是否是很像咱們寫Gson解析時的bean類;

沒錯,這個OkHttpClient裏面就是這樣的代碼,相似於bean類,主要作一些參數的賦值與獲取;

那麼到這裏咱們就明朗了,OkHttpClient裏面並無很複雜的邏輯,整個類的邏輯反而很清晰,就是實現參數的管理;

至於裏面的一些參數是幹嗎的,這裏咱們先無論,只要明白整個類的職責是啥就好了;

2,Request

Request,這個單詞的意思是請求,那麼這個類裏面是作的這個操做嗎?

咱們來看一下源碼,一窺究竟;

先來看一下這個成員變量,不多,就幾個,可是從命名能夠看出,這裏面定義了請求的URL,請求的method,還有請求頭headers,請求體body等參數;

再往下看;

從這裏能夠看出,源碼裏面也是定義了一些參數的賦值與獲取,那麼也是和bean類的邏輯差很少;

這裏面其實沒沒作啥特殊操做,主要是經過建造者模式,根據參數來建立Request類;

看一下Request的Builder的邏輯:

這裏只用做示例,沒必要關心代碼細節,咱們只須要了解這個類的職責,就是根據請求的參數構建實體類;

3,Response

Response,顧名思義,爲請求的返回體;

那麼這個類裏面究竟實現了什麼邏輯呢?

先來看一下成員變量:

從圖片能夠看出,Response和Request有點相似,封裝了一些參數返回,好比code,message,headers,body等等;

在來看一下這張圖片:

也是定義了參數的賦值與獲取,這裏就再也不贅述;

Response類裏面主要定義了服務器返回的相關信息,這裏面也沒有其餘的複雜邏輯,把它當成一個bean類來理解便可;

4,ReallCall

ReallCall,顧名思義,咱們能夠先理解爲真正的請求,那麼咱們接下來來看源碼求證一下,看看是否是真的是這樣;

首先,先來看一下參數:

從圖片能夠看出,只有幾個參數,都是在構造方法初始化的;

這裏面初始化了Transmitter,這個咱們後面再講;

那麼咱們再來看一下它這裏面有哪些方法:

這個幾個方法有沒有熟悉的感受,這裏實際上是經過橋接設計模式,實際調用的是OkHttpClient和Transmitter的函數;

這幾個方法是網絡請求的最核心的方法,execute()方法其實是同步請求,在當前線程就觸發了網絡請求,經過getResponseWithInterceptorChain()來執行網絡請求從而獲取到Response返回結果;

而enqueue(Callback responseCallback)方法,是在子線程中執行的,經過建立一個AsyncCall,傳遞給client.dispatcher(),這裏面主要是線程的執行邏輯;

這個AsyncCall是實現了Runnable接口,具體執行是在AsyncCall的execute()方法裏面;

咱們來看一下AsyncCall的execute()方法具體實現邏輯;

這裏面主要有兩個方法,一個是executeOn(),經過入參傳遞一個線程池,來執行當前線程,另外一個方法是execute(),爲異步的具體實現,讓咱們來看看這個方法作了啥?

和上面同步調用的邏輯同樣,也是經過getResponseWithInterceptorChain()來執行網絡請求從而獲取到Response返回結果;

還有這個方法getResponseWithInterceptorChain(),經過攔截器+責任鏈設計模式來實現網絡請求,這個咱們下面再介紹,這個只要瞭解是經過這個來進行網絡請求的便可;

小結: ReallCall的職責是進行真正的網絡請求,裏面封裝了兩個方法,一個是同步請求execute(),一個是異步請求enqueue(),還有最後也是最重要的一個方法getResponseWithInterceptorChain(),經過這個方法來執行網絡請求;

五、Dispatcher

咱們在看ReallCall源碼的時候,頻頻見到這個方法,client.dispatcher().調用,這個方法的真面目就是Dispatcher,這個Dispatcher是在建立OkHttpClient的時候進行初始化的;

接下來咱們來看看這個類的職責是作什麼的;

老規矩,先來看當作員變量:

參數很少,咱們來一個個介紹:

  • maxRequests:最多存在64個請求;
  • maxRequestsPerHost:每一個主機最多同時請求數爲5;
  • idleCallback:程序空閒時的回調;
  • executorService:線程池;
  • readyAsyncCalls:即將要進行的異步請求;
  • runningAsyncCalls:正在進行的異步請求;
  • runningSyncCalls:正在進行的同步請求;

下面來說一講這個類幾個比較重要的方法;

(1)線程池的建立:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
複製代碼

核心線程爲0,每隔60秒會清空空閒的線程,而最大線程無限制,可是已經經過成員變量來進行控制了,沒啥影響;

(2)同步請求的執行:

同步請求的執行是這個方法,具體調用是在ReallCall的excute()方法裏面,在Dispatcher先經過調用executed(),後面再調用finish()方法來移除同步請求;

來看一下這個finish()方法;

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
     // 移除隊列的請求
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }

	// 執行請求
    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
      // 觸發空閒線程執行
      idleCallback.run();
    }
  }
複製代碼

這個方法的邏輯很簡單,先移除隊列裏的call,而後再調用promoteAndExecute()執行已經準備好執行的請求,這個邏輯咱們下面再講;

(3)異步請求的執行:

void enqueue(AsyncCall call) {
    synchronized (this) {
    // 添加請求到異步隊列;
      readyAsyncCalls.add(call);

      if (!call.get().forWebSocket) {
        // 判斷當前請求是否已經存在
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        // 若是當前請求已經存在,則複用以前的線程計數,不進行遞增;
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    // 執行請求
    promoteAndExecute();
  }
複製代碼

這個方法裏面,先將請求添加到異步隊列readyAsyncCalls裏面,而後再調用promoteAndExecute()方法來觸發請求,下面咱們來看看promoteAndExecute()的邏輯;

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      // 一、遍歷準備要執行的請求隊列
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
		// 二、判斷當前正在執行的請求個數大於最大請求個數時,則取消請求
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        // 三、判斷當前主機的鏈接數超過5個時,則跳過當前請求;
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        // 添加請求到正在執行的隊列中
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      // 執行請求;
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }
複製代碼

這個promoteAndExecute()方法的主要邏輯是執行readyAsyncCalls隊列裏的請求,而maxRequests和maxRequestsPerHost也是在這裏處理的;

除此以後,這個類裏面還有一些查詢的方法,好比queuedCalls(),runningCalls()等,不過這些不是重點,瞭解便可;

六、Interceptor

攔截器,能夠說是OkHttp最重要的部分了,這一部分經過一個很巧妙的設計,將複雜的網絡請求邏輯分散到每一個攔截器中,這種設計模式就叫作責任鏈

責任鏈模式的優勢:下降耦合度,簡化對象,加強對象的靈活性;
缺點:性能會受到必定的影響;

那麼對於網絡請求,最簡單的實現就是從0到1,也就是我直接請求,不考慮異常因素,保證每次請求都能成功,那麼這個實現就很簡單,只須要調用請求網絡的api進行請求數據便可;

可是現實每每是殘酷的,網絡的環境極其複雜,而每一次的請求也不必定能返回,因此咱們須要使用各類策略來保證網絡請求能夠正常完成,好比重試,緩存等操做來保證網絡請求的正常使用;

而OkHttp的網絡請求經過責任鏈設計了幾個攔截器,巧妙的經過責任鏈模式來處理複雜的網絡請求,避免了類的臃腫而且提供了很好的擴展性,缺點就是當責任鏈上的對象過多時,可能會出現性能的問題;

那麼接下來咱們來看看OkHttp的攔截器的實現吧;

OkHttp的攔截器有:

  • RetryAndFollowUpInterceptor:失敗和重定向攔截器;
  • BridgeInterceptor:封裝Response的攔截器;
  • CacheInterceptor:緩存處理相關的攔截器;
  • ConnectInterceptor:鏈接服務的攔截器,真正的網絡請求在這裏實現;
  • CallServerInterceptor:負責寫請求和讀響應的攔截器;

下面咱們來一個個具體分析;

6.一、RetryAndFollowUpInterceptor

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      // 準備鏈接請求
      transmitter.prepareToConnect(request);
	  ...
      Response response;
      boolean success = false;
     
      // 執行其餘攔截器的功能,獲取Response;
      response = realChain.proceed(request, transmitter, null);

   	  // 根據Response的返回碼來判斷要執行重試仍是重定向;
      Request followUp = followUpRequest(response, route);
	  ...
      if (followUp == null) {
        // 若是followUpRequest返回的Request爲空,那邊就表示不須要執行重試或者重定向,直接返回數據;
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        // 若是followUp爲null,請求體不爲空,而且只須要請求一次時,那麼就返回response;
        return response;
      }

  	  // 判斷重試或者重定向的次數是否超過最大的次數,是的話則拋出異常;
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
	  // 將須要重試或者重定向的請求賦值給新的請求;
      request = followUp;
    }
  }
複製代碼

followUpRequest方法的邏輯咱們大概瞄一眼,就是根據返回碼來作一下操做;

6.二、BridgeInterceptor

橋接攔截器,這裏主要作網絡請求的封裝,用於簡化應用層的邏輯,好比網絡請求須要傳"Transfer-Encoding","Accept-Encoding","User-Agent","Cookie"這些參數,可是應用層不須要關心這些,那麼就由這個攔截器來作這些封裝;

當請求完成以後,也會對Response的header作一下封裝處理,返回給應用層,這樣應用層就不須要關心這個header的細節,簡化操做;

public Response intercept(Chain chain) throws IOException {
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
     // 處理封裝"Content-Length","Transfer-Encoding","Host","Connection","Accept-Encoding","Cookie","User-Agent"等請求頭;
     
	// 執行後續的攔截器的邏輯
    Response networkResponse = chain.proceed(requestBuilder.build());

  	// 獲取返回體的Builder
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
	...
    //處理返回的Response的"Content-Encoding"、"Content-Length"、"Content-Type"等返回頭;
    ...
    return responseBuilder.build();
  }
複製代碼

6.三、CacheInterceptor

CacheInterceptor是緩存處理的攔截器,咱們先來看一下這個攔截器的邏輯;

public Response intercept(Chain chain) throws IOException {
	// 先獲取候選緩存,前提是有配置緩存,也就是cache不爲空;
    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;
    // 獲取的本地的緩存;
    Response cacheResponse = strategy.cacheResponse;

    // 若是被禁止使用網絡數據且緩存數據爲空,那麼返回一個504的Response,而且body爲空;
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          ...
          .build();
    }

    // 若是不須要使用網絡數據,那麼就直接返回緩存的數據;
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

	// 執行後續的攔截器邏輯;
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      ...
    }

    if (cacheResponse != null) {
      // 若是緩存數據不爲空而且code爲304,表示數據沒有變化,繼續使用緩存數據;
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder().xx.build();
        ...
        // 更新緩存數據
        cache.update(cacheResponse, response);
        return response;
      }
    }

	// 獲取網絡返回的response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    // 將網絡數據保存到緩存中;
    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;
  }
複製代碼

在這個獲取緩存策略這一步,會生成一個CacheStrategy對象,用於管理緩存策略,那麼在將緩存策略以前,咱們先來了解幾個概念;

強緩存:在請求數據的時候,查看請求頭expires和cache-control是否命中緩存,若是是的話,那麼久就會從緩存中獲取數據,不會走網絡請求;

協商緩存:而協商緩存是在沒有命中強緩存的狀況下才會走的邏輯,必會走一次網絡請求,經過last-modified和etag返回頭判斷是否命中緩存,若是沒有命中,那麼就走網絡從新獲取到數據,協商緩存須要服務器支持才能實現;

那麼接下來咱們來看一下OkHttp是怎麼實現緩存策略邏輯的;

若是對於緩存邏輯不是很清楚的話,能夠看一下這篇文章:Android 你不得不學的HTTP相關知識

從上面那個方法,咱們來看看new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()的邏輯;

private CacheStrategy getCandidate() {
      
      // 若是不使用緩存,那麼就返回一個空的Response的CacheStrategy;
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
      // 強緩存
      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      // 判斷強緩存是否有效,是的話就返回緩存數據;
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }

      // 協商緩存
      String conditionName;
      String conditionValue;
      if (etag != null) {
        // etag協商緩存
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        // Last-Modified協商緩存
        conditionName = "If-Modified-Since";
        // 最後修改時間
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        // Last-Modified協商緩存
        conditionName = "If-Modified-Since";
        // 服務器最後修改時間
        conditionValue = servedDateString;
      } else {
        // 沒有協商緩存,返回一個空的Response的CacheStrategy;
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

	  // 設置header
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
複製代碼

6.四、ConnectInterceptor

這個攔截器比較重要,網絡的最底層實現都是經過這個類,這裏面封裝了socket鏈接和TLS握手等邏輯,接下來咱們來看看具體是怎麼實現的;

public Response intercept(Chain chain) throws IOException {
    ...
    // 這簡單的一行代碼,卻實現了無比複雜的網絡請求,Exchange用於下一個攔截器CallServerInterceptor進行網絡請求使用;
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
	// 執行後續的攔截器邏輯
    return realChain.proceed(request, transmitter, exchange);
  }
複製代碼

這個類主要實現瞭如下幾個步驟的邏輯:

  • 1:調用transmitter.newExchange方法;
  • 2:經過Transmitter的ExchangeFinder調用了find方法;
  • 3:ExchangeFinder裏調用了findHealthyConnection方法;
  • 4:ExchangeFinder裏調用了findConnection方法建立了RealConnection;
  • 5:調用了RealConnection的connect方法,實現了TCP + TLS 握手,底層經過socket來實現的;
  • 6: 經過RealConnection的newCodec方法建立了兩個協議類,一個是Http1ExchangeCodec,對應着HTTP1.1,一個是Http2ExchangeCodec,對應着HTTP2.0;

這個攔截器主要是實現網絡鏈接的邏輯,而網絡請求的邏輯是放在CallServerInterceptor這個攔截器中實現的;

那麼上面咱們講完這個攔截器的基本邏輯,下面咱們來看看更深層次的知識;

對於鏈接來講,最簡單的實現就是每次須要的時候都進行建立並鏈接,不須要考慮網絡環境,以及資源等等因素,可是現實狀況咱們不可能這樣作,由於若是要追求極致的體驗,咱們就必須得作優化;

而這裏的優化就是鏈接複用;

下面咱們來看看源碼是怎麼實現複用邏輯的,上面咱們瞭解到建立RealConnection是經過ExchangeFinder的findConnection方法,那麼咱們來看看這個方法裏的具體邏輯;

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    ...
    synchronized (connectionPool) {
      ...
      if (result == null) {
        // 嘗試從緩存池中獲取可用的鏈接
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        } else if (nextRouteToTry != null) {
          // 若是獲取不到,那麼久從Route裏面獲取
          selectedRoute = nextRouteToTry;
          nextRouteToTry = null;
        } else if (retryCurrentRoute()) {
          // 若是當前的路由是重試的路由,那麼就從路由裏面獲取
          selectedRoute = transmitter.connection.route();
        }
      }
    }
   
    if (result != null) {
      // 若是獲取到鏈接,那麼就直接返回結果
      return result;
    }

    // 若是前面沒有獲取到鏈接,那麼這裏就經過RouteSelector先獲取到Route,而後再獲取Connection;
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      //  經過RouteSelector獲取Route
      routeSelection = routeSelector.next();
    }

    // 
    List<Route> routes = null;
    synchronized (connectionPool) {
      if (newRouteSelection) {
        // 經過RouteSelector拿到Route集合(IP地址),再次嘗試從緩存池中獲取鏈接,看看是否有能夠複用的鏈接;
        routes = routeSelection.getAll();
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          ...
        }
      }

      if (!foundPooledConnection) {
        // 若是上面沒有獲取到,那麼就建立一個新的鏈接
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }
    
    // 開始TCP握手和TSL握手,這是一個阻塞的過程;
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      connectingConnection = null;
      // 將鏈接成功的RealConnection放到緩存池中,用於後續複用
      connectionPool.put(result);
      transmitter.acquireConnectionNoEvents(result);
    }
    
    return result;
  }
複製代碼

好了,上面的鏈接複用已經講完了,下面咱們來看看鏈接的具體邏輯,也就是connect的方法的具體邏輯;

public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
 	
    if (route.requiresTunnel()) {
      // 若是是HTTP的代理隧道,那麼就會走代理隧道的加載邏輯;
      connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
    } else {
      // 走正常的鏈接邏輯,無代理
      connectSocket(connectTimeout, readTimeout, call, eventListener);
    }
    // 創建協議,這裏會觸發TLS的握手
    establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
   
複製代碼

這個方法主要分三步:

  • 第一步:若是判斷設置了代理,那麼就會走有代理的方法connectTunnel,底層經過socket實現;
  • 第二步:若是沒有設置代理,那麼就走默認的無代理鏈接模式,底層經過socket實現;
  • 第三部:創建協議,這裏會觸發TLS握手的調用;

那麼接下來咱們來看看TLS握手的具體實現,調用方法是RealConnection的connectTls方法;

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    
      // 建立SSLSocket,用於鏈接;
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // 配置SSLSocket的密碼,TLS版本和擴展信息
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      
      // 開始TLS握手
      sslSocket.startHandshake();
      // 獲取sslSocketSession,用於創建會話
      SSLSession sslSocketSession = sslSocket.getSession();
      Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

      // 驗證目標主機的證書
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
        ...
      }

    // 檢查服務器提供的證書是否在固定證書裏面
    address.certificatePinner().check(address.url().host(),
        unverifiedHandshake.peerCertificates());

    // 鏈接成功,保存握手信息和ALPN協議
    String maybeProtocol = connectionSpec.supportsTlsExtensions()
        ? Platform.get().getSelectedProtocol(sslSocket)
        : null;
      ...
  }
複製代碼

這個connectTls方法主要作了幾個操做:

  • 第一步:建立SSLSocket,用於創建鏈接;
  • 第二步:開啓TLS握手;
  • 第三步:驗證目標主機的證書;
  • 第四步:檢查服務器提供的證書是否在固定證書裏面;
  • 第五步:鏈接成功,保存握手信息和ALPN協議;

第四步若是看不懂的,能夠參考一下這個連接,寫的很詳細;

SSL Pinning on Android

若是對於TLS握手還不是很清楚的,能夠看一下我以前寫的這篇文章:

Android 網絡編程之HTTPS詳解

6.五、CallServerInterceptor

在上一個攔截器ConnectInterceptor裏,咱們已經和服務器創建起鏈接了;

那麼接下來就是想服務器發送header和body以及接收服務器返回的數據了,這些邏輯都在這個攔截器中;

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

	// 將請求頭寫入到socket中,底層經過ExchangeCodec協議類(對應Http1ExchangeCodec和Http2ExchangeCodec),最終是經過Okio來實現的,具體實如今RealBufferedSink這個類裏面
    exchange.writeRequestHeaders(request);

   // 若是有body的話,經過Okio將body寫入到socket中,用於發送給服務器;
    BufferedSink bufferedRequestBody = Okio.buffer(
        exchange.createRequestBody(request, true));
    request.body().writeTo(bufferedRequestBody);
       
    // 底層經過ExchangeCodec協議類(對應Http1ExchangeCodec和Http2ExchangeCodec)來讀取返回頭header的數據;
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

	...
    
	// 建立返回體Response;
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
	
    ...
    
 	// 底層經過ExchangeCodec協議類(對應Http1ExchangeCodec和Http2ExchangeCodec)來讀取返回體body的數據;
    response = response.newBuilder()
        .body(exchange.openResponseBody(response))
        .build();
  
    return response;
  }
複製代碼

這個類的邏輯就是socket相關的操做了,在這裏經過Okio將數據寫入到socket和從socket中讀取服務器返回的數據;

OkHttp的源碼到這裏就大體分析完了,固然還有一些細節沒有涉及到,好比DNS,cookie等等,感興趣的能夠本身跟蹤源碼去分析,這裏就再也不深刻探究了;

其餘

Android 你不得不學的HTTP相關知識

Android 網絡編程之TCP、UDP詳解

Android 網絡編程之HTTPS詳解

關於我

兄dei,若是個人文章對你有幫助的話,請幫我點個贊吧️,也能夠關注一下個人Github博客;

相關文章
相關標籤/搜索