OkHttp

介紹

一個現(xiàn)代的Http請求客戶端,可以在java或者android使用,有以下特點

  • 支持HTTP2
  • 連接池,實現(xiàn)Http1.1長連接和http2.0多路復(fù)用
  • 攔截器,內(nèi)部預(yù)置攔截器和自定義攔截器支持,可以往HTTP請求時插入邏輯和職責(zé)

收獲

  • 攔截器的設(shè)計很精妙,責(zé)任鏈模式,單一職責(zé)思想,鏈?zhǔn)秸{(diào)用??山档痛a的工程復(fù)雜度,易擴展,易維護(hù)
  • 分層和模塊化是分解項目的重要手段,復(fù)雜龐大的功能可以通過分層和模塊化一步步拆解,工程上更容易實現(xiàn)和穩(wěn)定
  • 各個層次攔截器的閱讀,可以了解okhttp是如何一步步實現(xiàn)http協(xié)議,最底層的CallServerInterceptor是最終的HTTP包的構(gòu)建,解析,讀取,寫入。

sample

OkHttpClient client = new OkHttpClient();

  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();

調(diào)用流程

  • 構(gòu)建OkHttpClient
  • 構(gòu)建Request
  • OkHttpClient#newCall(Request)
  • call#execute或者call#enqueue(callback)
  • 解析Response

接口分析

構(gòu)建OKHttpClient

一如既往,提供一個外觀類OKHttpClient封裝了所有的配置,這個類毫無意外也是通過Builder構(gòu)建。
Builder

  • timeout參數(shù)配置(call,connect,read,write)
  • proxy配置http代理
  • cookieJar
  • cache
  • dns
  • socketFactory
  • sslSocketFactory https相關(guān),配置CA
  • hostnameVerifier
  • connectionPool
  • dispatcher
  • addInterceptor
  • addNetworkInterceptor
  • eventListener 用于監(jiān)聽網(wǎng)絡(luò)請求的時間
  • build

構(gòu)建Request

也提供Builder

  • url
  • header(String name,String value)
  • cacheControl
  • get,post,delete,put,patch
  • method(String method,RequestBody)設(shè)定http請求方法
  • tag
    對比Retrofit就發(fā)現(xiàn)接口比較原始,基本上更接近Http協(xié)議
  • Url
  • http method
  • header
  • body

Call

public interface Call extends Cloneable {
 
  Response execute() throws IOException;
  void enqueue(Callback responseCallback);
  void cancel();
 
  Request request();

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

提供同步和異步方法,注意OKHttp enqueue后的callback返回并不是UI線程,Retrofit幫我們轉(zhuǎn)接了。

框架設(shè)計

okhttp架構(gòu).png

這是原文,這個圖大致按照調(diào)用棧大致描繪了層次關(guān)系。

  • 接口層
  • 協(xié)議層
  • 連接層 連接池,支持長連接和多路復(fù)用
  • cache層
  • I/O層 高效的IO操作,依賴okio
  • 攔截器 貫穿上下,非常重要

攔截器

攔截器是OKHttp的一大特性,它是典型的責(zé)任鏈模式,鏈?zhǔn)竭f歸調(diào)用

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

  interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
    Call call();
}

分為application和network攔截器,主要處理request和response
一個interceptor通常步驟

  1. 處理Request
  2. 調(diào)用Chain#proceed
  3. 處理Response并返回

我們知道OkHttp通過newCall,返回的其實是RealCall,然后我們看RealCall#execute方法

public Response execute() throws IOException {
 
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      return result;
    }finally {
      client.dispatcher().finished(this);
    }
  }
   Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    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);
  }

getResponseWithInterceptorChain干了4件事

  • 添加用戶自定義的application interceptor
  • 添加內(nèi)置攔截器
  • 添加用戶自定義的network interceptor
  • 通過RealInterceptorChain開始鏈?zhǔn)竭f歸調(diào)用
public final class RealInterceptorChain implements Interceptor.Chain {
 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
 
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    return response;
  }
}

這個RealInterceptorChain#proceed里又構(gòu)建了RealInterceptorChain,調(diào)用了攔截器鏈表的下一個,每個攔截器的intercept方法需要調(diào)用的chain都是這個RealInterceptorChain,只不過新的實例,新的參數(shù)。Interceptor負(fù)責(zé)調(diào)chain#proceed觸發(fā)下一個攔截器


攔截器.png

