Android每週一輪子:Volley

Volley

序言

2018年談Volley,能夠說是too yong, too simple了,對於網絡庫,如今使用最多的莫過於OkHttp了,接觸使用Volley應該仍是大二的時候了。以後也看過其源碼,可是在不久前面試的時候,被問到一個Volley庫的問題,就是Volley中請求的優先級是如何調度的,卻卡住了,當時對於源碼的閱讀大多隻是停留在對於其實現的流程和項目的結構上,而對於其具體的特性和其如何實現了這些特性卻沒有去了解,相比於功能實現的流程,特性的實現細節也是不可忽略的,甚至能夠說這纔是一個庫的精華之所在,同時對於該網絡庫的缺陷在於那裏,經過了解其優點和缺陷,咱們能夠更好的揚長避短,充分利用該庫。本着該原則,準備對於以前閱讀的代碼進行一個從新的回顧,暫定的計劃爲一週拆一個輪子。面試

將以其實現流程,特性實現和其缺陷做爲主要切入點,進行代碼分析。數組

Volley 基礎使用

final TextView mTextView = (TextView) findViewById(R.id.text);
...

// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // Display the first 500 characters of the response string.
        mTextView.setText("Response is: "+ response.substring(0,500));
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        mTextView.setText("That didn't work!");
    }
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
複製代碼
  • 建立RequestQueue 請求隊列。
  • 建立Request,Volley提供了String,JsonObject等類型,用戶可本身繼承Reqeust實現本身定義的返回結果類型。
  • 將請求添加到請求隊列中。

通過以上三步,咱們就完成了一次網絡請求,在註冊的監聽器的onResponse方法中咱們能夠拿到請求成功的返回結果和在onErrorResponse方法中獲得出錯的信息。緩存

Volley實現

Volley實現結構圖

  • 請求隊列的建立
private static RequestQueue newRequestQueue(Context context, Network network) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();
    return queue;
}
複製代碼
  • 請求添加到隊列中
public <T> Request<T> add(Request<T> request) {
    //請求添加到mCurrentRequests中
    request.setRequestQueue(this);
    synchronized (mCurrentRequests) {
        mCurrentRequests.add(request);
    }
    //設置請求的Sequence,後期用來比較請求的優先級
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");

    //若是請求不須要緩存,直接加入網絡請求隊列
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }
    //若是須要緩存加入到緩存隊列中
    mCacheQueue.add(request);
    return request;
 }
複製代碼
  • 調用隊列的開啓
public void start() {
    stop(); 
    //建立緩存Dispatcher,並啓動
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 根據線程池設置的數目,建立網絡請求Dispatcher,並啓動
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}
複製代碼

這裏首先會關掉以前的Dispatcher,而後從新建立並開啓Dispatcherbash

  • 建立並開啓CacheDispatcher
public void run() {
    mCache.initialize();
    while (true) {
             //取出請求
            final Request<?> request = mCacheQueue.take();
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }
            //判斷Cache未命中,這加入到網絡請求隊列中
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // Cache miss; send off to the network dispatcher.
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                continue;
            }
            //緩存過時了,拿到緩存以後,再將該請求放置到網絡請求隊列中
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
            //判斷是否須要加入到等待隊列,若是須要則不加入網絡請求隊列
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                continue;
            }
          //Cache 未過時,Cache命中,這將其包裝成NetworkResponse
            Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
          //若是緩存不須要更新,直接將結果拋回去,不然檢測其是否在等待請求隊列中,若是不在執行請求,不然直接拋回
          if (!entry.refreshNeeded()) {
                mDelivery.postResponse(request, response);
            } else {
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);
                response.intermediate = true;

                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
        
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Restore the interrupted status
                                Thread.currentThread().interrupt();
                            }
                        }
                    });
                } else {
                    mDelivery.postResponse(request, response);
                }
            }
    }
}

複製代碼

