從OKHttp框架看代碼設計

在Android端,比較有名的網絡請求框架是OkHttp和Retrofit,後者在網絡請求又是依賴OkHttp的。因此說OkHttp是Android世界裏最出名的框架也不爲過,今天,咱們就來認真分析一下這個框架,依照我務實的風格,這篇文章絕對不會是爲了讀源碼而讀源碼。javascript

HTTP簡介

分析這個Http框架,咱們就先從Http談起,Http是互聯網上應用最廣泛的通信協議。而所謂通信協議,就是指通信雙方約定好傳輸數據的格式。因此要理解Http,只須要理解Http傳輸數據的格式,下面是Http數據傳輸雙方的大體的數據格式。java

上圖列出的並不夠詳細,由於咱們並非想研究Http自己。web

從上圖能夠看到,一個Http請求自己其實很簡單。緩存

從客戶端的角度來看服務器

  • 裝配Request(涉及到請求方法,url,cookie等等)
  • Client端發送request請求
  • 接收服務端返回的Response數據

是否是簡單到使人髮指?提及來這和大象裝冰箱其實還蠻像的。cookie


一個簡單的OkHttp請求

結合上面的步驟,咱們來看看在OkHttp框架是怎麼完成簡單一個網絡請求的呢?網絡

//構造Request
Request req=new Request.Builder()
                        .url(url)
                        .build();
//構造一個HttpClient
OkHttpClient client=new OkHttpClient();
//發送請求
client.newCall(req)
        .enqueue(new Callback() {
            //得到服務器返回的Response數據
            @Override
            public void onResponse(Call arg0, Response arg1) throws IOException {

            }

            @Override
            public void onFailure(Call arg0, IOException arg1) {
                // TODO Auto-generated method stub

            }
        });複製代碼

你瞧,這些步驟和咱們預想的都是同樣的,OKHttp爲你封裝了那些複雜,繁瑣的東西,只給你你想要的和你須要的。框架

既然它把Http請求封裝得如此簡單,它的內部的設計是否也很是的嚴謹呢?異步

首先給出OkHttp框架的整個處理流程:socket

能夠看到,OKHttp的框架中,構造Request和HttpClient兩個過程其實仍是很簡單的,而發送請求的過程則顯得十分複雜,固然也是最精彩的部分。

下面咱們就按照客戶端Http請求的流程來看看OKHttp框架的源碼。

構造Request

public final class Request {
   .......
   .....
  public static class Builder {
    private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    private Object tag;
    }

}複製代碼

使用builder的方式,來插入請求的數據,構建Request,Builder模式相信你們很是熟悉了,因此這裏僅僅給出它可構造的參數。

雖說是構建Request,其實也言過其實,由於你能看到實際上這些數據是不足以構建一個合法的Request的,其餘待補全的信息實際上是OkHttp在後面某個環節幫你加上去,但至少,在開發者來看,第一步構建Request此時已經完成了。

構造OKHttpClient

public class OkHttpClient implements Cloneable, Call.Factory, WebSocketCall.Factory {
  ......
  ......
  public static final class Builder {
    Dispatcher dispatcher;
    Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    ProxySelector proxySelector;
    CookieJar cookieJar;
    Cache cache;
    InternalCache internalCache;
    SocketFactory socketFactory;
    SSLSocketFactory sslSocketFactory;
    CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;

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

}複製代碼

依然是Builder來構建OKHttpClient,值得注意的是OKHttpClient實現了 Call.Factory接口,建立一個RealCall類的實例(Call的實現類)。開頭咱們看到,在發送請求以前,須要調用newCall()方法,建立一個指向RealCall實現類的Call對象,實際上RealCall包裝了Request和OKHttpClient這兩個類的實例。使得後面的方法中能夠很方便的使用這二者

咱們能夠看看RealCall的上層接口Call:

public interface Call extends Cloneable {
  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}複製代碼

基本上咱們會用到的大部分操做都定義在這個接口裏面了,能夠說這個接口是OkHttp框架的操做核心。在構造完HttpClient和Request以後,咱們只要持有Call對象的引用就能夠操控請求了。

咱們繼續按照上面的流程圖來查看代碼,發送請求時,將請求丟入請求隊列,即調用realCall.enqueue();

RealCall.java

@Override 
 public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //對代碼作了一點結構上的轉化,幫助閱讀
   Dispatcher dispatcher=client.dispatcher()
   //從這裏咱們也能感受到,咱們應該全局維護一個OkHttpClient實例,
    //由於每一個實例都會帶有一個請求隊列,而咱們只須要一個請求隊列便可
   dispatcher.enqueue(new AsyncCall(responseCallback));

