從OKHttp框架看程式碼設計

拉丁吳發表於2016-11-03

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

HTTP簡介

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

從OKHttp框架看程式碼設計

上圖列出的並不夠詳細,因為我們並不是想研究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框架看程式碼設計

可以看到,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。

從OKHttp框架看程式碼設計

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

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

所有牛逼的工程師,都是一個牛逼的設計師

共勉。


勘誤

暫無

相關文章