當緩存沒有命中的時候,須要發起網絡請求,這個時候,經過WaitingRequestManager來進行管理,其維護了一個Map來放置響應的請求,鍵爲請求CacheKey,值爲請求。若是Map中不包含該CacheKey,這將其加入,並將值置爲Null,若是有,則直接將其加入。第一次置爲Null的緣由是爲了防止在請求歸來時,在NetworkDispatcher中執行了一次數據的異步傳遞,在緩存請求隊列處理時再次被處理,經過這種方式也保證了對於同一個請求,只有可能被HttpStack執行一次,而每個請求設置的成功失敗回調都會被用到。網絡

private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
    String cacheKey = request.getCacheKey();
      //若是請求隊列包含該Cachekey,表示已經執行過網絡請求
    if (mWaitingRequests.containsKey(cacheKey)) {
        // There is already a request in flight. Queue up.
        List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
        if (stagedRequests == null) {
            stagedRequests = new ArrayList<Request<?>>();
        }
        request.addMarker("waiting-for-response");
        stagedRequests.add(request);
        mWaitingRequests.put(cacheKey, stagedRequests);
        if (VolleyLog.DEBUG) {
            VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
        }
        return true;
    } else {
        //將Value置爲null,由於其回調在NetworkDispatcher中被執行了
        mWaitingRequests.put(cacheKey, null);
        request.setNetworkRequestCompleteListener(this);
        if (VolleyLog.DEBUG) {
        return false;
    }
}
複製代碼

經過爲該Request設置setNetworkRequestCompleteListener,當網絡請求執行完成的時候,其onResponseReceived函數會被回調到。併發

public void onResponseReceived(Request<?> request, Response<?> response) {
    if (response.cacheEntry == null || response.cacheEntry.isExpired()) {
        onNoUsableResponseReceived(request);
        return;
    }
    String cacheKey = request.getCacheKey();
    List<Request<?>> waitingRequests;
    synchronized (this) {
        waitingRequests = mWaitingRequests.remove(cacheKey);
    }
    if (waitingRequests != null) {
        // 將等待的請求結果進行傳遞
        for (Request<?> waiting : waitingRequests) {
            mCacheDispatcher.mDelivery.postResponse(waiting, response);
        }
    }
}
複製代碼

這個時候,排隊的請求的回調則會被執行。less

  • 建立並開啓NetworkDispatcher
public void run() {
    while (true) {
      //取出請求
      Request<?> request = mQueue.take();
        //判斷請求是否須要取消
        if (request.isCanceled()) {
             request.finish("network-discard-cancelled");
             request.notifyListenerResponseNotUsable();
              continue;
         }
        //執行網絡請求
         NetworkResponse networkResponse = mNetwork.performRequest(request);
        //解析網絡請求結果
         Response<?> response = request.parseNetworkResponse(networkResponse);
        //判斷是否須要加入緩存
        if (request.shouldCache() && response.cacheEntry != null) {
             mCache.put(request.getCacheKey(), response.cacheEntry);
             request.addMarker("network-cache-written");
         }
        //經過Delivery將響應結果傳遞出去
        mDelivery.postResponse(request, response);
        request.notifyListenerResponseReceived(response);

  }
}
複製代碼
  • 請求處理到產生響應

Volley請求處理過程

Volley 特性和缺陷

特性異步

  • 自動調度網絡請求,支持多併發網絡請求
  • 透明的磁盤內存響應結果緩存
  • 支持請求優先級調整
  • 支持取消請求,並提供相應API
  • 高度可拓展
  • 網絡請求結果異步回調

缺陷ide

  • 不適合作大的網絡下載請求

接下來,針對Volley的特性和缺陷,分別展開,從源碼進行分析。函數

  1. 自動調度網絡請求,支持多併發網絡請求

在RequestQueue方法中,開啓了多個NetworkDispatcher,而每個Dispatcher都是一個線程。

NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
 mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
複製代碼