    /* *這個AsyncCall類繼承自Runnable *AsyncCall(responseCallback)至關於構建了一個可運行的線程 *responseCallback就是咱們指望的response的回調 */
  }複製代碼

咱們能夠進入Dispatcher這個分發器內部看看enqueue()方法的細節,再回頭看看AsyncCall執行的內容。

Dispatcher.java

...
...
  /**等待異步執行的隊列 */
  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<>();
  ...
  ...


  synchronized void enqueue(AsyncCall call) {
    //若是正在執行的請求小於設定值,
    //而且請求同一個主機的request小於設定值
    if (runningAsyncCalls.size() < maxRequests &&
            runningCallsForHost(call) < maxRequestsPerHost) {
        //添加到執行隊列,開始執行請求
      runningAsyncCalls.add(call);
      //得到當前線程池,沒有則建立一個
      ExecutorService mExecutorService=executorService();
      //執行線程
      mExecutorService.execute(call);
    } else {
        //添加到等待隊列中
      readyAsyncCalls.add(call);
    }
  }複製代碼

在分發器中,它會根據狀況決定把call加入請求隊列仍是等待隊列,在請求隊列中的話,就會在線程池中執行這個請求。

嗯,如今咱們能夠回頭查看AsyncCall這個Runnable的實現類

RealCall.java

//它是RealCall的一個內部類
//NamedRunnable實現了Runnable接口,把run()方法封裝成了execute()
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override 
    protected void execute() {
      boolean signalledCallback = false;
      try {
        //一言不和就返回Response,那沒說的,這個方法裏面確定執行了request請求
        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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } 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));
    //包裹這request的chain
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }複製代碼

在這個getResponseWithInterceptorChain()方法中,咱們看到了大量的Interceptor,根據上面的流程圖,就意味着網絡請求流程可能到了末尾了,也終於到了我介紹的重點了,由於這個Interceptor設計確實是精彩。

瞭解Interceptor以前,咱們先來理一理,到目前爲止,咱們只有一個信息不全的Request,框架也沒有作什麼實質性的工做,與其說網絡請求快到結尾了,不如說咱們纔剛剛開始,由於不少事情:爲Request添加必要信息,request失敗重連,緩存,獲取Response等等這些什麼都沒作,也就是說,這全部的工做都交給了Interceptor,你能想象這有多複雜。

Interceptor:大家這些辣雞。


Interceptor詳解

Interceptor是攔截者的意思,就是把Request請求或者Response回覆作一些處理,而OkHttp經過一個「鏈條」Chain把全部的Interceptor串聯在一塊兒,保證全部的Interceptor一個接着一個執行。

這個設計忽然之間就把問題分解了,在這種機制下,全部繁雜的事物均可以歸類,每一個Interceptor只執行一小類事物。這樣,每一個Interceptor只關注本身分內的事物,問題的複雜度一會兒下降了幾倍。並且這種插拔的設計,極大的提升了程序的可拓展性。

我特麼怎麼就沒有想到過這種設計??

平復一下心情......

咱們先來看看Interceptor接口:

public interface Interceptor {
  //只有一個接口方法
  Response intercept(Chain chain) throws IOException;
    //Chain大概是鏈條的意思
  interface Chain {
    // Chain其實包裝了一個Request請求
    Request request();
    //得到Response
    Response proceed(Request request) throws IOException;
    //得到當前網絡鏈接
    Connection connection();
  }
}複製代碼

其實「鏈條」這個概念不是很容易理解Interceptor(攔截者),所以,我用一個更加生動的環形流水線生產的例子來幫助你在概念上徹底理解Interceptor。

「包裝了Request的Chain遞歸的從每一個Interceptor手中走過去,最後請求網絡獲得的Response又會逆序的從每一個Interceptor走回來,把Response返回到開發者手中」

我相信,經過這個例子,關於OkHttp的Interceptor以及它和Chain之間的關係在概念上應該可以理清楚了。

