OKHttp3源碼解析

參考資源

鑒于一些關(guān)于OKHttp3源碼的解析文檔過(guò)于碎片化疹瘦,本文系統(tǒng)的财骨,由淺入深得宛畦,按照網(wǎng)絡(luò)請(qǐng)求發(fā)起的流程順序來(lái)講解OkHttp3的源碼。在自己學(xué)習(xí)的同時(shí)猫十,給大家分享一些經(jīng)驗(yàn)览濒。

主要架構(gòu)和流程

OKHttpClient、Call

OKHttp3在項(xiàng)目中發(fā)起網(wǎng)絡(luò)請(qǐng)求的API如下:

okHttpClient.newCall(request).execute();

OKHttpClient類:

  • OKHttpClient 里面組合了很多的類對(duì)象拖云。其實(shí)是將OKHttp的很多功能模塊贷笛,全部包裝進(jìn)這個(gè)類中,讓這個(gè)類單獨(dú)提供對(duì)外的API宙项,這種設(shè)計(jì)叫做外觀模式乏苦。

  • 由于內(nèi)部功能模塊太多,使用了Builder模式(生成器模式)來(lái)構(gòu)造尤筐。

它的方法只有一個(gè):newCall.返回一個(gè)Call對(duì)象(一個(gè)準(zhǔn)備好了的可以執(zhí)行和取消的請(qǐng)求)汇荐。

Call接口:

public interface Call {
  
  Request request();
 
  //同步的方法,直接返回Response
  Response execute() throws IOException;
  
  //異步的盆繁,傳入回調(diào)CallBack即可(接口掀淘,提供onFailure和onResponse方法)
  void enqueue(Callback responseCallback);
  
  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  interface Factory {
    Call newCall(Request request);
  }
}

Call接口提供了內(nèi)部接口Factory(用于將對(duì)象的創(chuàng)建延遲到該工廠類的子類中進(jìn)行,從而實(shí)現(xiàn)動(dòng)態(tài)的配置油昂,工廠方法模式)革娄。

實(shí)際的源碼中,OKHttpClient實(shí)現(xiàn)了Call.Factory接口冕碟,返回了一個(gè)RealCall對(duì)象拦惋。

@Override 
public Call newCall(Request request) {
    return new RealCall(this, request);
}

RealCall里面的兩個(gè)關(guān)鍵方法是:execute 和 enqueue。分別用于同步和異步得執(zhí)行網(wǎng)絡(luò)請(qǐng)求安寺。后面會(huì)詳細(xì)介紹厕妖。

請(qǐng)求Request、返回?cái)?shù)據(jù)Response

Request:
    public final class Request {
      //url字符串和端口號(hào)信息挑庶,默認(rèn)端口號(hào):http為80言秸,https為443.其他自定義信息
      private final HttpUrl url;
      
      //"get","post","head","delete","put"....
      private final String method;
      
      //包含了請(qǐng)求的頭部信息,name和value對(duì)挠羔。最后的形勢(shì)為:$name1+":"+$value1+"\n"+ $name2+":"+$value2+$name3+":"+$value3...
      private final Headers headers;
      
      //請(qǐng)求的數(shù)據(jù)內(nèi)容
      private final RequestBody body;
      
      //請(qǐng)求的附加字段井仰。對(duì)資源文件的一種摘要。保存在頭部信息中:ETag: "5694c7ef-24dc"破加【愣瘢客戶端可以在二次請(qǐng)求的時(shí)候,在requst的頭部添加緩存的tag信息(如If-None-Match:"5694c7ef-24dc")范舀,服務(wù)端用改信息來(lái)判斷數(shù)據(jù)是否發(fā)生變化合是。
      private final Object tag;
      
      //各種附值函數(shù)和Builder類
      ...
        
     }

其中內(nèi)部類RequestBody: 請(qǐng)求的數(shù)據(jù)。抽象類:

    public abstract class RequestBody {
     
      ...
      
      //返回內(nèi)容類型
      public abstract MediaType contentType();
    
      //返回內(nèi)容長(zhǎng)度
      public long contentLength() throws IOException {
        return -1;
      }
    
      //如何寫(xiě)入緩沖區(qū)锭环。BufferedSink是第三方庫(kù)okio對(duì)輸入輸出API的一個(gè)封裝聪全,不做詳解。
      public abstract void writeTo(BufferedSink sink) throws IOException;

    }

OKHttp3中給出了兩個(gè)requestBody的實(shí)現(xiàn)FormBody 和 MultipartBody辅辩,分別對(duì)應(yīng)了兩種不同的MIME類型:"application/x-www-form-urlencoded"和"multipart/"+xxx.作為的默認(rèn)實(shí)現(xiàn)难礼。

Response:
    public final class Response implements Closeable {
      //網(wǎng)絡(luò)請(qǐng)求的信息
      private final Request request;
      
      //網(wǎng)路協(xié)議娃圆,OkHttp3支持"http/1.0","http/1.1","h2"和"spdy/3.1"
      private final Protocol protocol;
      
      //返回狀態(tài)碼,包括404(Not found),200(OK),504(Gateway timeout)...
      private final int code;
      
      //狀態(tài)信息蛾茉,與狀態(tài)碼對(duì)應(yīng)
      private final String message;
      
      //TLS(傳輸層安全協(xié)議)的握手信息(包含協(xié)議版本讼呢,密碼套件(https://en.wikipedia.org/wiki/Cipher_suite),證書(shū)列表
      private final Handshake handshake;
      
      //相應(yīng)的頭信息谦炬,格式與請(qǐng)求的頭信息相同悦屏。
      private final Headers headers;
      
      //數(shù)據(jù)內(nèi)容在ResponseBody中
      private final ResponseBody body;
      
      //網(wǎng)絡(luò)返回的原聲數(shù)據(jù)(如果未使用網(wǎng)絡(luò),則為null)
      private final Response networkResponse;
      
      //從cache中讀取的網(wǎng)絡(luò)原生數(shù)據(jù)
      private final Response cacheResponse;
      
      //網(wǎng)絡(luò)重定向后的键思,存儲(chǔ)的上一次網(wǎng)絡(luò)請(qǐng)求返回的數(shù)據(jù)础爬。
      private final Response priorResponse;
      
      //發(fā)起請(qǐng)求的時(shí)間軸
      private final long sentRequestAtMillis;
      
      //收到返回?cái)?shù)據(jù)時(shí)的時(shí)間軸
      private final long receivedResponseAtMillis;
    
      //緩存控制指令喧枷,由服務(wù)端返回?cái)?shù)據(jù)的中的Header信息指定牲蜀,或者客戶端發(fā)器請(qǐng)求的Header信息指定。key:"Cache-Control"
      //詳見(jiàn)<a >RFC 2616,14.9</a>
      private volatile CacheControl cacheControl; // Lazily initialized.
        
      //各種附值函數(shù)和Builder類型          ...
    }