對於網絡請求經過一個優先級阻塞隊列存放,每個線程能夠從隊列中獲取請求,而後執行,最後將響應結果返回。Volley內部管理了這些線程,無需開發者關心。

  1. 透明的磁盤內存響應結果緩存

對於緩存,Volley支持用戶制定本身的緩存規則,經過實現Cache接口,實現本身的緩存。同時提供了一個默認的緩存實現DiskBasedCache。其是如何實現透明的磁盤內存響應的呢?

首先對於全部的請求,在判斷爲設置了緩存的,將會加入到緩存隊列中,而後從緩存中去取,若是緩存中有則返回,若是沒有或者過時則將其加入到網絡請求隊列中。

在CacheDispatcher中,咱們首先調用了Cache的初始化方法

mCache.initialize();
複製代碼

以後經過請求的CacheKey從Cache的get方法中獲取緩存。其內部實現是內存中維護了一個LinkedHashMap,以請求的CacheKey做爲Key,CacheHeader做爲值,對於每個請求內容經過文件的形式存放在磁盤文件中,初始化的時候,讀取緩存文件目錄,將其加載到內存中,查詢的時候,根據內存中的緩存進行判斷,從磁盤中加載數據。

  1. 優先級實現

Volley支持優先級的調整,經過一個PriorityblockingQueue隊列,進行調度,將請求加入進來,該隊列會根據其存放的Obejct的compare方法對其進行優先級的比較,來肯定其前後順序。

@Override
public int compareTo(Request<T> other) {
    Priority left = this.getPriority();
    Priority right = other.getPriority();

    // High-priority requests are "lesser" so they are sorted to the front.
    // Equal priorities are sorted by sequence number to provide FIFO ordering.
    return left == right ?
            this.mSequence - other.mSequence :
            right.ordinal() - left.ordinal();
}
複製代碼

首先根據請求設置的優先級,而後根據其Sequence值,這個值是在時間順序上遞增的。

  1. 任意的取消網絡請求

經過爲請求設置cacel字段,在請求執行的時候,對其進行判斷,來肯定是否爲取消了,而後再次執行。

  1. 高度可拓展

Volley支持對於Cache的拓展,HttpStack的拓展,也就是對於網絡請求具體執行類的拓展,這裏提供了HttpClient和HttpUrlConnection。支持對於網絡請求回調的自定義。支持網絡請求解析的自定義。用戶能夠根據本身的需求,進行響應的插樁拓展。

  1. 網絡請求結果異步回調
  • 建立調度器
new ExecutorDelivery(new Handler(Looper.getMainLooper()));
複製代碼
public ExecutorDelivery(final Handler handler) {
    // Make an Executor that just wraps the handler.
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}
複製代碼
  • 傳遞數據
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
複製代碼

調度器持有了主線程的Handler,經過Handler的post將咱們的數據透傳到主線程之中。經過這種方式從而實現異步回調。

  1. 不適合大數據量傳遞

在返回的結果,經過byte[]字段中,而後存放在內存之中,而後獲取相應的編碼方式,將其轉化爲字符串,這樣就致使當網絡請求數目增長,返回內容增長的時候,致使內存中的數據量增長,所以Volley不適合進行大數據量的傳遞,而實現小而頻繁的請求。

new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
複製代碼

這裏的responseContents爲一個byte類型數組。

總結

Volley的源碼實現比較簡單,代碼量也不是很大,比較容易閱讀和理解,因此Volley做爲成了新年以來拆的第一個輪子。拆輪子的過程能夠幫助咱們學習到功能的實現,同時也能夠從中學習到一些設計思想。接下來的計劃是每週拆一個輪子,多是Android也多是其它方面的。時間充裕,可能會找一些代碼量大的,時間少的話,可能就找一些簡單輪子來拆。拆分思路爲其基礎使用,實現原理梳理,特性和缺陷分析。經過這三方面來完全瞭解一個輪子的實現。

相關文章
相關標籤/搜索