原始碼分析三:OkHttp(1)—總體架構

楊昆發表於2018-03-06

剛開始接觸android,使用的網路框架是Android-async-http,之後是volley,從2016年開始,專案全部開始使用okHttp。目前來說,OkHttp是Android端最火熱的輕量級框架,由移動支付Square公司貢獻用於替代HttpUrlConnection和Apache HttpClient。

最新版本版本

implementation 'com.squareup.okhttp3:okhttp:3.10.0'複製程式碼

專案地址:https://github.com/square/okhttp,stars:2.5w+

okhttp的之所以如此受歡迎,特點如下:

  • 支援HTTPS/HTTP2/WebSocket(在OkHttp3.7中已經剝離對Spdy的支援,轉而大力支援HTTP2)
  • 內部維護任務佇列執行緒池,友好支援併發訪問
  • 內部維護連線池,支援多路複用,減少連線建立開銷
  • socket建立支援最佳路由
  • 提供攔截器鏈(InterceptorChain),實現request與response的分層處理(如透明GZIP壓縮,logging等)

分析思路

  • 整體架構
  • 攔截器
  • 任務佇列
  • 快取策略
  • 多路複用

呼叫方法

先從使用上來入手,okHttp可以構造非同步和同步的網路請求,分別如下:

同步呼叫

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain(false);
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);
  }
}複製程式碼

首先加鎖置標誌位,接著使用分配器dispatcher的executed方法將call加入到同步佇列中,然後呼叫getResponseWithInterceptorChain方法(稍後分析)執行http請求,最後呼叫finishied方法將call從同步佇列中刪除。

非同步呼叫

void enqueue(Callback responseCallback, boolean forWebSocket) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}複製程式碼

同樣先置標誌位,然後將封裝的一個執行體放到非同步執行佇列中。這裡面引入了一個新的類AsyncCall,這個類繼承於NamedRunnable,實現了Runnable介面。NamedRunnable可以給當前的執行緒設定名字,並且用模板方法將執行緒的執行體放到了execute方法中,然後根據佇列時序執行

攔截器

除了同步呼叫和非同步呼叫,OkHttp還提供了一個攔截器的概念。攔截器提供了攔截請求和攔截伺服器應答的介面。OkHttp提供了一個攔截器鏈的概念,通過將一個個攔截器組合成一個攔截器鏈,可以達到在不同層面做不同攔截操作的效果,有點AOP的意思。攔截器後面有詳細分析。

總體架構

原始碼分析三:OkHttp(1)—總體架構

上圖是OkHttp的總體架構,大致可以分為以下幾層:

  • Interface——介面層:接受網路訪問請求
  • Protocol——協議層:處理協議邏輯
  • Connection——連線層:管理網路連線,傳送新的請求,接收伺服器訪問
  • Cache——快取層:管理本地快取
  • I/O——I/O層:實際資料讀寫實現
  • Inteceptor——攔截器層:攔截網路訪問,插入攔截邏輯

Interface——介面層:

介面層接收使用者的網路訪問請求(同步請求/非同步請求),發起實際的網路訪問。OkHttpClient是OkHttp框架的客戶端,更確切的說是一個使用者皮膚。使用者使用OkHttp進行各種設定,發起各種網路請求都是通過OkHttpClient完成的。每個OkHttpClient內部都維護了屬於自己的任務佇列,連線池,Cache,攔截器等,所以在使用OkHttp作為網路框架時應該全域性共享一個OkHttpClient例項。

Call描述一個實際的訪問請求,使用者的每一個網路請求都是一個Call例項。Call本身只是一個介面,定義了Call的介面方法,實際執行過程中,OkHttp會為每一個請求建立一個RealCall,每一個RealCall內部有一個AsyncCall:

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() {
      ...
  }
    ...
}複製程式碼

AsyncCall繼承的NamedRunnable繼承自Runnable介面

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}複製程式碼

所以每一個Call就是一個執行緒,而執行Call的過程就是執行其execute方法的過程。

Dispatcher是OkHttp的任務佇列,其內部維護了一個執行緒池,當有接收到一個Call時,Dispatcher負責線上程池中找到空閒的執行緒並執行其execute方法。這部分將會單獨拿一篇部落格進行介紹,詳細內容可參考本系列接下來的文章。

Protocol——協議層:處理協議邏輯

Protocol層負責處理協議邏輯,OkHttp支援Http1/Http2/WebSocket協議,並在3.7版本中放棄了對Spdy協議,鼓勵開發者使用Http/2。

Connection——連線層:管理網路連線,傳送新的請求,接收伺服器訪問

連線層顧名思義就是負責網路連線。在連線層中有一個連線池,統一管理所有的Socket連線,當使用者新發起一個網路請求時,OkHttp會首先從連線池中查詢是否有符合要求的連線,如果有則直接通過該連線傳送網路請求;否則新建立一個網路連線。

RealConnection描述一個物理Socket連線,連線池中維護多個RealConnection例項。由於Http/2支援多路複用,一個RealConnection可以支援多個網路訪問請求,所以OkHttp又引入了StreamAllocation來描述一個實際的網路請求開銷(從邏輯上一個Stream對應一個Call,但在實際網路請求過程中一個Call常常涉及到多次請求。如重定向,Authenticate等場景。所以準確地說,一個Stream對應一次請求,而一個Call對應一組有邏輯關聯的Stream),一個RealConnection對應一個或多個StreamAllocation,所以StreamAllocation可以看做是RealConenction的計數器,當RealConnection的引用計數變為0,且長時間沒有被其他請求重新佔用就將被釋放。

連線層是OkHttp的核心部分,這部分當然也會單獨拿一篇部落格詳細講解,詳細內容可參考本專題相關文章。

Cache——快取層:管理本地快取

Cache層負責維護請求快取,當使用者的網路請求在本地已有符合要求的快取時,OkHttp會直接從快取中返回結果,從而節省網路開銷。這部分內容也會單獨拿一篇部落格進行介紹,詳細內容可參考本專題相關文章。

I/O——I/O層:實際資料讀寫實現

I/O層負責實際的資料讀寫。OkHttp的另一大有點就是其高效的I/O操作,這歸因於其高效的I/O庫okio

這部分內容筆者也打算另開一個專題進行介紹。詳細內容可以參考本部落格相關內容。

Inteceptor——攔截器層:攔截網路訪問,插入攔截邏輯

攔截器層提供了一個類AOP介面,方便使用者可以切入到各個層面對網路訪問進行攔截並執行相關邏輯。在下一篇部落格中,筆者將通過介紹一個實際的網路訪問請求例項來介紹攔截器的原理。


相關文章