網(wǎng)絡(luò)框架- Okhttp3與Volley整體架構(gòu)分析

一、Okhttp3

okhttp由square公司出品,目前主流的網(wǎng)絡(luò)庫,功能相對比較全面。

1.1總體框架設(shè)計

Okhttp 3.4之前有個類:HttpEngine,網(wǎng)絡(luò)請求和響應(yīng)的核心邏輯封裝在HttpEngine中,3.4及其以后版本重構(gòu)了,去掉了這個類,它的功能在getResponseWithInterceptorChain中拆分為CacheInterceptor、ConnectInterceptor、CallServerInterceptor 等幾個攔截器來實現(xiàn)。

1.2請求流程
1.3 類圖
1.4 異步請求時序圖

基本流程:
1)Okhttp通過newCall創(chuàng)建RealCall發(fā)起一個網(wǎng)絡(luò)請求任務(wù),同時傳入Request。
2)網(wǎng)絡(luò)請求的調(diào)度交給Dispatcher通過一個受限制的Cached類型的線程池來處理。
3)3.4之前通過HttpEngine來處理網(wǎng)絡(luò)請求,之后通過對應(yīng)的幾個固定攔截器來處理網(wǎng)絡(luò)請求。
4)callback返回Response。

二、Volley

Volley 是 Google 1/0 2013 發(fā)布的Android異步網(wǎng)絡(luò)請求框架和圖片加載框架。適合數(shù)據(jù)量小,通信頻繁的網(wǎng)絡(luò)操作。

2.1總體框架設(shè)計

上層接受自定義Reuqest,請求放入RequestQueue中,通過兩種Dispatch Thread不斷從RequestQueue中取出請求,根據(jù)是否已緩存調(diào)用Cache或Network這兩類數(shù)據(jù)獲取接口之一,
從內(nèi)存緩存或是服務(wù)器取得請求的數(shù)據(jù),然后交由ResponseDelivery去做結(jié)果分發(fā)及回調(diào)處理。

2.2請求流程
2.3 類圖
2.4 請求時序圖

基本流程:
1)Volley創(chuàng)建一個RequestQueue,該Queue包含1個CacheDispatcher線程,4個NetworkDispatcher線程。
2)request設(shè)置能緩存則請求放入mCacheQueue,交由CacheDispatcher處理,獲取緩存。不能緩存放入mNetworkQueue,走網(wǎng)絡(luò)請求。
3)真正網(wǎng)絡(luò)請求由BasicNetwork.performRequest執(zhí)行。
4)最終解析response并返回callback。

三、Okhttp3 和 Volley幾個點的對比

3.1 框架設(shè)計

這里只是對框架設(shè)計本身做對比,而非功能層面。

Okhttp3

優(yōu)點

  • 首先設(shè)計上一個最大的直觀感受是無處不在的建造者模式。不僅包括OkhttpClient、Request、Response這種核心組件,也包括CacheControl、Headers這種具體功能都使用的建造者模式,配置非常靈活。
  • 通過各種Interceptor以責(zé)任鏈模式對Request和Response進(jìn)行監(jiān)聽、參數(shù)獲取或者調(diào)整某些參數(shù)信息。

缺點

  • Request個性配置支持不夠,例如:為不同Request設(shè)置不同的超時時間,因為超時是由OkHttpClient統(tǒng)一設(shè)置的,當(dāng)然也可以對個性化定制的Request去走OkHttpClient.newBuilder(),但是這里考慮到并發(fā)問題,并不是一個非常好的策略。
  • Request中的配置信息基本上異常都是throw一個Exception,這也就要求初始化Request的時候必須做好try catch,不然線上環(huán)境很容易crash,例如:.url()設(shè)置請求url,如果url格式出現(xiàn)問題,會在HttpUrl.parse里直接throw異常,交給調(diào)用處去處理,這時候如果調(diào)用處疏忽沒做處理,就會crash。
  • callBack返回的異常情況需要自行封裝,嚴(yán)格說也不能叫做是缺點吧,只是這點Volley做得更好。
Volley

優(yōu)點

  • Request拓展性非常強。
  • callBack異常有封裝VolleyError,同時也封裝了各種異常子類,這部分handleError功能寫起來比Okhttp3方便很多。


缺點
BasicNetwork 中的statuCode狀態(tài)碼處理有點籠統(tǒng),if (statusCode < 200 || statusCode > 299) 直接throw new IOException(); 另外還有volley 401 錯誤處理

3.2 重試
Okhttp3

通過攔截器RetryAndFollowUpInterceptor來做,因為攔截器是基于OkHttpClient來做的,如果是通用型重試還好,如果Request需要不同個性化重試策略,比如重試次數(shù)有差別,或者是否提供其他重試域名等等。這種情況下RetryAndFollowUpInterceptor就不太好做了,況且他還是final的,默認(rèn)策略不滿足還只能自己重新寫。