Note:所有網(wǎng)絡(luò)請(qǐng)求的頭部信息的key甩鳄,不是隨便寫(xiě)的赔桌。都是RFC協(xié)議規(guī)定的失乾。request的header與response的header的標(biāo)準(zhǔn)都不同。具體的見(jiàn) List of HTTP header fields纬乍。OKHttp的封裝類Request和Response為了應(yīng)用程序編程方便碱茁,會(huì)把一些常用的Header信息專門(mén)提取出來(lái),作為局部變量仿贬。比如contentType纽竣,contentLength,code,message,cacheControl,tag...它們其實(shí)都是以name-value對(duì)的形勢(shì)茧泪,存儲(chǔ)在網(wǎng)絡(luò)請(qǐng)求的頭部信息中蜓氨。

我們使用了retrofit2,它提供了接口converter將自定義的數(shù)據(jù)對(duì)象(各類自定義的request和response)和OKHttp3中網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)類型(ReqeustBody和ResponseBody)進(jìn)行轉(zhuǎn)換队伟。
而converterFactory是converter的工廠模式穴吹,用來(lái)構(gòu)建各種不同類型的converter。

故而可以添加converterFactory由retrofit完成requestBody和responseBody的構(gòu)造嗜侮。

這里對(duì)retrofit2不展開(kāi)討論港令,后續(xù)會(huì)出新的文章來(lái)詳細(xì)討論。僅僅介紹一下converterFacotry锈颗,以及它是如何構(gòu)建OkHttp3中的RequestBody和ResponseBody的顷霹。

  • Note: retrofit2中的Response與okhttp3中的response不同,前者是包含了后者击吱。既retrofit2中的response是一層封裝淋淀,內(nèi)部才是真正的okhttp3種的response。

我們項(xiàng)目中的一個(gè)converterFacotry代碼如下:

    public class RsaGsonConverterFactory extends Converter.Factory {
   
    //省略部分代碼
    ...
    
    private final Gson gson;

    private RsaGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    } 
    //將返回的response的Type覆醇,注釋朵纷,和retrofit的傳進(jìn)來(lái)炭臭,返回response的轉(zhuǎn)換器。Gson只需要type就可以將responseBody轉(zhuǎn)換為需要的類型袍辞。
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new RsaGsonResponseBodyConverter<>(gson, adapter);
    }
    //將request的參數(shù)類型徽缚,參數(shù)注釋,方法注釋和retrofit傳進(jìn)來(lái)革屠,返回request的轉(zhuǎn)換器。Gson只需要type就可以將request對(duì)象轉(zhuǎn)換為OKHttp3的reqeustBody類型排宰。
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new RsaGsonRequestBodyConverter<>(gson, adapter);
    }
    }

該Factory(工廠方法模式似芝,用于動(dòng)態(tài)的創(chuàng)建對(duì)象)主要是用來(lái)生產(chǎn)response的converter和request的converter。顯然我們使用了Gson作為數(shù)據(jù)轉(zhuǎn)換的橋梁板甘。分別對(duì)應(yīng)如下兩個(gè)類:

  • response的converter(之所以命名為Rsa党瓮,是做了一層加解密):

      public class RsaGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
          private final Gson gson;
          private final TypeAdapter<T> adapter;
      
          RsaGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
              this.gson = gson;
              this.adapter = adapter;
          }
      
          @Override public T convert(ResponseBody value) throws IOException {
              JsonReader jsonReader = gson.newJsonReader(value.charStream());
              try {
                  return adapter.read(jsonReader);
              } finally {
                  value.close();
              }
          }
      }
    

直接將value中的值封裝為JsonReader供Gson的TypeAdapter讀取,獲取轉(zhuǎn)換后的對(duì)象盐类。

  • request的converter:

      final class RsaGsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
          private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
          private static final Charset UTF_8 = Charset.forName("UTF-8");
      
          private final Gson gson;
          private final TypeAdapter<T> adapter;
      
          RsaGsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
              this.gson = gson;
              this.adapter = adapter;
          }
      
          @Override public RequestBody convert(T value) throws IOException {
      
              Buffer buffer = new Buffer();
              Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
              JsonWriter jsonWriter = gson.newJsonWriter(writer);
      
              adapter.write(jsonWriter, value);
              jsonWriter.close();
              //如果是RsaReq的子類寞奸,則進(jìn)行一層加密。
              if(value instanceof RsaReq){
                 //加密過(guò)程
              }
              //不需要加密在跳,則直接讀取byte值枪萄,用來(lái)創(chuàng)建requestBody
              else {
                  //這個(gè)構(gòu)造方法是okhttp專門(mén)為okio服務(wù)的構(gòu)造方法。
                  return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
              }
          }
      }   
    

上面的流操作使用的是第三方庫(kù)okio猫妙〈煞可以看到,retrofit割坠,okhttp,okio這三個(gè)庫(kù)是完全相互兼容并互相提供了專有的API齐帚。

請(qǐng)求的分發(fā)和線程池技術(shù)

OKHttpClient類中有個(gè)成員變量dispatcher負(fù)責(zé)請(qǐng)求的分發(fā)。既在真正的請(qǐng)求RealCall的execute方法中彼哼,使用dispatcher來(lái)執(zhí)行任務(wù):

  • RealCall的execute方法:

      @Override 
      public Response execute() throws IOException {
      synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
      }
      try {
        //使用dispatcher 來(lái)分發(fā)任務(wù)
        client.dispatcher().executed(this);
        Response result = getResponseWithInterceptorChain();
        if (result == null) throw new IOException("Canceled");
        return result;
      } finally {
        client.dispatcher().finished(this);
      }
      }
    
  • RealCall的enqueue方法:

      @Override public void enqueue(Callback responseCallback) {
          synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
          }
          //使用dispatcher來(lái)將人物加入隊(duì)列
          client.dispatcher().enqueue(new AsyncCall(responseCallback));
        }
    

OKHttp3中分發(fā)器只有一個(gè)類 ——Dispathcer.

Dispathcer

(1) 其中包含了線程池executorService:

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

參數(shù):

  • 0:核心線程數(shù)量对妄。保持在線程池中的線程數(shù)量(即使已經(jīng)空閑),為0代表線程空閑后不會(huì)保留敢朱,等待一段時(shí)間后停止剪菱。
  • Integer.MAX_VALUE: 線程池可容納線程數(shù)量。
  • 60拴签,TimeUnit.SECONDS: 當(dāng)線程池中的線程數(shù)大于核心線程數(shù)時(shí)琅豆,空閑的線程會(huì)等待60s后才會(huì)終止。如果小于篓吁,則會(huì)立刻停止茫因。
  • new SynchronousQueue<Runnable>():線程的等待隊(duì)列。同步隊(duì)列杖剪,按序排隊(duì)冻押,先來(lái)先服務(wù)驰贷。
    Util.threadFactory("OkHttp Dispatcher", false): 線程工廠,直接創(chuàng)建一個(gè)名為 “OkHttp Dispathcer”的非守護(hù)線程洛巢。

(2) 執(zhí)行同步的Call:直接加入runningSyncCalls隊(duì)列中括袒,實(shí)際上并沒(méi)有執(zhí)行該Call,交給外部執(zhí)行稿茉。

  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