可是我仍然想講講每一個Intercept的做用,以及在代碼層面他們是如何依次被調用的。

那咱們繼續看看代碼

Response getResponseWithInterceptorChain() throws IOException {

    List<Interceptor> interceptors = new ArrayList<>();
    //添加開發者應用層自定義的Interceptor
    interceptors.addAll(client.interceptors());
    //這個Interceptor是處理請求失敗的重試,重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //這個Interceptor工做是添加一些請求的頭部或其餘信息
    //並對返回的Response作一些友好的處理(有一些信息你可能並不須要)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //這個Interceptor的職責是判斷緩存是否存在,讀取緩存,更新緩存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //這個Interceptor的職責是創建客戶端和服務器的鏈接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        //添加開發者自定義的網絡層攔截器
      interceptors.addAll(client.networkInterceptors());
    }
    //這個Interceptor的職責是向服務器發送數據,
    //而且接收服務器返回的Response
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //一個包裹這request的chain
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    //把chain傳遞到第一個Interceptor手中
    return chain.proceed(originalRequest);
  }複製代碼

到這裏,咱們經過源碼已經能夠總結一些在開發中須要注意的問題了:

  • Interceptor的執行的是順序的,也就意味着當咱們本身自定義Interceptor時是否應該注意添加的順序呢?
  • 在開發者自定義攔截器時,是有兩種不一樣的攔截器能夠自定義的。

接着,從上面最後兩行代碼講起:

首先建立了一個指向RealInterceptorChain這個實現類的chain引用,而後調用了 proceed(request)方法。

RealInterceptorChain.java

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final Connection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }
....
....
....
 @Override 
 public Response proceed(Request request) throws IOException {
            //直接調用了下面的proceed(.....)方法。
    return proceed(request, streamAllocation, httpCodec, connection);
  }

    //這個方法用來獲取list中下一個Interceptor,並調用它的intercept()方法
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;
    ....
    ....
    ....

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //從list中獲取到第一個Interceptor
    Interceptor interceptor = interceptors.get(index);
    //而後調用這個Interceptor的intercept()方法,並等待返回Response
    Response response = interceptor.intercept(next);
    ....
    ....
    return response;
  }複製代碼

從上文可知,若是沒有開發者自定義的Interceptor時,首先調用的RetryAndFollowUpInterceptor,負責失敗重連操做

RetryAndFollowUpInterceptor.java

...
...
    //直接調用自身的intercept()方法
 @Override 
 public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    ....
    ....
      Response response = null;
      boolean releaseConnection = true;
      try {
        //在這裏經過繼續調用RealInterceptorChain.proceed()這個方法
        //在RealInterceptorChain的list中拿到下一個Interceptor
        //而後繼續調用Interceptor.intercept(),並等待返回Response
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        ....
        ....
      } catch (IOException e) {
       ....
       ....
      } finally {
        ....
        ....
      }

    }
  }
...
...複製代碼

嗯,到這裏,Interceptor纔算講的差很少了,OKHttp也纔算講得差很少了,若是你想研究每一個Interceptor的細節,歡迎自行閱讀源碼,如今在框架上,你不會再遇到什麼難題了。這裏篇幅太長,不能再繼續講了。

若是你仍是好奇OKHttp究竟是怎麼發出請求?

我能夠作一點簡短的介紹:這個請求動做發生在CallServerInterceptor(也就是最後一個Interceptor)中,並且其中還涉及到Okio這個io框架,經過Okio封裝了流的讀寫操做,能夠更加方便,快速的訪問、存儲和處理數據。最終請求調用到了socket這個層次,而後得到Response。


總結

OKHttp中的這個Interceptor這個設計十分精彩,不只分解了問題,下降了複雜度,還提升了拓展性,和可維護性,總之值得你們認真學習。

我記得有位前輩和我講過,好的工程師不是代碼寫的快寫的工整,而是代碼的設計完美。不少時候,咱們都在埋頭寫代碼,卻忘記了如何設計代碼,如何在代碼層面有效的分解難度,劃分問題,即便在增長需求,項目的複雜度也保持不變,這是咱們都應該思考的問題。

全部牛逼的工程師,都是一個牛逼的設計師

共勉。


勘誤

暫無

相關文章
相關標籤/搜索