1. 程式人生 > >Android-volley淺談-從原始碼去了解它為什麼是Google推薦的網路請求框架

Android-volley淺談-從原始碼去了解它為什麼是Google推薦的網路請求框架

今天想總結的是Volley這個網路請求框架,雖然volley論火爆程度比不上okhttp和retrofit 這兩個,並且在日常使用的過程中可能很少有人能去深究為什麼volley是Google 所推薦的網路框架,不管從使用還是從原始碼去理解,我覺得volley都是一款值得去深究 去學習的框架

寫在下面之前可以先看下我的總結,因為很多東西和大家講的差不多,主要更是對volley的一個總結-----如果嫌太長可以直接看這一段總結(雖然好像也有點長哦,不過理一理還是很容易看懂的~~)

volley是一個輕量型的框架 基本滿足了 我們日常中的網路請求 適合於資料小 頻繁請求的場景 因為volley是面向介面開發的 所以它的可擴充套件性強 是基於httpurlconnection 開發的 當每次請求進來的時候 會加入到一個requestqueue中 這個queue內部維護了兩個佇列 一個是快取佇列 多個是網路請求佇列(預設是4個)    然後建立完佇列之後會建立一個請求(request,有Stringrequest  jsonarrayrequest  imagerequest 都是繼承於request  所以如果你要自定義網路請求那麼繼承於request就可以了,重寫裡面的兩個返回方法 一個是deliverResponse方法 主要作用是實現成功回撥  還有一個是parseNetworkResponse() 來處理響應資料 )至此一個完整的request過程就結束了 然後就開始新增請求進隊列了 這個過程首先判斷是否允許被快取(預設是可以被快取的) 如果不允許那就新增到網路快取佇列,如果可以再進一步判斷是否有相同請求在執行 如果有那就直接等待 如果沒有那就放入快取佇列中 然後完成整個新增過程  下面我們就詳細分析一下快取執行緒的過程 裡面一開始有個while迴圈 表示它一直在等待著請求的到來 接著首先判斷這個快取 有沒有 如果沒有那就直接新增到網路快取中去 如果有判斷是否過期 過期了同樣新增到網路快取中去  如果都正常 那就直接打包資料 解析為response 直接post到主執行緒 處理  然後我們在分析一下這個網路執行緒 內部封裝了httpurlconnection  將處理結果回撥到上面的方法中進行處理  我們通過分析知道 快取執行緒和網路請求執行緒都通過一個ExecutorDelivery#postResponse()方法將結果返回到主執行緒中處理 其實這個方法的實現很簡單 就是裡面獲取到了主執行緒的handler  才能將處理結果返回給主執行緒的

優點

縱觀目前的三大網路請求框架 okhttp retrofit volley 各有各的優勢,下面是我從網上找到的一些對比 ,可能篇幅優點長,其實也沒必要去深究,只需要看最後一個我標紅的地方就知道volley的優點了。

1.OkHttp
    Android 開發中是可以直接使用現成的api進行網路請求的,就是使用HttpClient、HttpUrlConnection 進行操作,目前HttpClient 已經被廢棄,而 android-async-http 是基於HttpClient的,可能也是因為這個原因作者放棄維護。 而OkHttp是Square公司開源的針對Java和Android程式,封裝的一個高效能http請求庫,它的職責跟HttpUrlConnection 是一樣的,支援 spdy、http 2.0、websocket ,支援同步、非同步,而且 OkHttp 又封裝了執行緒池,封裝了資料轉換,封裝了引數使用、錯誤處理等,api使用起來更加方便。可以把它理解成是一個封裝之後的類似HttpUrlConnection的東西,但是在使用的時候仍然需要自己再做一層封裝,這樣才能像使用一個框架一樣更加順手。

2.Retrofit
   Retrofit是Square公司出品的預設基於OkHttp封裝的一套RESTful網路請求框架,RESTful是目前流行的一套api設計的風格, 並不是標準。Retrofit的封裝可以說是很強大,裡面涉及到一堆的設計模式,可以通過註解直接配置請求,可以使用不同的http客戶端,雖然預設是用http ,可以使用不同Json Converter 來序列化資料,同時提供對RxJava的支援,使用Retrofit + OkHttp + RxJava + Dagger2 可以說是目前比較潮的一套框架,但是需要有比較高的門檻。 