(3) 將Call加入隊(duì)列:如果當(dāng)前正在執(zhí)行的call數(shù)量大于maxRequests,64,或者該call的Host上的call超過(guò)maxRequestsPerHost锹锰,5,則加入readyAsyncCalls排隊(duì)等待漓库。否則加入runningAsyncCalls恃慧,并執(zhí)行。

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

(4) 從ready到running的輪轉(zhuǎn)渺蒿,在每個(gè)call 結(jié)束的時(shí)候調(diào)用finished痢士,并:

    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!");
          //每次remove完后,執(zhí)行promoteCalls來(lái)輪轉(zhuǎn)茂装。
          if (promoteCalls) promoteCalls();
          runningCallsCount = runningCallsCount();
          idleCallback = this.idleCallback;
        }
        //線程池為空時(shí)怠蹂,執(zhí)行回調(diào)
        if (runningCallsCount == 0 && idleCallback != null) {
          idleCallback.run();
        }
      }

(5) 線程輪轉(zhuǎn):遍歷readyAsyncCalls,將其中的calls添加到runningAysncCalls少态,直到后者滿城侧。

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

執(zhí)行請(qǐng)求

同步的請(qǐng)求RealCall 實(shí)現(xiàn)了Call接口:
可以execute,enqueue和cancle。
異步的請(qǐng)求AsyncCall(RealCall的內(nèi)部類)實(shí)現(xiàn)了Runnable接口:
只能run(調(diào)用了自定義函數(shù)execute).

execute 對(duì)比:

  • RealCall:

    @Override public Response execute() throws IOException {
      synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
      }
      try {
        //分發(fā)彼妻。實(shí)際上只是假如了隊(duì)列赞庶,并沒(méi)有執(zhí)行
        client.dispatcher().executed(this);
        //實(shí)際上的執(zhí)行。
        Response result = getResponseWithInterceptorChain();
        //返回結(jié)果
        if (result == null) throw new IOException("Canceled");
        return result;
      } finally {
        //執(zhí)行完畢澳骤,finish
        client.dispatcher().finished(this);
      }
    }
    
  • AsyncCall:

    @Override protected void execute() {
          boolean signalledCallback = false;
          try {
            //實(shí)際執(zhí)行歧强。
            Response response = getResponseWithInterceptorChain();
            //執(zhí)行回調(diào)
            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) {
              Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            //執(zhí)行完畢,finish
            client.dispatcher().finished(this);
          }
        }

實(shí)際上的執(zhí)行函數(shù)都是getResponseWithInterceptorChain():

    private Response getResponseWithInterceptorChain() throws IOException {
        //創(chuàng)建一個(gè)攔截器列表
        List<Interceptor> interceptors = new ArrayList<>();
        //優(yōu)先處理自定義攔截器
        interceptors.addAll(client.interceptors());
        //失敗重連攔截器
        interceptors.add(retryAndFollowUpInterceptor);
        //接口橋接攔截器(同時(shí)處理cookie邏輯)
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        //緩存攔截器
        interceptors.add(new CacheInterceptor(client.internalCache()));
        //分配連接攔截器
        interceptors.add(new ConnectInterceptor(client));
        //web的socket連接的網(wǎng)絡(luò)配置攔截器
        if (!retryAndFollowUpInterceptor.isForWebSocket()) {
          interceptors.addAll(client.networkInterceptors());
        }
        //最后是連接服務(wù)器發(fā)起真正的網(wǎng)絡(luò)請(qǐng)求的攔截器
        interceptors.add(new CallServerInterceptor(
            retryAndFollowUpInterceptor.isForWebSocket())); 
        Interceptor.Chain chain = new RealInterceptorChain(
            interceptors, null, null, null, 0, originalRequest);
        //流式執(zhí)行并返回response
        return chain.proceed(originalRequest);
      }

這里的攔截器的作用:將一個(gè)流式工作分解為可配置的分段流程为肮,既實(shí)現(xiàn)了邏輯解耦摊册,又增強(qiáng)了靈活性,使得該流程清晰颊艳,可配置茅特。

各個(gè)攔截器(Interceptor)

這里的攔截器有點(diǎn)像安卓里面的觸控反饋的Interceptor。既一個(gè)網(wǎng)絡(luò)請(qǐng)求棋枕,按一定的順序白修,經(jīng)由多個(gè)攔截器進(jìn)行處理,該攔截器可以決定自己處理并且返回我的結(jié)果重斑,也可以選擇向下繼續(xù)傳遞兵睛,讓后面的攔截器處理返回它的結(jié)果。這個(gè)設(shè)計(jì)模式叫做責(zé)任鏈模式

與Android中的觸控反饋interceptor的設(shè)計(jì)略有不同的是祖很,后者通過(guò)返回true 或者 false 來(lái)決定是否已經(jīng)攔截笛丙。而OkHttp這里的攔截器通過(guò)函數(shù)調(diào)用的方式,講參數(shù)傳遞給后面的攔截器的方式進(jìn)行傳遞假颇。這樣做的好處是攔截器的邏輯比較靈活胚鸯,可以在后面的攔截器處理完并返回結(jié)果后仍然執(zhí)行自己的邏輯;缺點(diǎn)是邏輯沒(méi)有前者清晰笨鸡。

攔截器接口的源碼:

public interface Interceptor {
 Response intercept(Chain chain) throws IOException;

 interface Chain {
   Request request();

   Response proceed(Request request) throws IOException;

   Connection connection();
 }
}

其中的Chain是用來(lái)傳遞的鏈姜钳。這里的傳遞邏輯偽代碼如下:
代碼的最外層邏輯

 Request request = new Request(){};
 
 
 Arrlist<Interceptor> incpts = new Arrlist();
 Interceptor icpt0 = new Interceptor(){ XXX };
 Interceptor icpt1 = new Interceptor(){ XXX };
 Interceptor icpt2 = new Interceptor(){ XXX };
 ...
 incpts.add(icpt0);
 incpts.add(icpt1);
 incpts.add(icpt2);
 
 Interceptor.Chain chain  = new MyChain(incpts);
 chain.proceed(request);

封裝的Chain的內(nèi)部邏輯

 public class MyChain implement Interceptor.Chain{
    Arrlist<Interceptor> incpts;
    int index = 0;
    
    public MyChain(Arrlist<Interceptor> incpts){
        this(incpts, 0);
    }
    
    public MyChain(Arrlist<Interceptor> incpts, int index){
        this.incpts = incpts;
        this.index =index;
    }
    
    public void setInterceptors(Arrlist<Interceptor> incpts ){
        this.incpts = incpts;
    }
 
    @override
    Response proceed(Request request) throws IOException{
            Response response = null;
            ...
            //取出第一個(gè)interceptor來(lái)處理
            Interceptor incpt = incpts.get(index);
            //生成下一個(gè)Chain,index標(biāo)識(shí)當(dāng)前Interceptor的位置形耗。
            Interceptor.Chain nextChain = new MyChain(incpts,index+1);
            response =  incpt.intercept(nextChain);
            ...
            return response;
    }
  } 

