Android每周一輪子:Volley

Volley

序言

2018年談Volley,可以說是too yong, too simple了,對于網(wǎng)絡庫,現(xiàn)在使用最多的莫過于OkHttp了,接觸使用Volley應該還是大二的時候了。之后也看過其源碼,但是在不久前面試的時候,被問到一個Volley庫的問題,就是Volley中請求的優(yōu)先級是如何調(diào)度的,卻卡住了,當時對于源碼的閱讀大多只是停留在對于其實現(xiàn)的流程和項目的結(jié)構(gòu)上,而對于其具體的特性和其如何實現(xiàn)了這些特性卻沒有去了解,相比于功能實現(xiàn)的流程,特性的實現(xiàn)細節(jié)也是不可忽略的,甚至可以說這才是一個庫的精華之所在,同時對于該網(wǎng)絡庫的缺陷在于那里,通過了解其優(yōu)勢和缺陷,我們可以更好的揚長避短,充分利用該庫。本著該原則,準備對于之前閱讀的代碼進行一個重新的回顧,暫定的計劃為一周拆一個輪子。

將以其實現(xiàn)流程,特性實現(xiàn)和其缺陷作為主要切入點,進行代碼分析。

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);
  • 創(chuàng)建RequestQueue 請求隊列。
  • 創(chuàng)建Request,Volley提供了String,JsonObject等類型,用戶可自己繼承Reqeust實現(xiàn)自己定義的返回結(jié)果類型。
  • 將請求添加到請求隊列中。

經(jīng)過以上三步,我們就完成了一次網(wǎng)絡請求,在注冊的監(jiān)聽器的onResponse方法中我們可以拿到請求成功的返回結(jié)果和在onErrorResponse方法中得到出錯的信息。

Volley實現(xiàn)

Volley實現(xiàn)結(jié)構(gòu)圖
  • 請求隊列的創(chuàng)建
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,后期用來比較請求的優(yōu)先級
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");

    //如果請求不需要緩存,直接加入網(wǎng)絡請求隊列
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }
    //如果需要緩存加入到緩存隊列中
    mCacheQueue.add(request);
    return request;
 }
  • 調(diào)用隊列的開啟
public void start() {
    stop(); 
    //創(chuàng)建緩存Dispatcher,并啟動
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 根據(jù)線程池設置的數(shù)目,創(chuàng)建網(wǎng)絡請求Dispatcher,并啟動
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

這里首先會關掉之前的Dispatcher,然后重新創(chuàng)建并開啟Dispatcher

  • 創(chuàng)建并開啟CacheDispatcher
public void run() {
    mCache.initialize();
    while (true) {
             //取出請求
            final Request<?> request = mCacheQueue.take();
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }
            //判斷Cache未命中,這加入到網(wǎng)絡請求隊列中
            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;
            }
            //緩存過期了,拿到緩存之后,再將該請求放置到網(wǎng)絡請求隊列中
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
            //判斷是否需要加入到等待隊列,如果需要則不加入網(wǎng)絡請求隊列
                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");
          //如果緩存不需要更新,直接將結(jié)果拋回去,否則檢測其是否在等待請求隊列中,如果不在執(zhí)行請求,否則直接拋回
          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);
                }
            }
    }
}

當緩存沒有命中的時候,需要發(fā)起網(wǎng)絡請求,這個時候,通過WaitingRequestManager來進行管理,其維護了一個Map來放置響應的請求,鍵為請求CacheKey,值為請求。如果Map中不包含該CacheKey,這將其加入,并將值置為Null,如果有,則直接將其加入。第一次置為Null的原因是為了防止在請求歸來時,在NetworkDispatcher中執(zhí)行了一次數(shù)據(jù)的異步傳遞,在緩存請求隊列處理時再次被處理,通過這種方式也保證了對于同一個請求,只有可能被HttpStack執(zhí)行一次,而每一個請求設置的成功失敗回調(diào)都會被用到。

private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
    String cacheKey = request.getCacheKey();
      //如果請求隊列包含該Cachekey,表示已經(jīng)執(zhí)行過網(wǎng)絡請求
    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,因為其回調(diào)在NetworkDispatcher中被執(zhí)行了
        mWaitingRequests.put(cacheKey, null);
        request.setNetworkRequestCompleteListener(this);
        if (VolleyLog.DEBUG) {
        return false;
    }
}

通過為該Request設置setNetworkRequestCompleteListener,當網(wǎng)絡請求執(zhí)行完成的時候,其onResponseReceived函數(shù)會被回調(diào)到。

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) {
        // 將等待的請求結(jié)果進行傳遞
        for (Request<?> waiting : waitingRequests) {
            mCacheDispatcher.mDelivery.postResponse(waiting, response);
        }
    }
}

這個時候,排隊的請求的回調(diào)則會被執(zhí)行。

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

  }
}
  • 請求處理到產(chǎn)生響應