3.Volley
   Volley是Google官方出的一套小而巧的非同步請求庫,該框架封裝的擴充套件性很強,支援HttpClient、HttpUrlConnection, 甚至支援OkHttp,而且Volley裡面也封裝了ImageLoader,所以如果你願意你甚至不需要使用圖片載入框架,不過這塊功能沒有一些專門的圖片載入框架強大,對於簡單的需求可以使用,稍複雜點的需求還是需要用到專門的圖片載入框架。Volley也有缺陷,比如不支援post大資料,所以不適合上傳檔案。不過Volley設計的初衷本身也就是為頻繁的、資料量小的網路請求而生

其實在日常開發中,選擇以上三中框架都可以作為專案的主要網路請求框架都是可行的,根據各自的使用特點去封裝使用,但上面的對比圈紅的地方也就是volley的最強優點,短小精悍,高頻繁,資料量小。下面我們就是通過這個特點從原始碼角度去理解它。

這個圖可能每個人都見過吧  2333333  再形象不過了

由此可見volley的特點

通訊更快,更簡單(資料量不大,但網路通訊頻繁)

Get、Post網路請求及網路影象的高效率非同步處理

排序(可以通過網路請求的優先順序進行處理)

網路請求的快取

多級別取消請求

volley的使用

volley自帶三種返回方式

 Volley的GET、Post

       StringRequest:主要使用在對請求資料的返回型別不確定的情況下,StringRequest涵蓋了JsonObjectRequest 和JsonArrayRequest。

       JsonObjectRequest:當確定請求資料的返回型別為JsonObject時使用。

       JsonArrayRequest:當確定請求資料的返回型別為JsonArray時使用。

下面是volley的基本使用方式

      首先我們需要建立一個RequestQueue requestQueue,然後構建一個自己所需要的XXRequestreq,之後通過requestQueue.add(req);將請求新增至請求佇列

public class MyApplication extends Application {
    public static RequestQueue queues;

    @Override
    public void onCreate() {
        super.onCreate();
        queues = Volley.newRequestQueue(getApplicationContext());
    }

    public static RequestQueue getHttpQueues(){
        return queues;
    }
}


 ------------------------------------------------------------

 private void volley_Get(){
        String url = "";
        StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                //success 成功操作
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                 //fail  失敗操作
            }
        });

        request.setTag("Get");
        MyApplication.getHttpQueues().add(request);
    }

---------------------------------------------------------------------
public void vollye_Post(){
        String url = "";
        StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
               //-----------------
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                 //-----------------------
            }
        }){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String,String> hashMap = new HashMap<>();
                hashMap.put("tel", "......");
                return hashMap;
            }
        };
        request.setTag("Post");
        MyApplication.getHttpQueues().add(request);
    }

      Post請求的方法使用與Get方法相似,但它多了一個方法,getParams()這個方法用Map來獲取你所需要Post的資料, 返回資料為Json格式資料時,即將你所要傳入的資料放到引數中即可,不用使用getParams()方法.

Volley的生命週期和Activity的生命週期

    程式碼很簡單,其實就是在Activity銷燬時候,取消對應的網路請求,避免網路請求在後臺浪費資源,在onStop()方法中通過之前設定的Tag取消網路請求:

@Override
protected void onStop() {
    super.onStop();
    //通過Tag標籤取消請求佇列中對應的全部請求
    MyApplication.getHttpQueues().cancelAll(tag);
}

volley還有個很少有人使用的功能 那就是 使用ImageRequest載入網路圖片

LruCache:這個類非常適合用來快取圖片,它的主要演算法原理是把最近使用的物件用強引用儲存在 LinkedHashMap 中,並且把最近最少使用的物件在快取值達到預設定值之前從記憶體中移除。