各個(gè)Interceptor類中的實(shí)現(xiàn):

public class MyInterceptor implement Intercetpor{
    @Override 
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //前置攔截邏輯
        ...
        Response response = chain.proceed(request);//傳遞Interceptor
        //后置攔截邏輯
        ...
        return response;
    }
}

在這個(gè)鏈中哥桥,最后的一個(gè)Interceptor一般用作生成最后的Response操作,它不會(huì)再繼續(xù)傳遞給下一個(gè)趟脂。

  • 失敗重連以及重定向的攔截器:RetryAndFollowUpInterceptor

    失敗重連攔截器核心源碼:
    一個(gè)循環(huán)來(lái)不停的獲取response。每循環(huán)一次都會(huì)獲取下一個(gè)request例衍,如果沒(méi)有昔期,則返回response,退出循環(huán)佛玄。而獲取下一個(gè)request的邏輯硼一,是根據(jù)上一個(gè)response返回的狀態(tài)碼,分別作處理梦抢。

intercept方法源碼:

    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
    
        ...
    
        int followUpCount = 0;
        Response priorResponse = null;
        
        //循環(huán)入口
        while (true) {
          ...
    
          Response response = null;
          try {
            //一次請(qǐng)求處理般贼,獲得結(jié)果
            response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
          } catch (RouteException e) {
            ...
            continue;
          } catch (IOException e) {
            ...
            continue;
          } finally {
            ...
          }
    
          // 如果前一次請(qǐng)求結(jié)果不為空,講它添加到新的請(qǐng)求結(jié)果中奥吩。通常第一次請(qǐng)求一定是異常請(qǐng)求結(jié)果哼蛆,一定沒(méi)有body。
          if (priorResponse != null) {
            response = response.newBuilder()
                .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
                .build();
          }
          //獲取后續(xù)的請(qǐng)求霞赫,比如驗(yàn)證腮介,重定向,失敗重連...
          Request followUp = followUpRequest(response);
    
          if (followUp == null) {
            ...
            //如果沒(méi)有后續(xù)的請(qǐng)求了端衰,直接返回請(qǐng)求結(jié)果
            return response;
          }
           ...      
          //
          request = followUp;
          priorResponse = response;
        }
      }

獲取后續(xù)的的請(qǐng)求叠洗,比如驗(yàn)證,重定向旅东,失敗重連

private Request followUpRequest(Response userResponse) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Connection connection = streamAllocation.connection();
    Route route = connection != null
        ? connection.route()
        : null;
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            requestBuilder.method(method, null);
          }
          requestBuilder.removeHeader("Transfer-Encoding");
          requestBuilder.removeHeader("Content-Length");
          requestBuilder.removeHeader("Content-Type");
        }

        // When redirecting across hosts, drop all authentication headers. This
        // is potentially annoying to the application layer since they have no
        // way to retain them.
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();

      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
    // repeat the request (even non-idempotent ones.)
    if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
      return null;
    }

    return userResponse.request();

  default:
    return null;
}

}

  • 橋接攔截器BridgeInterceptor

橋接攔截器的主要作用是將:

  1. 請(qǐng)求從應(yīng)用層數(shù)據(jù)類型類型轉(zhuǎn)化為網(wǎng)絡(luò)調(diào)用層的數(shù)據(jù)類型灭抑。


  2. 將網(wǎng)絡(luò)層返回的數(shù)據(jù)類型 轉(zhuǎn)化為 應(yīng)用層數(shù)據(jù)類型。

         1. 保存最新的cookie(默認(rèn)沒(méi)有cookie抵代,需要應(yīng)用程序自己創(chuàng)建腾节,詳見(jiàn)
                     [Cookie的API]
                     (https://square.github.io/okhttp/3.x/okhttp/okhttp3/CookieJar.html)
                     和
                     [Cookie的持久化]
                     (https://segmentfault.com/a/1190000004345545));
         2. 如果request中使用了"gzip"壓縮,則進(jìn)行Gzip解壓禀倔。解壓完畢后移除Header中的"Content-Encoding"和"Content-Length"(因?yàn)镠eader中的長(zhǎng)度對(duì)應(yīng)的是壓縮前數(shù)據(jù)的長(zhǎng)度榄融,解壓后長(zhǎng)度變了,所以Header中長(zhǎng)度信息實(shí)效了)救湖;
         3. 返回response愧杯。
    

補(bǔ)充:Keep-Alive 連接:
HTTP中的keepalive連接在網(wǎng)絡(luò)性能優(yōu)化中,對(duì)于延遲降低與速度提升的有非常重要的作用鞋既。
通常我們進(jìn)行http連接時(shí)力九,首先進(jìn)行tcp握手,然后傳輸數(shù)據(jù)邑闺,最后釋放

http連接

這種方法的確簡(jiǎn)單跌前,但是在復(fù)雜的網(wǎng)絡(luò)內(nèi)容中就不夠用了,創(chuàng)建socket需要進(jìn)行3次握手陡舅,而釋放socket需要2次握手(或者是4次)抵乓。重復(fù)的連接與釋放tcp連接就像每次僅僅擠1mm的牙膏就合上牙膏蓋子接著再打開(kāi)接著擠一樣。而每次連接大概是TTL一次的時(shí)間(也就是ping一次)靶衍,在TLS環(huán)境下消耗的時(shí)間就更多了灾炭。很明顯,當(dāng)訪問(wèn)復(fù)雜網(wǎng)絡(luò)時(shí)颅眶,延時(shí)(而不是帶寬)將成為非常重要的因素蜈出。
當(dāng)然,上面的問(wèn)題早已經(jīng)解決了涛酗,在http中有一種叫做keepalive connections的機(jī)制铡原,它可以在傳輸數(shù)據(jù)后仍然保持連接,當(dāng)客戶端需要再次獲取數(shù)據(jù)時(shí)商叹,直接使用剛剛空閑下來(lái)的連接而不需要再次握手

Keep-Alive連接

在現(xiàn)代瀏覽器中燕刻,一般同時(shí)開(kāi)啟6~8個(gè)keepalive connections的socket連接,并保持一定的鏈路生命剖笙,當(dāng)不需要時(shí)再關(guān)閉酌儒;而在服務(wù)器中,一般是由軟件根據(jù)負(fù)載情況決定是否主動(dòng)關(guān)閉枯途。

  • 緩存攔截器CacheInterceptor

緩存攔截器的主要作用是將請(qǐng)求 和 返回 關(guān)連得保存到緩存中忌怎。客戶端與服務(wù)端根據(jù)一定的機(jī)制酪夷,在需要的時(shí)候使用緩存的數(shù)據(jù)作為網(wǎng)絡(luò)請(qǐng)求的響應(yīng)榴啸,節(jié)省了時(shí)間和帶寬。

客戶端與服務(wù)端之間的緩存機(jī)制:
  • 作用:將HTPTP和HTTPS的網(wǎng)絡(luò)返回?cái)?shù)據(jù)緩存到文件系統(tǒng)中晚岭,以便在服務(wù)端數(shù)據(jù)沒(méi)發(fā)生變化的情況下復(fù)用鸥印,節(jié)省時(shí)間和帶寬;
  • 工作原理:客戶端發(fā)器網(wǎng)絡(luò)請(qǐng)求,如果緩存文件中有一份與該請(qǐng)求匹配(URL相同)的完整的返回?cái)?shù)據(jù)(比如上一次請(qǐng)求返回的結(jié)果)库说,那么客戶端就會(huì)發(fā)起一個(gè)帶條件(例子狂鞋,服務(wù)端第一次返回?cái)?shù)據(jù)時(shí),在response的Header中添加上次修改的時(shí)間信息:Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT潜的;客戶端再次請(qǐng)求的時(shí)候骚揍,在request的Header中添加這個(gè)時(shí)間信息:If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT)的獲取資源的請(qǐng)求。此時(shí)服務(wù)端根據(jù)客戶端請(qǐng)求的條件啰挪,來(lái)判斷該請(qǐng)求對(duì)應(yīng)的數(shù)據(jù)是否有更新信不。如果需要返回的數(shù)據(jù)沒(méi)有變化,那么服務(wù)端直接返回 304 "not modified"亡呵〕榛睿客戶端如果收到響應(yīng)碼味304 的信息,則直接使用緩存數(shù)據(jù)锰什。否則下硕,服務(wù)端直接返回更新的數(shù)據(jù)。具體如下圖所示:


    帶緩存的網(wǎng)絡(luò)請(qǐng)求流程