內(nèi)置攔截器

  • retryAndFollowUpInterceptor 超時重試和重定向
  • BridgeInterceptor 一些header字段,content-length,content-encode做透明gzip,cookie,keep-alive等
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor 真正的網(wǎng)絡(luò)請求

自定義攔截器

  • application 不考慮重傳和重定向,不考慮cache,永遠(yuǎn)調(diào)用一次
  • network 在connect和callserver之間,命中cache會被短路

總結(jié)攔截器我們發(fā)現(xiàn),整個流程一層層往下貫穿,再一層層往上,跟網(wǎng)絡(luò)協(xié)議棧的思路是一樣的。這里其實也可以用裝飾者來實現(xiàn)

interface ITask {
    Response call(Requst requst);
  }
  
  class TaskImpl implements ITask{
    private ITask nextTask;
    public TaskImpl(ITask task){
      nextTask = task;
    }

    public Response call(Requst requst) {
    // 在此處可以處理request
      if(nextTask != null){
        response = nextTask.call(requst);
      }
    // 在此處可以處理response
      return response;
    }
  }

class main(){
    ITask a = new TaskImpl();
    ITask b = new TaskImpl(a);
    ITask c = new TaskImpl(b);
    c.call(request);
  }

任務(wù)調(diào)度

通常我們會調(diào)用call#enqueu(callback)異步方法等待結(jié)果返回,OKHttp內(nèi)部維護(hù)線程池用來執(zhí)行請求,具體實現(xiàn)類是Dispatcher

public final class Dispatcher {
private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
 /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

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

void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    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.
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        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;
  }
}

內(nèi)部維護(hù)了3個任務(wù)隊列來存儲請求,一個線程池來執(zhí)行任務(wù)
enqueue

  • 先把任務(wù)插入readyQueue
  • 遍歷readyQueue,判斷是否超過總體最大值和單host最大值
  • 遍歷所有可運行的請求,調(diào)用AsyncCall#executeOn(executorService)
  • 這個AsyncCall最終也是調(diào)用的getResponseWithInterceptorChain觸發(fā)攔截器,獲取結(jié)果,然后直接在子線程回調(diào)結(jié)果

緩存

CacheInterceptor來攔截緩存,使用DiskLruCache來實現(xiàn)緩存,CacheStrategy做緩存策略

public final class CacheInterceptor implements Interceptor {
  public Response intercept(Chain chain) throws IOException {
     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;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

     // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
  }
}
  • 通過request獲取cache結(jié)果
  • 通過CacheStrategy判斷是否需要請求網(wǎng)絡(luò),不需要直接短路返回,不繼續(xù)往下走攔截器
  • 繼續(xù)chain.proceed,請求網(wǎng)絡(luò),獲取response
  • 更新緩存
  • 返回response

CacheStrategy

public final class CacheStrategy {
/** The request to send on the network, or null if this call doesn't use the network. */
  public final @Nullable Request networkRequest;
  /** The cached response to return or validate; or null if this call doesn't use a cache. */
  public final @Nullable Response cacheResponse;

  public static class Factory {
    final long nowMillis;
    final Request request;
    final Response cacheResponse;
    private Date servedDate;
    private Date lastModified;
     private Date expires;
    private String etag;
  }

  public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null, null);
      }
      return candidate;
    }

    private CacheStrategy getCandidate() {
     CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

  if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }
      。。。
    }
}
  • 通過Factory構(gòu)建CacheStrategy
  • 兩個公有final變量,networkRequest標(biāo)識是否需要請求網(wǎng)絡(luò),CacheResponse組裝了緩存的結(jié)果
  • 工廠循環(huán)遍歷cache response的header,主要是緩存刷新的兩組字段,expires和last-modifiled,etag
  • CacheStrategy基本根據(jù)HTTP的cache協(xié)議

連接池

性能提升的關(guān)鍵,為了實現(xiàn)http1.1的長連接和http2的多路復(fù)用

  • 長連接,一個請求結(jié)束后,不會立即關(guān)閉TCP socket,而是等待下一個請求,直到超時。規(guī)避TCP的擁塞控制的慢啟動,可以顯著提升響應(yīng)速度
  • 多路復(fù)用,二進(jìn)制幀,header壓縮。一個tcp socket支持多個http請求并行,大大增加并行效率

地址

  • url
  • Address 包含域名,port,https setting,protocol
  • Route 包含ip,proxy。同一個Address可能有多個Route,因為DNS返回多個ip

流程