Volley請求處理過程

Volley 特性和缺陷

特性

  • 自動調(diào)度網(wǎng)絡請求,支持多并發(fā)網(wǎng)絡請求
  • 透明的磁盤內(nèi)存響應結(jié)果緩存
  • 支持請求優(yōu)先級調(diào)整
  • 支持取消請求,并提供相應API
  • 高度可拓展
  • 網(wǎng)絡請求結(jié)果異步回調(diào)

缺陷

  • 不適合做大的網(wǎng)絡下載請求

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

  1. 自動調(diào)度網(wǎng)絡請求,支持多并發(fā)網(wǎng)絡請求

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

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

對于網(wǎng)絡請求通過一個優(yōu)先級阻塞隊列存放,每一個線程可以從隊列中獲取請求,然后執(zhí)行,最后將響應結(jié)果返回。Volley內(nèi)部管理了這些線程,無需開發(fā)者關心。

  1. 透明的磁盤內(nèi)存響應結(jié)果緩存

對于緩存,Volley支持用戶制定自己的緩存規(guī)則,通過實現(xiàn)Cache接口,實現(xiàn)自己的緩存。同時提供了一個默認的緩存實現(xiàn)DiskBasedCache。其是如何實現(xiàn)透明的磁盤內(nèi)存響應的呢?

首先對于所有的請求,在判斷為設置了緩存的,將會加入到緩存隊列中,然后從緩存中去取,如果緩存中有則返回,如果沒有或者過期則將其加入到網(wǎng)絡請求隊列中。

在CacheDispatcher中,我們首先調(diào)用了Cache的初始化方法

mCache.initialize();

之后通過請求的CacheKey從Cache的get方法中獲取緩存。其內(nèi)部實現(xiàn)是內(nèi)存中維護了一個LinkedHashMap,以請求的CacheKey作為Key,CacheHeader作為值,對于每一個請求內(nèi)容通過文件的形式存放在磁盤文件中,初始化的時候,讀取緩存文件目錄,將其加載到內(nèi)存中,查詢的時候,根據(jù)內(nèi)存中的緩存進行判斷,從磁盤中加載數(shù)據(jù)。

  1. 優(yōu)先級實現(xiàn)

Volley支持優(yōu)先級的調(diào)整,通過一個PriorityblockingQueue隊列,進行調(diào)度,將請求加入進來,該隊列會根據(jù)其存放的Obejct的compare方法對其進行優(yōu)先級的比較,來確定其先后順序。

@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();
}

首先根據(jù)請求設置的優(yōu)先級,然后根據(jù)其Sequence值,這個值是在時間順序上遞增的。

  1. 任意的取消網(wǎng)絡請求

通過為請求設置cacel字段,在請求執(zhí)行的時候,對其進行判斷,來確定是否為取消了,然后再次執(zhí)行。

  1. 高度可拓展

Volley支持對于Cache的拓展,HttpStack的拓展,也就是對于網(wǎng)絡請求具體執(zhí)行類的拓展,這里提供了HttpClient和HttpUrlConnection。支持對于網(wǎng)絡請求回調(diào)的自定義。支持網(wǎng)絡請求解析的自定義。用戶可以根據(jù)自己的需求,進行響應的插樁拓展。

  1. 網(wǎng)絡請求結(jié)果異步回調(diào)
  • 創(chuàng)建調(diào)度器
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);
        }
    };
}
  • 傳遞數(shù)據(jù)
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

調(diào)度器持有了主線程的Handler,通過Handler的post將我們的數(shù)據(jù)透傳到主線程之中。通過這種方式從而實現(xiàn)異步回調(diào)。

  1. 不適合大數(shù)據(jù)量傳遞

在返回的結(jié)果,通過byte[]字段中,然后存放在內(nèi)存之中,然后獲取相應的編碼方式,將其轉(zhuǎn)化為字符串,這樣就導致當網(wǎng)絡請求數(shù)目增加,返回內(nèi)容增加的時候,導致內(nèi)存中的數(shù)據(jù)量增加,因此Volley不適合進行大數(shù)據(jù)量的傳遞,而實現(xiàn)小而頻繁的請求。

new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);

這里的responseContents為一個byte類型數(shù)組。

總結(jié)

Volley的源碼實現(xiàn)比較簡單,代碼量也不是很大,比較容易閱讀和理解,所以Volley作為成了新年以來拆的第一個輪子。拆輪子的過程可以幫助我們學習到功能的實現(xiàn),同時也可以從中學習到一些設計思想。接下來的計劃是每周拆一個輪子,可能是Android也可能是其它方面的。時間充裕,可能會找一些代碼量大的,時間少的話,可能就找一些簡單輪子來拆。拆分思路為其基礎使用,實現(xià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ā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

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