ImageCache:Volley框架內部自己處理了DiskBasedCache硬碟快取,但是沒有處理LruCache記憶體快取,因為一般在處理圖片的問題上才更多的用到LruCache快取,但是它提供了一個ImageCache介面供我們自己實現,該介面預設需要實現兩個方法:getBitmap(String key)和putBitmap(String key, Bitmap bitmap)

ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap bitmap) {
                imageView.setImageBitmap(bitmap);
            }
        }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                imageView.setBackgroundResource(Color.BLACK);
            }
        });
MyApplication.getHttpQueues().add(request);

上面只是簡單的介紹了一下volley的使用方式,一般使用過程還需要進一步的封裝,各位看客可以去github上去找一找封裝好的庫,這不是今天的重頭戲,今天我們主要通過原始碼去理解volley 

原始碼

上原始碼前  再去偷一張圖  先看下大致的載入過程

建立一個請求

RequestQueue queue = Volley.newRequestQueue(context)

Volley.newRequestQueue(this) 來建立一個請求佇列。Volley 中提供了兩種建立請求佇列的方法,newRequestQueue(Context context,HttpStack stack)newRequestQueue(Context context)

這行程式碼就是執行了一個new出一個佇列的操作,

public static RequestQueue newRequestQueue(Context context) {
//  在此方法內部會呼叫另一個 newRequestQueue 方法,第二個引數為 null 代表使用預設的 HttpStack 實現
    return newRequestQueue(context, null);
}
/**
   * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
   * You may set a maximum size of the disk cache in bytes.
   *
   * @param context A {@link Context} to use for creating the cache dir.
   * @param stack An {@link HttpStack} to use for the network, or null for default.
   * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
   * @return A started {@link RequestQueue} instance.
   */
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

  
    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
      
    Network network = new BasicNetwork(stack);
    RequestQueue queue;
    if (maxDiskCacheBytes <= -1){
       // No maximum size specified
     
       queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    }
    else {
       // Disk cache size specified
       queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
    }
     
    queue.start();
    return queue;
}

有三個引數,我們使用了只有一個引數context的方法,這個是對應的過載方法,最終呼叫的是三個引數的方法,context是上下文;stack代表需要使用的網路連線請求類,這個一般不用設定,方法內部會根據當前系統的版本號呼叫不同的網路連線請求類(HttpUrlConnection和HttpClient);最後一個引數是快取的大小。接著我們看方法內部,這裡先建立了快取檔案,然後根據不同的系統版本號例項化不同的請求類,用stack引用這個類。接著又例項化了一個BasicNetwork,這個類在下面會說到。然後到了實際例項化請求佇列的地方:new RequestQueue(),這裡接收兩個引數,分別是快取和network(BasicNetwork)。例項化RequestQueue後,呼叫了start()方法,最後返回這個RequestQueue。

Volley 呼叫 getCacheDir() 方法來獲取快取目錄,Volley 中的快取檔案會儲存在 /data/data/packagename/cache 目錄下面,並不是儲存在 SD 卡中的。

Volley 當中有對 HttpStack 的預設實現,HttpStack 是真正用來執行請求的介面 ,根據版本號的不同,例項化不同的物件,在 Android2.3 版本之前採用基於 HttpClient 實現的 HttpClientStack 物件,不然則採用基於 HttpUrlConnection 實現的 HUrlStack

之後我們通過 HttpStack 構建了一個 Network 物件,它會呼叫 HttpStack#performRequest() 方法來執行請求,並且將請求的結果轉化成 NetworkResponse 物件,NetworkResponse 類封裝了響應的響應碼響應體響應頭等資料。

接著我們會將之前構建的快取目錄以及網路物件傳入 RequestQueue(Cache cache, Network network) 的建構函式中,構造一個 RequestQueue 物件,然後呼叫佇列的 start()方法來啟動佇列,其實就是啟動佇列中的兩種執行緒:

/**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     * @param delivery A ResponseDelivery interface for posting responses and errors
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        //例項化網路請求陣列,陣列大小預設是4
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                //ResponseDelivery是一個介面,實現類是ExecutorDelivery
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

//啟動佇列中所有的排程執行緒.
public void start() {
    stop();  // 確保停止所有當前正在執行的排程執行緒
    // 建立快取排程執行緒,並啟動它,用來處理快取佇列中的請求
    mCacheDispatcher = new CacheDispatcher(mCacheQueue,mNetworkQueue,mCache,mDelivery);
    mCacheDispatcher.start();
    
    // 建立一組網路排程執行緒,並啟動它們,用來處理網路佇列中的請求,預設執行緒數量為4,也可以通過RequestQueue的建構函式指定執行緒數量。
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue,mNetwork,mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

start() 方法中,主要是啟動了兩種執行緒分別是 CacheDispatcherNetworkDispatcher,它們都是執行緒類,顧名思義 CacheDispatcher 執行緒會處理快取佇列中請求,NetworkDispatcher 處理網路佇列中的請求,由此可見在我們呼叫 Volley 的公開方法建立請求佇列的時候,其實就是開啟了兩種執行緒在等待著處理我們新增的請求。

新增請求 add(Request)

看下手畫的流程圖  有點醜 

建立了 RequestQueue ,現在我們只需要構建一個 Request 物件,add(Request<T> request) 方法

public <T> Request<T> add(Request<T> request) {
    // 將請求加入到當前請求隊列當中,毋庸置疑的我們需要將所有的請求集合在一個佇列中,方便我們做統一操作,例如:取消單個請求或者取消具有相同標記的請求...
    request.setRequestQueue(this);
    synchronized (mCurrentRequests) {
        mCurrentRequests.add(request);
    }

    // 給請求設定順序.
    request.setSequence(getSequenceNumber());
    request.addMarker("add-to-queue");

    // 如果請求是不能夠被快取的,直接將該請求加入網路佇列中.
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }

    // 如果有相同的請求正在被處理,就將請求加入對應請求的等待佇列中去.等到相同的正在執行的請求處理完畢的時候會呼叫 finish()方法,然後將這些等待佇列中的請求全部加入快取佇列中去,讓快取執行緒來處理
    synchronized (mWaitingRequests) {
        String cacheKey = request.getCacheKey();
        if (mWaitingRequests.containsKey(cacheKey)) {
            // 有相同請求在處理,加入等待佇列.
            Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            if (stagedRequests == null) {
                stagedRequests = new LinkedList<>();
            }
            stagedRequests.add(request);
            mWaitingRequests.put(cacheKey, stagedRequests);
            if (VolleyLog.DEBUG) {
                VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);}
        } else {
            // 向mWaitingRequests中插入一個當前請求的空佇列,表明當前請求正在被處理
            mWaitingRequests.put(cacheKey, null);
            mCacheQueue.add(request);
        }
        return request;
    }
}

上面手畫的就是執行流程

處理請求快取/網路

這次流程圖不手畫了,從網上偷了一張  2333333

先看下快取請求

public CacheDispatcher(
    BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
    Cache cache, ResponseDelivery delivery) {
    mCacheQueue = cacheQueue;
    mNetworkQueue = networkQueue;
    mCache = cache;
    mDelivery = delivery;
}

 


這是建構函式,可以看見該物件內部持有快取佇列網路佇列快取物件響應投遞物件的引用。

@Override
public void run() {
    if (DEBUG) VolleyLog.v("start new dispatcher");
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // 初始化快取,將快取目錄下的所有快取檔案的摘要資訊載入到記憶體中.
    mCache.initialize();

    //無線迴圈,意味著執行緒啟動之後會一直執行
    while (true) {
        try {
            // 從快取佇列中取出一個請求,如果沒有請求則一直等待
            final Request<?> request = mCacheQueue.take();
            request.addMarker("cache-queue-take");

            // 如果當前請求已經取消,那就停止處理它
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }
            // 嘗試取出快取實體物件
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // 沒有快取,將當前請求加入網路請求佇列,讓NetworkDispatcher進行處理.
                mNetworkQueue.put(request);
                continue;
            }

            // 如果快取實體過期,任然將當前請求加入網路請求佇列,讓NetworkDispatcher進行處理.
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }

            // 將快取實體解析成NetworkResponse物件.
            request.addMarker("cache-hit");
            Response<?> response = request.parseNetworkResponse(
            new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
            
            if (!entry.refreshNeeded()) {
                // 快取依舊新鮮,投遞響應.
                mDelivery.postResponse(request, response);
            } else {
                //快取已經不新鮮了,我們可以進行響應投遞,然後將請求加入網路佇列中去,進行新鮮度驗證,如果響應碼為 304,代表快取新鮮可以繼續使用,不用重新整理響應結果
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);

                // 標記當前響應為中間響應,如果經過伺服器驗證快取不新鮮了,那麼隨後將有第二條響應到來.這意味著當前請求並沒有完成,只是暫時顯示快取的資料,等到伺服器驗證快取的新鮮度之後才會將請求標記為完成
                response.intermediate = true;

                // 將響應投遞給使用者,然後加入網路請求佇列中去.
                mDelivery.postResponse(request, response, new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mNetworkQueue.put(request);
                        } catch (InterruptedException e) {
                            // Not much we can do about this.
                        }
                    }
                });
            }

        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
        }
    }
}

 

再看下網路佇列

public NetworkDispatcher(BlockingQueue<Request<?>> queue,
    Network network, Cache cache,ResponseDelivery delivery) {
    mQueue = queue;
    mNetwork = network;
    mCache = cache;
    mDelivery = delivery;
}

@Override
    public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        Request<?> request;
        try {
            // 從網路佇列中取出一個請求,沒有請求則一直等待.
            request = mQueue.take();
        } catch (InterruptedException e) {
        // We may have been interrupted because it was time to quit.
            if (mQuit) {
            return;
            }
            continue;
        }

        try {
            request.addMarker("network-queue-take");

            // 如果請求被取消的話,就結束當前請求,不再執行
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }
            addTrafficStatsTag(request);

            // 執行網路請求.
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");
            // 如果響應碼為304,並且我們已經傳遞了一次響應,不需要再傳遞一次驗證的響應,意味著本次請求處理完成。也就是說該請求的快取是新鮮的,我們直接使用就可以了。
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // 在工作執行緒中想響應資料解析成我們需要的Response物件,之所以在工作執行緒進行資料解析,是為了避免一些耗時操作造成主執行緒的卡頓.
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");
            // 如果允許,則將響應資料寫入快取,這裡的快取是需要伺服器支援的,這點我們接下來再說
            // TODO: Only update cache metadata instead of entire record for 304s.
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // 傳遞響應資料.
            request.markDelivered();
            mDelivery.postResponse(request, response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
        } catch (Exception e) {
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
        }
    }
}

private void processRequest() throws InterruptedException {
        // Take a request from the queue.
        Request<?> request = mQueue.take();

        long startTimeMs = SystemClock.elapsedRealtime();
        try {
            request.addMarker("network-queue-take");

            // If the request was cancelled already, do not perform the
            // network request.
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }

            addTrafficStatsTag(request);

            // Perform the network request.
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

            // If the server returned 304 AND we delivered a response already,
            // we're done -- don't deliver a second identical response.
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // Parse the response here on the worker thread.
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // Write to cache if applicable.
            // TODO: Only update cache metadata instead of entire record for 304s.
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // Post the response back.
            request.markDelivered();
            mDelivery.postResponse(request, response);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        } catch (Exception e) {
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        }
    }


網路排程執行緒也是不斷地從佇列中取出請求並且判斷該請求是否被取消了。如果該請求沒有被取消,就去請求網路,並把網路請求的資料回調回主執行緒。請求網路時呼叫Network的performRequest()方法

mDelivery.postResponse(request, response);


@Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

 

ResponseDeliveryRunnable裡面做了什麼?


@SuppressWarnings("unchecked")
        @Override
        public void run() {
            // NOTE: If cancel() is called off the thread that we're currently running in (by
            // default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
            // won't be called, since it may be canceled after we check isCanceled() but before we
            // deliver the response. Apps concerned about this guarantee must either call cancel()
            // from the same thread or implement their own guarantee about not invoking their
            // listener after cancel() has been called.

            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
 

上面程式碼可以看到如果響應成功將會執行Request的deliverResponse方法,並把響應結果傳進去,如果響應失敗,就執行deliverError方法,並把響應失敗的物件傳進去。接著我們就看看Request的deliverResponse都幹了什麼。Request的子類有很多,這裡就拿StringRequest來做參考。

@Override
    protected void deliverResponse(String response) {
        Response.Listener<String> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {
            listener.onResponse(response);
        }
    }

 錯誤的回撥在父類Request中可以找到

public void deliverError(VolleyError error) {
        Response.ErrorListener listener;
        synchronized (mLock) {
            listener = mErrorListener;
        }
        if (listener != null) {
            listener.onErrorResponse(error);
        }
    }
 

拿到響應結果之後,如果請求成功則回撥onResponse
,如果請求失敗則回撥onErrorResponse,整個流程就是這樣了。

總結

(1)Volley是如何把請求的資料回調回主執行緒中的?

使用Handler.postRunnable(Runnable)方法回調回主執行緒中進行處理,ExecutorDelivery的構造方法中可以看到這段程式碼,如下所示

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

 

總結就是持有主執行緒的handler

(2)Volley開啟了幾個後臺執行緒?

總共開啟了5個執行緒:1個快取排程執行緒和4個網路排程執行緒,並且執行緒的優先順序為10,即後臺執行緒。Volley其實並沒有開啟執行緒池去維護執行緒,而是硬性地開了5個執行緒

(3)為什麼說volley適合多頻網路請求

ByteArrayPool 
ByteArrayPool產生背景

根據類名,知道這是一個位元組陣列快取池。沒錯,就是用來快取 網路請求獲得的資料。 
當網路請求得到返回資料以後,我們需要在記憶體開闢出一塊區域來存放我們得到的網路資料,不論是json還是圖片,都會存在於記憶體的某一塊區域,然後拿到UI顯示,然而客戶端請求是相當頻繁的操作,想一下我們平時使用知乎等一些客戶端,幾乎每一個操作都要進行網路請求(雖然知乎大部分是WebView)。那麼問題來了:這麼頻繁的資料請求,獲得資料以後我們先要在堆記憶體開闢儲存空間,然後顯示,等到時機成熟,GC回收這塊區域,如此往復,那麼GC的負擔就相當的重,然而Android客戶端處理能力有限,頻繁GC對客戶端的效能有直接影響。我們能不能減少GC和記憶體的分配呢?我想這個問題就是這個類的產生背景。 
實現原理(怎麼實現快取從而減少GC)

在ByteArrayPool中維護了兩個List集合。 
屬性名 作用 型別 
mBuffersByLastUse 按照最近使用對byte[]排序 LinkedList 
mBuffersBySize 按照byte[]大小對byte[]排序 ArrayList

通過上述兩個屬性的作用可以分析出它們是ByteArrayPool類對byte[]的管理。 
從緩衝區取空間

當請求資料返回以後,我們不是急於在記憶體開闢空間,而是從ByteArrayPool中取出一塊已經分配的記憶體區域。此時會呼叫ByteArrayPool的getBuf(int)方法,來得到一塊引數大小的區域

方法首先檢查 要插入的資料大小有沒有超出邊界,如果沒有,利用二分法找到插入位置,將資料插入到上述的兩個集合,完成排序。然後更新緩衝池的大小,以方便從緩衝區中取儲存空間。 

ByteArrayPool利用getBuf和returnBuf以及mBuffersByLastUse和mBuffersBySize完成位元組陣列的快取。當需要使記憶體區域的時候,先從已經分配的區域中獲得以減少記憶體分配次數。當空間用完以後,在將資料返回給此緩衝區。這樣,就會減少記憶體區域堆記憶體的波動和減少GC的回收,讓CPU把更多的效能留給頁面的渲染,提高效能。

總結就是記憶體做了一層位元組快取

(4)全域性共享RequestQueue

RequestQueue沒有必要每個Activity裡面都建立,全域性保有一個即可。這時候自然想到使用Application了。我們可以在Application裡面建立RequestQueue,並向外暴露get方法。