客戶端緩存的實(shí)現(xiàn):

OKHttp3的緩存類為Cache類汁胆,它實(shí)際上是一層緩存邏輯的包裝類梭姓。內(nèi)部有個(gè)專門(mén)負(fù)責(zé)緩存文件讀寫(xiě)的類:DiskLruCache。于此同時(shí)沦泌,OKHttp3還定義了一個(gè)緩存接口:InternalCache糊昙。這個(gè)緩存接口類作為Cache的成員變量其所有的實(shí)現(xiàn)辛掠,都是調(diào)用了Cahce類的函數(shù)實(shí)現(xiàn)的谢谦。它們間具體的關(guān)系如下:

緩存的工作機(jī)制

InternalCache接口:

    public interface InternalCache {
      Response get(Request request) throws IOException;
    
      CacheRequest put(Response response) throws IOException;
    
      /**
       * 移除request相關(guān)的緩存數(shù)據(jù)
       */
      void remove(Request request) throws IOException;
    
      /**
       * 用網(wǎng)絡(luò)返回的數(shù)據(jù),更新緩存中數(shù)據(jù)
       */
      void update(Response cached, Response network);
    
      /** 統(tǒng)計(jì)網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù) */
      void trackConditionalCacheHit();
    
      /** 統(tǒng)計(jì)網(wǎng)絡(luò)返回的數(shù)據(jù) */
      void trackResponse(CacheStrategy cacheStrategy);
    }

Note:setInternalCache這個(gè)方法是不對(duì)應(yīng)用程序開(kāi)放的萝衩,應(yīng)用程序只能使用cache(Cache cache)這個(gè)方法來(lái)設(shè)置緩存回挽。并且,Cache內(nèi)部的internalCache是final的猩谊,不能被修改千劈。總結(jié):internalCache這個(gè)接口雖然是public的牌捷,但實(shí)際上墙牌,應(yīng)用程序是無(wú)法創(chuàng)建它,并附值到OkHttpClient中去的暗甥。

那么問(wèn)題來(lái)了喜滨,為什么不用Cahce實(shí)現(xiàn)InternalCache這個(gè)接口,而是以組合的方式撤防,在它的內(nèi)部實(shí)現(xiàn)都調(diào)用了Cache的方法呢虽风?
因?yàn)椋?/p>

  • 在Cache中,InternalCache接口的兩個(gè)統(tǒng)計(jì)方法:trackConditionalCacheHit和trackResponse (之所以要統(tǒng)計(jì),是為了查看緩存的效率辜膝。比如總的請(qǐng)求次數(shù)與緩存的命中次數(shù)无牵。)需要用內(nèi)置鎖進(jìn)行同步。
  • Cache中厂抖,將trackConditionalCacheHit和trackResponse方法 從public變?yōu)闉閜rivite了茎毁。不允許子類重寫(xiě),也不開(kāi)放給應(yīng)用程序验游。

Cache類:

  • 將網(wǎng)絡(luò)請(qǐng)求的文件讀寫(xiě)操作委托給內(nèi)部的DiskLruCache類來(lái)處理充岛。

  • 負(fù)責(zé)將url轉(zhuǎn)換為對(duì)應(yīng)的key。

  • 負(fù)責(zé)數(shù)據(jù)統(tǒng)計(jì):寫(xiě)成功計(jì)數(shù)耕蝉,寫(xiě)中斷計(jì)數(shù)崔梗,網(wǎng)絡(luò)調(diào)用計(jì)數(shù),請(qǐng)求數(shù)量計(jì)數(shù)垒在,命中數(shù)量計(jì)數(shù)蒜魄。

  • 負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)對(duì)象Response 與 寫(xiě)入文件系統(tǒng)中的文本數(shù)據(jù) 之間的轉(zhuǎn)換。使用內(nèi)部類Entry來(lái)實(shí)現(xiàn)场躯。具體如下:

      網(wǎng)絡(luò)請(qǐng)求的文件流文本格式如下:
              {
                   http://google.com/foo
                   GET
                   2
                   Accept-Language: fr-CA
                   Accept-Charset: UTF-8
                   HTTP/1.1 200 OK
                   3
                   Content-Type: image/png
                   Content-Length: 100
                   Cache-Control: max-age=600
                   ...
                }     
      內(nèi)存對(duì)象的Entry格式如下.
              private static final class Entry {
                  private final String url;
                  private final Headers varyHeaders;
                  private final String requestMethod;
                  private final Protocol protocol;
                  private final int code;
                  private final String message;
                  ...
              }           
    

通過(guò)okio的讀寫(xiě)API谈为,實(shí)現(xiàn)它們之間靈活的切換。

DiskLruCache類:

簡(jiǎn)介:一個(gè)有限空間的文件緩存踢关。

  • 每個(gè)緩存數(shù)據(jù)都有一個(gè)string類型的key和一些固定數(shù)量的值伞鲫。
  • 緩存的數(shù)據(jù)保存在文件系統(tǒng)的一個(gè)目錄下。這個(gè)目錄必須是該緩存獨(dú)占的:因?yàn)榫彺孢\(yùn)行時(shí)會(huì)刪除和修改該目錄下的文件签舞,因而該緩存目錄不能被其他線程使用秕脓。
  • 緩存限制了總的文件大小。如果存儲(chǔ)的大小超過(guò)了限制儒搭,會(huì)以LRU算法來(lái)移除一些數(shù)據(jù)吠架。
  • 可以通過(guò)edit,update方法來(lái)修改緩存數(shù)據(jù)。每次調(diào)用edit搂鲫,都必須以commit或者abort結(jié)束傍药。commit是原子操作。
  • 客戶端調(diào)用get方法來(lái)讀取一個(gè)緩存文件的快照(存儲(chǔ)了key,快照序列號(hào)魂仍,數(shù)據(jù)源和數(shù)據(jù)長(zhǎng)度)拐辽。

