okHttp架構

從OKHttp框架看代碼設計

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

HTTP簡介

分析這個Http框架,我們就先從Http談起,Http是互聯網上應用最普遍的通訊協議。而所謂通訊協議,就是指通訊雙方約定好傳輸數據的格式。所以要理解Http,只需要理解Http傳輸數據的格式,下面是Http數據傳輸雙方的大致的數據格式。

上圖列出的並不夠詳細,因爲我們並不是想研究Http本身。

從上圖可以看到,一個Http請求本身其實很簡單。

從客戶端的角度來看

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

是不是簡單到令人髮指?說起來這和大象裝冰箱其實還蠻像的。


一個簡單的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框架的整個處理流程:

可以看到,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這個設計十分精彩,不僅分解了問題,降低了複雜度,還提高了拓展性,和可維護性,總之值得大家認真學習。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章