Volley

Request.setRetryPolicy來實現(xiàn),這對單一Request個性化定制來說比Okhttp3靈活,它接口為RetryPolicy,有個默認(rèn)實現(xiàn)DefaultRetryPolicy,支持參數(shù)的動態(tài)配置,當(dāng)然也可以自定義。相對比較靈活。

3.3 調(diào)度
Okhttp3

Dispatcher提供了在最大并發(fā)數(shù)64和每個主機最大請求數(shù)5的范圍內(nèi)的一個類似CacheThreadPool的線程池。這種類型線程池比較適用于:高并發(fā)但每個任務(wù)耗時較少的場景。

//準(zhǔn)備隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//執(zhí)行隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  //最大并發(fā)數(shù)。
  private int maxRequests = 64;
  //每個主機的最大請求數(shù)。
  private int maxRequestsPerHost = 5;

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

那么來看看調(diào)度邏輯:

  void enqueue(AsyncCall call) {
    synchronized (this) {
      //加入雙端準(zhǔn)備隊列
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }

將符合條件的calls從readyAsyncCalls移動到runningAsyncCalls里面,在executor service上執(zhí)行它們。

  private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      //循環(huán)判斷準(zhǔn)備請求隊列是否還有請求
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
        //如果執(zhí)行請求的隊列數(shù)量大于等于最大請求數(shù),中止循環(huán)
        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        //小于最大主機請求限制
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
        //從準(zhǔn)備隊列里移除自身。
        i.remove();
        executableCalls.add(asyncCall);
        //添加到執(zhí)行隊列中。
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
    //對新添加的任務(wù)嘗試進(jìn)行異步調(diào)用。
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }
    return isRunning;
  }

對應(yīng)的AsyncCall 就是一個Runnable。

這里可以在源碼的基礎(chǔ)上做兩個層面的自定義調(diào)度優(yōu)先級:

1)線程層面的

可以通過自定義Request傳入priority,在RealCall中獲取對應(yīng)Request的優(yōu)先級并設(shè)置給AsyncCall

  final class AsyncCall extends NamedRunnable {
    ...
        @Override
        public void run() {
            super.run();
            //originalRequest 就是傳進(jìn)來的Request
            Thread.currentThread().setPriority(originalRequest.priority());
        }
2)線程池Queue層面的
     executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                new PriorityBlockingQueue<Runnable>(60,new AsycCallComparator<Runnable>()), Util.threadFactory( "OkHttp Dispatcher", false));

這里使用PriorityBlockingQueue,它是一個具有優(yōu)先級的無界隊列,同時需要實現(xiàn)一個有序的比較器:

public class AsycCallComparator<T> implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        if ((o1 instanceof RealCall.AsyncCall) && (o2 instanceof RealCall.AsyncCall)) {
            RealCall.AsyncCall task1 = (RealCall.AsyncCall) o1;
            RealCall.AsyncCall task2 = (RealCall.AsyncCall) o2;
            int result = task2.priority() - task1.priority();
            return result;
        }
        return 0;
    }
}
Volley

Volley是1個Cache Thread,4個Network Thread。各自都有一個對應(yīng)的PriorityBlockingQueue維護(hù)請求隊列。

    /**
     * The cache triage queue.
     */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
            new PriorityBlockingQueue<>();

    /**
     * The queue of requests that are actually going out to the network.
     */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
            new PriorityBlockingQueue<>();
  /**
     * Number of network request dispatcher threads to start.
     */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        //mDispatchers.length 即為DEFAULT_NETWORK_THREAD_POOL_SIZE
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

對于優(yōu)先級調(diào)度來說:
Volley本身mCacheQueue 和 mNetworkQueue 就使用的PriorityBlockingQueue,而線程優(yōu)先級就更方便了,重構(gòu)一個RequestQueue的start方法,直接給NetworkDispatcher設(shè)置優(yōu)先級。

 public void start(int threadPriority) {
        stop(); // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
            try {
                networkDispatcher.setPriority(threadPriority);
            } catch (Exception e) {
                e.printStackTrace();
            }
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

最近完成了公司網(wǎng)絡(luò)框架組件的重構(gòu),新增了Okhttp支持,在保證不動業(yè)務(wù)層調(diào)用的情況下,調(diào)整了網(wǎng)絡(luò)庫整體架構(gòu),支持Okhttp和Volley動態(tài)切換,并且從其他Module的耦合中剝離出來,單獨做maven管理。從兩個框架的使用情況來看,常規(guī)請求都是50-100ms量級的速度,兩者不相上下。兩者對get 、post 、multipart post支持都還不錯。后續(xù)再進(jìn)一步看看表現(xiàn)差異以及存在的問題。

最后編輯于
?著作權(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ù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

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