緩存使用了日志文件(文件名為journal)來(lái)存儲(chǔ)緩存的數(shù)據(jù)目錄和操作記錄。一個(gè)典型的日志文件的文本文檔如下:

     //第一行為緩存的名字
     libcore.io.DiskLruCache                            
     1                                                    //緩存的版本
     100                                                 //應(yīng)用版本
     2                                                   //值的數(shù)量          
     //緩存記錄:操作 key 第一個(gè)數(shù)據(jù)的長(zhǎng)度 第二個(gè)數(shù)據(jù)的長(zhǎng)度    
     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054    
     DIRTY 335c4c6028171cfddfbaae1a9c313c52
     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
     REMOVE 335c4c6028171cfddfbaae1a9c313c52
     DIRTY 1ab96a171faeeee38496d8b330771a7a
     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
     READ 335c4c6028171cfddfbaae1a9c313c52
     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

操作記錄:狀態(tài)+key+額外擦數(shù)

  • CLEAN key param0 param1:該key的對(duì)應(yīng)的數(shù)據(jù)為最新的有效數(shù)據(jù)擦酌,后續(xù)為額外的參數(shù)
  • DIRTY key:該key對(duì)應(yīng)的數(shù)據(jù)被創(chuàng)建或被修改俱诸。
  • REMOVE key:該key對(duì)應(yīng)的數(shù)據(jù)被刪除。
  • READ key:改key對(duì)應(yīng)的數(shù)據(jù)被讀取的記錄仑氛∫野#——用于LRU算法來(lái)統(tǒng)計(jì)哪些數(shù)據(jù)是最新的數(shù)據(jù)闸英。

一些冗余的操作記錄,比如DIRTY,REMOVE...比較多的時(shí)候(大于2000個(gè)介袜,或者超過(guò)總數(shù)量)甫何,會(huì)發(fā)器線程對(duì)該日志文件進(jìn)行壓縮(刪除這些冗余的日志記錄)。此時(shí)遇伞,會(huì)創(chuàng)建一個(gè)journal.tmp文件作為臨時(shí)的文件辙喂,供緩存繼續(xù)使用。同時(shí)還有個(gè)journal.bkp文件鸠珠,用作journal文件的臨時(shí)備份巍耗。
換文文件結(jié)構(gòu)如下:

緩存的文件mu lu

工作原理:

  • 每個(gè)緩存記錄在內(nèi)存中的對(duì)象封裝為Entry類

       private final class Entry {         
         private final String key; //緩存對(duì)應(yīng)的key
         
         private final long[] lengths; //文件的長(zhǎng)度
         private final File[] cleanFiles; //有效的數(shù)據(jù)文件
         private final File[] dirtyFiles; //正在修改的數(shù)據(jù)文件
     
         private boolean readable;//是否可讀
     
         private Editor currentEditor;//當(dāng)前的編輯器。一個(gè)Entry一個(gè)時(shí)候只能被一個(gè)編輯器修改渐排。
    
         private long sequenceNumber; //唯一序列號(hào)炬太,相當(dāng)于版本號(hào),當(dāng)內(nèi)存中緩存數(shù)據(jù)發(fā)生變化時(shí)驯耻,該序列號(hào)會(huì)改變亲族。
         
         ...    
         }
    
  • 創(chuàng)建緩存快照對(duì)象,作為每次讀取緩存時(shí)的一個(gè)內(nèi)容快照對(duì)象:

       public final class Snapshot implements Closeable {
         private final String key;   //緩存的key
         private final long sequenceNumber; //創(chuàng)建快照的時(shí)候的緩存序列號(hào)
         private final Source[] sources;//數(shù)據(jù)源,okio的API可缚,可以直接讀取
         private final long[] lengths;//數(shù)據(jù)長(zhǎng)度霎迫。
         ...
         }
    

    內(nèi)容快照作為讀去緩存的對(duì)象(而不是將Entry直接返回)的作用:
    (1). 內(nèi)容快照的數(shù)據(jù)結(jié)構(gòu)方式更便于數(shù)據(jù)的讀取(將file轉(zhuǎn)換為source),并且隱藏了Entry的細(xì)節(jié)帘靡;
    (2). 內(nèi)容快照在創(chuàng)建的時(shí)候記錄了當(dāng)時(shí)的Entry的序列號(hào)知给。因而可以用快照的序列號(hào)與緩存的序列號(hào)對(duì)比,如果序列號(hào)不相同描姚,則說(shuō)明緩存數(shù)據(jù)發(fā)生了修改涩赢,該條數(shù)據(jù)就是失效的。

  • 緩存內(nèi)容Entry的這種工作機(jī)制(單個(gè)editor轰胁,帶有序列號(hào)的內(nèi)容快照)以最小的代價(jià)谒主,實(shí)現(xiàn)了單線程修改朝扼,多線程讀寫(xiě)的數(shù)據(jù)對(duì)象(否則則需要使用復(fù)雜的鎖機(jī)制)既添降低了邏輯的復(fù)雜性赃阀,又提高了性能(缺點(diǎn)就是高并發(fā)情況下,導(dǎo)致數(shù)據(jù)頻繁失效擎颖,導(dǎo)致緩存的命中率降低)榛斯。
  • 變化的序列號(hào)計(jì)數(shù)在很多涉及并發(fā)讀取的機(jī)制都有使用。比如:SQlite的連接搂捧。
  • DiskLruCache緩存文件工作流:
DiskLruCache緩存工作流

其返回的SnapShot數(shù)據(jù)快照驮俗,提供了Source接口(okio),供外部?jī)?nèi)類Cache直接轉(zhuǎn)化為內(nèi)存對(duì)象Cache.Entry。外部類進(jìn)一步Canche.Entry轉(zhuǎn)化外OKHttpClient使用的Response允跑。

OkHttpClient從緩存中獲取一個(gè)url對(duì)應(yīng)的緩存數(shù)據(jù)的數(shù)據(jù)格式變化過(guò)程如下:

緩存數(shù)據(jù)格式變化過(guò)程

LRU的算法體現(xiàn)在:DiskLreCache的日志操作過(guò)程中王凑,每一次讀取緩存都產(chǎn)生一個(gè)READ的記錄搪柑。由于緩存的初始化是按照日志文件的操作記錄順序來(lái)讀取的,所以這相當(dāng)于把這條緩存數(shù)據(jù)放置到了緩存隊(duì)列的頂端索烹,也就完成了LRU算法:last recent used工碾,最近使用到的數(shù)據(jù)最新。

連接攔截器 和 最后的請(qǐng)求服務(wù)器的攔截器