public interface Connection {
  Route route();
  //TCP連接
  Socket socket();
  //TLS
  Handshake handshake();
  //Http協(xié)議
  Protocol protocol();
}
  • 通過url創(chuàng)建Address
  • 從ConnectionPool獲取Connection
  • 如果沒獲取到,則向DNS查詢IP,得到Route
  • 如果是新的route,發(fā)起tcp連接或者tls握手,獲得Connection
  • 通過Connection發(fā)起請求,流轉(zhuǎn)到network攔截器

ConnectInterceptor通過StreamAllocation#newStream獲得Connection

CallServerInterceptor

真正的http請求和解析都在這個攔截器里面,依賴okio這個庫。

  • exchange 管理類
  • ExchangeCode,接口類,定義打包request,解析response的行為
/** Encodes HTTP requests and decodes HTTP responses.  */
interface ExchangeCodec {

  /** Returns an output stream where the request body can be streamed.  */
  fun createRequestBody(request: Request, contentLength: Long): Sink

  /** This should update the HTTP engine's sentRequestMillis field.  */
  fun writeRequestHeaders(request: Request)

  /** Flush the request to the underlying socket and signal no more bytes will be transmitted.  */
  fun finishRequest()

 fun readResponseHeaders(expectContinue: Boolean): Response.Builder?

  fun openResponseBodySource(response: Response): Source
}

okhttp遷移很多文件為Kotlin,我們至少要大致能看懂Kotlin代碼

  • Http1ExchangeCodec HTTP/1協(xié)議的實現(xiàn)類
  • Http2ExchangeCodec HTTP/2協(xié)議的實現(xiàn)類。二進(jìn)制Header和Body。多路復(fù)用。
public final class Http1ExchangeCodec implements ExchangeCodec {
 /** The client that configures this stream. May be null for HTTPS proxy tunnels. */
  private final OkHttpClient client;

  /** The connection that carries this stream. */
  private final RealConnection realConnection;
  //socket對應(yīng)的輸入流
  private final BufferedSource source;
  //socket對應(yīng)的輸出流
  private final BufferedSink sink;

 /** HTTP協(xié)議標(biāo)準(zhǔn),寫入request到流,requestline和header */
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }

@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
       StatusLine statusLine = StatusLine.parse(readHeaderLine());

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

      return responseBuilder;
 
  }

/** 按照http標(biāo)準(zhǔn)讀取header,一行一行的讀 */
  private Headers readHeaders() throws IOException {
    Headers.Builder headers = new Headers.Builder();
    // parse the result headers until the first blank line
    for (String line; (line = readHeaderLine()).length() != 0; ) {
      addHeaderLenient(headers, line);
    }
    return headers.build();
  }

 @Override public Source openResponseBodySource(Response response) {
    if (!HttpHeaders.hasBody(response)) {
      return newFixedLengthSource(0);
    }

  //分段讀取
    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
      return newChunkedSource(response.request().url());
    }

  // size已知
    long contentLength = HttpHeaders.contentLength(response);
    if (contentLength != -1) {
      return newFixedLengthSource(contentLength);
    }

    return newUnknownLengthSource();
  }
}

[如何調(diào)試](https://blog.csdn.net/alvinhuai/article/details/81288270,用Android Studio跑OkHttp的sampleClient模塊,加一些配置,可在本機直接跑,也可以用AS的Debugger斷點調(diào)試。像Retrofit這種純java的庫,都可以在本機調(diào)試,效率高。
)

reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 2.okhttp3.0整體流程:1).創(chuàng)建okhttpclient客戶端對象,表示所有的http請求的客戶端的類,...
    無為3閱讀 433評論 0 1
  • Okhttp 基礎(chǔ)知識導(dǎo)圖 Okhttp 使用1,創(chuàng)建一個客戶端。2,創(chuàng)建一個請求。3,發(fā)起請求(入?yún)⒒卣{(diào))。 一...
    gczxbb閱讀 2,153評論 0 2
  • 本文為本人原創(chuàng),轉(zhuǎn)載請注明作者和出處。 在上一章我們分析了Okhttp分發(fā)器對同步/異步請求的處理,本章將和大家一...
    業(yè)松閱讀 1,046評論 2 8
  • 前言 用OkHttp很久了,也看了很多人寫的源碼分析,在這里結(jié)合自己的感悟,記錄一下對OkHttp源碼理解的幾點心...
    Java小鋪閱讀 1,607評論 0 13
  • 文/SweetAnna 媽媽是個性比較強的女子 我年輕那會,爸爸媽媽時不時的會吵架 看到他們爭吵...
    SweetAnna閱讀 137評論 0 0

友情鏈接更多精彩內(nèi)容