這兩個(gè)連接器基本上完成了最后發(fā)起網(wǎng)絡(luò)請(qǐng)求的工作百姓。追所以劃分為兩個(gè)攔截器渊额,除了解耦之外,更重要的是在這兩個(gè)流程之間還可以插入一個(gè)專門(mén)為WebSocket服務(wù)的攔截器( WebSocket一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議,本文不做詳解)垒拢。

關(guān)于OKHttp如何真正發(fā)起網(wǎng)絡(luò)請(qǐng)求的旬迹,下面專門(mén)詳細(xì)講解。

發(fā)起網(wǎng)絡(luò)請(qǐng)求

  • 簡(jiǎn)介:OKHttp的網(wǎng)絡(luò)請(qǐng)求的實(shí)現(xiàn)是socket(應(yīng)用程序與網(wǎng)絡(luò)層進(jìn)行交互的API)求类。socket發(fā)起網(wǎng)絡(luò)請(qǐng)求的流程一般是:
    (1). 創(chuàng)建socket對(duì)象;
    (2). 連接到目標(biāo)網(wǎng)絡(luò);
    (3). 進(jìn)行輸入輸出流操作奔垦。
  • 在OKHttp框架里面,(1)(2)的實(shí)現(xiàn)尸疆,封裝在connection接口中宴倍,具體的實(shí)現(xiàn)類是RealConnection。(3)是通過(guò)stream接口來(lái)實(shí)現(xiàn)仓技,根據(jù)不同的網(wǎng)絡(luò)協(xié)議鸵贬,有Http1xStream和Http2xStream兩個(gè)實(shí)現(xiàn)類。
  • 由于創(chuàng)建網(wǎng)絡(luò)連接的時(shí)間較久(如果是HTTP的話脖捻,需要進(jìn)行三次握手)阔逼,而請(qǐng)求經(jīng)常是頻繁的碎片化的,所以為了提高網(wǎng)絡(luò)連接的效率地沮,OKHttp3實(shí)現(xiàn)了網(wǎng)絡(luò)連接復(fù)用:
  • 新建的連接connection會(huì)存放到一個(gè)緩存池connectionpool中嗜浮。網(wǎng)絡(luò)連接完成后不會(huì)立即釋放,而是存活一段時(shí)間摩疑。網(wǎng)絡(luò)連接存活狀態(tài)下危融,如果有相同的目標(biāo)連接,則復(fù)用該連接雷袋,用它來(lái)進(jìn)行寫(xiě)入寫(xiě)出流操作吉殃。
  • 統(tǒng)計(jì)每個(gè)connection上發(fā)起網(wǎng)絡(luò)請(qǐng)求的次數(shù),若次數(shù)為0楷怒,則一段時(shí)間后釋放該連接蛋勺。
  • 每個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)一個(gè)stream,connection鸠删,connectionpool等數(shù)據(jù)抱完,將它封裝為StreamAllocation對(duì)象。

具體的流程見(jiàn)下圖:

網(wǎng)絡(luò)請(qǐng)求API流程

類之間的關(guān)系如下:

類之間的關(guān)系

幾個(gè)主要的概念:

Connection:連接刃泡。

真正的底層實(shí)現(xiàn)網(wǎng)絡(luò)連接的接口巧娱。它包含了連接的路線碉怔,物理層socket,連接協(xié)議禁添,和握手信息眨层。

public interface Connection {
  /** 返回連接線路信息(包涵url,dns上荡,proxy) */
  Route route();

  /**
   * 返回連接使用的Socket(網(wǎng)絡(luò)層到應(yīng)用層之間的一層封裝)
   */
  Socket socket();

  /**
   * 返回傳輸層安全協(xié)議的握手信息
   */
  Handshake handshake();

  /**
   * 返回網(wǎng)絡(luò)協(xié)議:Http1.0,Http1.1,Http2.0...
   */
  Protocol protocol();
}

在OKHttp3中的實(shí)現(xiàn):RealConnection.

  • 包含了連接的信息趴樱,包括socket,它是與網(wǎng)絡(luò)層交互的接口酪捡,真正實(shí)現(xiàn)網(wǎng)絡(luò)連接叁征;
  • 內(nèi)部維護(hù)了一個(gè)列表List<StreamAllocation>,相當(dāng)于發(fā)起網(wǎng)絡(luò)請(qǐng)求的引用計(jì)數(shù)容器逛薇。下面詳細(xì)討論捺疼。
  • 包涵輸入輸出流,source和sink永罚。但是Connection只負(fù)責(zé)將socket的操作啤呼,與source和sink建立起連接,針對(duì)source和sink的寫(xiě)入和讀取操作呢袱,交給響應(yīng)的Stream完成(解耦)官扣。將socket封裝為okio的代碼:
    source = Okio.buffer(Okio.source(socket));
    sink = Okio.buffer(Okio.sink(socket));

這就將socket的讀寫(xiě)操作標(biāo)準(zhǔn)華為okio的API。

  • 還有個(gè)內(nèi)部類framedConnection羞福,專門(mén)用來(lái)處理Http2和SPDY(goole推出的網(wǎng)絡(luò)協(xié)議)的(不詳細(xì)討論)惕蹄。
Stream: 完成網(wǎng)絡(luò)請(qǐng)求的讀寫(xiě)流程功能。

接口治专,源碼如下:

public interface HttpStream {


  /** 創(chuàng)建請(qǐng)求的輸出流*/
  Sink createRequestBody(Request request, long contentLength);

  /** 寫(xiě)請(qǐng)求的頭部信息(Header) */
  void writeRequestHeaders(Request request) throws IOException;

  /** 將請(qǐng)求寫(xiě)入sokcet */
  void finishRequest() throws IOException;

  /** 讀取網(wǎng)絡(luò)返回的頭部信息(header)*/
  Response.Builder readResponseHeaders() throws IOException;

  /** 返回網(wǎng)絡(luò)返回的數(shù)據(jù) */
  ResponseBody openResponseBody(Response response) throws IOException;

  /**
   * 異步的取消卖陵,并釋放相關(guān)的資源。(connect pool的線程會(huì)自動(dòng)完成)
   */
  void cancel();
}
  • 這個(gè)功能實(shí)際上是從connection中剝離出來(lái):connection負(fù)責(zé)底層連接的實(shí)現(xiàn)张峰,其寫(xiě)入request和讀取response的功能交給stream來(lái)完成泪蔫。
  • 之所以需要將該功能解耦出來(lái),一個(gè)重要的原因:根據(jù)不同的網(wǎng)絡(luò)請(qǐng)求協(xié)議喘批,request的寫(xiě)入和response的讀出撩荣,具有不同的實(shí)現(xiàn)。比如http1,http2等谤祖,響應(yīng)的類為Http1xStream婿滓,Http2xStream老速。
ConnectionPool:內(nèi)存連接池

負(fù)責(zé)管理HTTP連接粥喜,可以復(fù)用相同Address(包括url,dns橘券,port额湘,是否加密,proxy等信息)的連接卿吐,以減少網(wǎng)絡(luò)延遲。

  • 維護(hù)了一個(gè)雙端隊(duì)列锋华,默認(rèn)的實(shí)現(xiàn)是:最多5個(gè)空閑的連接嗡官,這5個(gè)連接最多存活5分鐘。
  • 直到連接被清除毯焕,底層的socket連接才會(huì)釋放衍腥。
StreamAllocation: 網(wǎng)絡(luò)流的分配計(jì)數(shù)器(下文簡(jiǎn)稱SA)

計(jì)數(shù)功能:
每發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,都會(huì)新建SA對(duì)象纳猫。由于連接connection可以被復(fù)用婆咸,所以一個(gè)connection可以對(duì)應(yīng)多次網(wǎng)絡(luò)請(qǐng)求,即多個(gè)SA芜辕。所以RealConnection中維護(hù)了一個(gè)SA的列表尚骄。每次創(chuàng)建新的SA的時(shí)候,會(huì)在對(duì)應(yīng)的connection的列表+1侵续。以下情形下倔丈,會(huì)把列表-1,并釋放SA對(duì)應(yīng)的資源:

  • 網(wǎng)絡(luò)請(qǐng)求完成状蜗;
  • 網(wǎng)絡(luò)請(qǐng)求時(shí)發(fā)生異常需五;
  • 重定向,則release原來(lái)的網(wǎng)絡(luò)請(qǐng)求轧坎,并新建新的警儒;
  • 重定向次數(shù)超過(guò)限制(OKHttp3最大20);
  • 網(wǎng)絡(luò)返回信息頭部包涵"Connection:close"的信息眶根;
  • 網(wǎng)絡(luò)返回的數(shù)據(jù)信息長(zhǎng)度未知蜀铲,則響應(yīng)的connection不再分配stream。

該引用計(jì)數(shù)的列表的作用:統(tǒng)計(jì)一個(gè)connection對(duì)應(yīng)的網(wǎng)絡(luò)請(qǐng)求的數(shù)量属百,如果為空记劝,則connection進(jìn)入空閑,開(kāi)始倒計(jì)時(shí)回收族扰。未回收之前的connection都可以復(fù)用厌丑,降低網(wǎng)絡(luò)延遲(因?yàn)閯?chuàng)建和銷毀連接的開(kāi)銷比較長(zhǎng),如果時(shí)HTTP渔呵,則需要三次握手)怒竿。

SA中的方法:

  • newStream: 創(chuàng)建新的連接,并從connectionPool中找能夠復(fù)用的connection扩氢,如果沒(méi)有能復(fù)用的耕驰,則新建connection,并加入到connectionPool中录豺。
  • acquire:網(wǎng)絡(luò)被創(chuàng)建朦肘,將對(duì)應(yīng)的connection中SA的引用+1.
  • release:網(wǎng)絡(luò)請(qǐng)求結(jié)束饭弓,將對(duì)應(yīng)的connection中SA的引用-1.
最后的發(fā)起網(wǎng)絡(luò)請(qǐng)求的代碼
    @Override 
    public Response intercept(Chain chain) throws IOException {
    
        //獲取httpstream對(duì)象,在之前的ConnectInterceptor中創(chuàng)建
        HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
        StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
        Request request = chain.request();
    
        long sentRequestMillis = System.currentTimeMillis();
        
        //寫(xiě)請(qǐng)求的header
        httpStream.writeRequestHeaders(request);
        
        //寫(xiě)請(qǐng)求的body
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
          Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
        
        //完成網(wǎng)絡(luò)請(qǐng)求
        httpStream.finishRequest();
    
        //讀取網(wǎng)絡(luò)請(qǐng)求返回的header
        Response response = httpStream.readResponseHeaders()
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    
        //讀取網(wǎng)絡(luò)請(qǐng)求返回的body
        if (!forWebSocket || response.code() != 101) {
          response = response.newBuilder()
              .body(httpStream.openResponseBody(response))
              .build();
        }
                    
        ...
        
        //返回結(jié)果
        return response;
        }  

深入網(wǎng)絡(luò)請(qǐng)求Socket

未完待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末媒抠,一起剝皮案震驚了整個(gè)濱河市弟断,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趴生,老刑警劉巖阀趴,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異苍匆,居然都是意外死亡舍咖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)锉桑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)排霉,“玉大人,你說(shuō)我怎么就攤上這事民轴【幔” “怎么了杉辙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我着绊,道長(zhǎng)离斩,這世上最難降的妖魔是什么腮鞍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任睦疫,我火速辦了婚禮,結(jié)果婚禮上因苹,老公的妹妹穿的比我還像新娘苟耻。我一直安慰自己,他們只是感情好扶檐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布凶杖。 她就那樣靜靜地躺著,像睡著了一般款筑。 火紅的嫁衣襯著肌膚如雪智蝠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天奈梳,我揣著相機(jī)與錄音杈湾,去河邊找鬼。 笑死攘须,一個(gè)胖子當(dāng)著我的面吹牛漆撞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼叫挟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼艰匙!你這毒婦竟也來(lái)了限煞?” 一聲冷哼從身側(cè)響起抹恳,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎署驻,沒(méi)想到半個(gè)月后奋献,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旺上,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年瓶蚂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣吱。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窃这,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出征候,到底是詐尸還是另有隱情杭攻,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布疤坝,位于F島的核電站兆解,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏跑揉。R本人自食惡果不足惜锅睛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望历谍。 院中可真熱鬧现拒,春花似錦、人聲如沸望侈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)甜无。三九已至扛点,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岂丘,已是汗流浹背陵究。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奥帘,地道東北人铜邮。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親松蒜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扔茅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)秸苗,斷路器召娜,智...
    卡卡羅2017閱讀 134,665評(píng)論 18 139
  • 在上一篇文章中,主要梳理了 OKHttp 請(qǐng)求的整體流程惊楼,了解到攔截器在其中有非常重要的意義玖瘸,那么本篇文章就重點(diǎn)介...
    lijiankun24閱讀 820評(píng)論 0 3
  • 1 介紹 在我們所處的互聯(lián)網(wǎng)世界中,HTTP協(xié)議算得上是使用最廣泛的網(wǎng)絡(luò)協(xié)議檀咙。OKHttp是一款高效的HTTP客戶...
    天才木木閱讀 5,727評(píng)論 7 53
  • 最近抽時(shí)間分析一下 OKHttp3 的源碼雅倒,關(guān)于 OKHttp 源碼解析的資料已經(jīng)有很多了,但是關(guān)于 OKHttp...
    lijiankun24閱讀 353評(píng)論 0 1
  • 入臺(tái)以來(lái)的第一個(gè)、唯一一個(gè)周末棕诵,想搞些大動(dòng)作-去臺(tái)南裁良,但本周有臺(tái)風(fēng),于是決定把臺(tái)北周邊的景點(diǎn)游一遍年鸳。 室友早就說(shuō)想...
    荔枝你個(gè)栗子閱讀 253評(píng)論 0 0