Volley 源碼分析

我的博客: Volley 源碼分析

Volley 的使用流程分析

官網(wǎng)示例

  1. 創(chuàng)建一個(gè)請求隊(duì)列 RequestQueue,并啟動隊(duì)列
  2. 創(chuàng)建一個(gè)請求 Request 添加到請求隊(duì)列中

創(chuàng)建 RequestQueue 對象

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

// 實(shí)例化一個(gè)請求隊(duì)列
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";

// 創(chuàng)建一個(gè)期待類型為字符串類型的請求
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!");
    }
});
// 將請求添加到請求隊(duì)列中
queue.add(stringRequest);

上面代碼片段的第5行,我們調(diào)用 Volley.newRequestQueue(this) 來創(chuàng)建一個(gè)請求隊(duì)列。Volley 中提供了兩種創(chuàng)建請求隊(duì)列的方法,newRequestQueue(Context context,HttpStack stack)newRequestQueue(Context context)

public static RequestQueue newRequestQueue(Context context) {
//  在此方法內(nèi)部會調(diào)用另一個(gè) newRequestQueue 方法,第二個(gè)參數(shù)為 null 代表使用默認(rèn)的 HttpStack 實(shí)現(xiàn)
    return newRequestQueue(context, null);
}
 public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    // 緩存文件目錄 data/data/packagename/cache/volley
    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();//基于HttpClient
        } else {
            //基于HttpUrlConnection
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    //利用HttpStack創(chuàng)建一個(gè)Network對象
    Network network = new BasicNetwork(stack);

    //創(chuàng)建一個(gè)RequestQueue對象,在構(gòu)造函數(shù)中傳入緩存對象,網(wǎng)絡(luò)對象
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    //啟動隊(duì)列
    queue.start();

    return queue;
}

在上面代碼片段中的第2行可以看見,Volley 調(diào)用 getCacheDir() 方法來獲取緩存目錄,Volley 中的緩存文件會存儲在 /data/data/packagename/cache 目錄下面,并不是存儲在 SD 卡中的。

從12~18的代碼可以看見,Volley 當(dāng)中有對 HttpStack 的默認(rèn)實(shí)現(xiàn),HttpStack 是真正用來執(zhí)行請求的接口 ,根據(jù)版本號的不同,實(shí)例化不同的對象,在 Android2.3 版本之前采用基于 HttpClient 實(shí)現(xiàn)的 HttpClientStack 對象,不然則采用基于 HttpUrlConnection 實(shí)現(xiàn)的 HUrlStack

之后我們通過 HttpStack 構(gòu)建了一個(gè) Network 對象,它會調(diào)用 HttpStack#performRequest() 方法來執(zhí)行請求,并且將請求的結(jié)果轉(zhuǎn)化成 NetworkResponse 對象,NetworkResponse 類封裝了響應(yīng)的響應(yīng)碼,響應(yīng)體,響應(yīng)頭等數(shù)據(jù)。

接著我們會將之前構(gòu)建的緩存目錄以及網(wǎng)絡(luò)對象傳入 RequestQueue(Cache cache, Network network) 的構(gòu)造函數(shù)中,構(gòu)造一個(gè) RequestQueue 對象,然后調(diào)用隊(duì)列的 start()方法來啟動隊(duì)列,其實(shí)就是啟動隊(duì)列中的兩種線程:


//啟動隊(duì)列中所有的調(diào)度線程.
public void start() {
    stop();  // 確保停止所有當(dāng)前正在運(yùn)行的調(diào)度線程
    // 創(chuàng)建緩存調(diào)度線程,并啟動它,用來處理緩存隊(duì)列中的請求
    mCacheDispatcher = new CacheDispatcher(mCacheQueue,mNetworkQueue,mCache,mDelivery);
    mCacheDispatcher.start();
    
    // 創(chuàng)建一組網(wǎng)絡(luò)調(diào)度線程,并啟動它們,用來處理網(wǎng)絡(luò)隊(duì)列中的請求,默認(rèn)線程數(shù)量為4,也可以通過RequestQueue的構(gòu)造函數(shù)指定線程數(shù)量。
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue,mNetwork,mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

start() 方法中,主要是啟動了兩種線程分別是 CacheDispatcherNetworkDispatcher,它們都是線程類,顧名思義 CacheDispatcher 線程會處理緩存隊(duì)列中請求,NetworkDispatcher 處理網(wǎng)絡(luò)隊(duì)列中的請求,由此可見在我們調(diào)用 Volley 的公開方法創(chuàng)建請求隊(duì)列的時(shí)候,其實(shí)就是開啟了兩種線程在等待著處理我們添加的請求。

添加請求 add(Request)

之前我們已經(jīng)創(chuàng)建了 RequestQueue 對象,現(xiàn)在我們只需要構(gòu)建一個(gè) Request 對象,并將它加入到請求隊(duì)列中即可。下面我們來看看 add(Request<T> request) 方法:

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

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

    // 如果請求是不能夠被緩存的,直接將該請求加入網(wǎng)絡(luò)隊(duì)列中.
    if (!request.shouldCache()) {
        mNetworkQueue.add(request);
        return request;
    }

    // 如果有相同的請求正在被處理,就將請求加入對應(yīng)請求的等待隊(duì)列中去.等到相同的正在執(zhí)行的請求處理完畢的時(shí)候會調(diào)用 finish()方法,然后將這些等待隊(duì)列中的請求全部加入緩存隊(duì)列中去,讓緩存線程來處理
    synchronized (mWaitingRequests) {
        String cacheKey = request.getCacheKey();
        if (mWaitingRequests.containsKey(cacheKey)) {
            // 有相同請求在處理,加入等待隊(duì)列.
            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中插入一個(gè)當(dāng)前請求的空隊(duì)列,表明當(dāng)前請求正在被處理
            mWaitingRequests.put(cacheKey, null);
            mCacheQueue.add(request);
        }
        return request;
    }
}

add.png-53.6kB
add.png-53.6kB

RequestQueue#add(Request) 方法的調(diào)用流程

處理請求 Cache/NetworkDispatcher

請求被加入緩存請求隊(duì)列或者是網(wǎng)絡(luò)請求隊(duì)列,在后臺我們的緩存處理線程,網(wǎng)絡(luò)處理線程,一直在運(yùn)行著等待著請求的到來。我們先來看看 CacheDispatcher 線程是如何處理的:

CacheDispatcher

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

這是 CacheDispatcher 的構(gòu)造函數(shù),可以看見該對象內(nèi)部持有緩存隊(duì)列,網(wǎng)絡(luò)隊(duì)列,緩存對象,響應(yīng)投遞對象的引用。

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

    // 初始化緩存,將緩存目錄下的所有緩存文件的摘要信息加載到內(nèi)存中.
    mCache.initialize();

    //無線循環(huán),意味著線程啟動之后會一直運(yùn)行
    while (true) {
        try {
            // 從緩存隊(duì)列中取出一個(gè)請求,如果沒有請求則一直等待
            final Request<?> request = mCacheQueue.take();
            request.addMarker("cache-queue-take");

            // 如果當(dāng)前請求已經(jīng)取消,那就停止處理它
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }
            // 嘗試取出緩存實(shí)體對象
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                // 沒有緩存,將當(dāng)前請求加入網(wǎng)絡(luò)請求隊(duì)列,讓NetworkDispatcher進(jìn)行處理.
                mNetworkQueue.put(request);
                continue;
            }

            // 如果緩存實(shí)體過期,任然將當(dāng)前請求加入網(wǎng)絡(luò)請求隊(duì)列,讓NetworkDispatcher進(jìn)行處理.
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }

            // 將緩存實(shí)體解析成NetworkResponse對象.
            request.addMarker("cache-hit");
            Response<?> response = request.parseNetworkResponse(
            new NetworkResponse(entry.data, entry.responseHeaders));
            request.addMarker("cache-hit-parsed");
            
            if (!entry.refreshNeeded()) {
                // 緩存依舊新鮮,投遞響應(yīng).
                mDelivery.postResponse(request, response);
            } else {
                //緩存已經(jīng)不新鮮了,我們可以進(jìn)行響應(yīng)投遞,然后將請求加入網(wǎng)絡(luò)隊(duì)列中去,進(jìn)行新鮮度驗(yàn)證,如果響應(yīng)碼為 304,代表緩存新鮮可以繼續(xù)使用,不用刷新響應(yīng)結(jié)果
                request.addMarker("cache-hit-refresh-needed");
                request.setCacheEntry(entry);

                // 標(biāo)記當(dāng)前響應(yīng)為中間響應(yīng),如果經(jīng)過服務(wù)器驗(yàn)證緩存不新鮮了,那么隨后將有第二條響應(yīng)到來.這意味著當(dāng)前請求并沒有完成,只是暫時(shí)顯示緩存的數(shù)據(jù),等到服務(wù)器驗(yàn)證緩存的新鮮度之后才會將請求標(biāo)記為完成
                response.intermediate = true;

                // 將響應(yīng)投遞給用戶,然后加入網(wǎng)絡(luò)請求隊(duì)列中去.
                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;
            }
        }
    }
}

在代碼的注釋中基本上可以理清 CacheDispatcher 的工作流程,下面附上流程圖

緩存run方法 .png-95.6kB
緩存run方法 .png-95.6kB

CacheDispatcher#run() 方法內(nèi)部流程

NetworkDispatcher
在 CacheDispatcher 當(dāng)中我們會把一些不符合條件的請求加入網(wǎng)絡(luò)請求隊(duì)列中,下面我們來看看在 NetworkDispatcherrun() 方法中是怎么來處理這些請求的:

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

這是 NetworkDispatcher 的構(gòu)造函數(shù),可以看見該對象內(nèi)部持有網(wǎng)絡(luò)隊(duì)列,緩存對象,響應(yīng)投遞對象,Network對象的引用。

@Override
    public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        Request<?> request;
        try {
            // 從網(wǎng)絡(luò)隊(duì)列中取出一個(gè)請求,沒有請求則一直等待.
            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");

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

            // 執(zhí)行網(wǎng)絡(luò)請求.
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");
            // 如果響應(yīng)碼為304,并且我們已經(jīng)傳遞了一次響應(yīng),不需要再傳遞一次驗(yàn)證的響應(yīng),意味著本次請求處理完成。也就是說該請求的緩存是新鮮的,我們直接使用就可以了。
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // 在工作線程中想響應(yīng)數(shù)據(jù)解析成我們需要的Response對象,之所以在工作線程進(jìn)行數(shù)據(jù)解析,是為了避免一些耗時(shí)操作造成主線程的卡頓.
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");
            // 如果允許,則將響應(yīng)數(shù)據(jù)寫入緩存,這里的緩存是需要服務(wù)器支持的,這點(diǎn)我們接下來再說
            // 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");
            }

            // 傳遞響應(yīng)數(shù)據(jù).
            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);
        }
    }
}

在代碼當(dāng)中的第29行,我們調(diào)用 Network 對象的 performRequest(Request<?> request) 方法來執(zhí)行網(wǎng)絡(luò)請求,并且返回我們需要的 NetworkResponse 對象。如果是304響應(yīng),并且我們已經(jīng)傳遞過一次響應(yīng),就不需要在傳遞新的解析數(shù)據(jù),不然我們將數(shù)據(jù)解析成 Reponse 對象,并傳遞給主線程進(jìn)行回到處理,如果該請求允許被緩存,就將該請求的結(jié)果寫入緩存中去,這就是 Networkdispatcher 的工作流程。以下是 NetworkDispatcher 的流程圖:

網(wǎng)絡(luò)處理線程run方法.png-73.9kB
網(wǎng)絡(luò)處理線程run方法.png-73.9kB

NetworkDispatcher的run() 方法

執(zhí)行請求 performRequest

在上面 NetworkDispatcher 的代碼中第29行,會通過 NetworkperformRequest 方法來進(jìn)行網(wǎng)絡(luò)請求:

public interface Network {
    /**
    * 執(zhí)行指定的請求.
    * @param request 被處理的請求
    * @return 一個(gè) NetworkResponse 對象,包含響應(yīng)的數(shù)據(jù),頭部以及響應(yīng)碼等數(shù)據(jù)
    * @throws VolleyError on errors
    */
    NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

Network 是一個(gè)接口,它的內(nèi)部只有這一個(gè)方法,在 Volley 中我們最終調(diào)用的是它的實(shí)現(xiàn)類,BasicNetwork 的 performRequest() 方法,方法如下所示:

@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    long requestStart = SystemClock.elapsedRealtime();//請求開始的時(shí)間
    while (true) {
        HttpResponse httpResponse = null;
        byte[] responseContents = null;
        Map<String, String> responseHeaders = Collections.emptyMap();
        try {
            // 收集頭部
            Map<String, String> headers = new HashMap<String, String>();
            //對于addCacheHeaders這個(gè)方法,我們也會在緩存的部分進(jìn)行介紹
            addCacheHeaders(headers, request.getCacheEntry());//附加請求頭部,用來驗(yàn)證緩存數(shù)據(jù)?
            httpResponse = mHttpStack.performRequest(request, headers);
            StatusLine statusLine = httpResponse.getStatusLine();
            int statusCode = statusLine.getStatusCode();

            responseHeaders = convertHeaders(httpResponse.getAllHeaders());
            // 驗(yàn)證緩存的新鮮度.
            if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                //請求的資源沒有修改,意思可以使用緩存中的數(shù)據(jù)
                Cache.Entry entry = request.getCacheEntry();
                if (entry == null) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                    responseHeaders, true,SystemClock.elapsedRealtime() -requestStart);
                }

                // A HTTP 304 response does not have all header fields. We
                // have to use the header fields from the cache entry plus
                // the new ones from the response.
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                entry.responseHeaders.putAll(responseHeaders);
                return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
            }

            // Some responses such as 204s do not have content.  We must check.
            if (httpResponse.getEntity() != null) {
                responseContents = entityToBytes(httpResponse.getEntity());
            } else {
                // Add 0 byte response as a way of honestly representing a
                // no-content request.
                responseContents = new byte[0];
            }

            // if the request is slow, log it. 請求持續(xù)時(shí)間
            long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
            logSlowRequests(requestLifetime, request, responseContents, statusLine);

            if (statusCode < 200 || statusCode > 299) {
                throw new IOException();
            }
            //一條真正的網(wǎng)絡(luò)響應(yīng)
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false,SystemClock.elapsedRealtime() - requestStart);
        } catch (SocketTimeoutException e) {
            //......這部分代碼是用來進(jìn)行請求重試的,我們隨后在做解析
        } 
    }
}

在上面代碼的第12行,又會調(diào)用 HttpStack 對象的 performRequest() 方法去執(zhí)行網(wǎng)絡(luò)請求,在 HttpStack 中才真正進(jìn)行網(wǎng)絡(luò)請求,HttpStack 對象在我們一開始調(diào)用 Volley.newRequestQueue() 的方法時(shí)候初始化的,默認(rèn)情況下,如果系統(tǒng)版本在 Android2.3 之前就會創(chuàng)建 HttpClientStack,之后就會創(chuàng)建 HUrlStack 對象,同樣我們也可以實(shí)現(xiàn)自己的 HttpStack對象,通過 Volley 的重載方法 newRequestQueue(Context,HttpStack) 將我們自定義的 HttpStack 傳入即可。

在代碼的18行,我們會進(jìn)行新鮮度驗(yàn)證,如果是304響應(yīng)那么我們會直接利用緩存實(shí)體的數(shù)據(jù)。之后會將響應(yīng)的數(shù)據(jù)組裝成一個(gè) NetworkResponse 對象返回。
在回到之前 NetworkDispatcher 的代碼中,當(dāng)我們獲得這個(gè) NetworkResponse 對象之后,如果是304響應(yīng)那我們的請求處理結(jié)束,不然的話就會調(diào)用 Request#parseNetworkResponse(NetworkResponse) 方法將 NetworkResponse 對象解析成我們需要的 Response 對象,這是一個(gè)抽象方法,由子類具體實(shí)現(xiàn)來解析成期望的響應(yīng)類型,此方法在工作線程調(diào)用。如果該請求可以被緩存,就會將響應(yīng)實(shí)體寫入緩存,標(biāo)記請求被投遞,然后調(diào)用 ResponseDelivery 對象的 postResponse() 方法來將解析的結(jié)果投遞到主線程中,然后進(jìn)行回調(diào)處理。

響應(yīng)傳遞、回調(diào) postResponse

響應(yīng)結(jié)果投遞接口,主要負(fù)責(zé)將響應(yīng)的結(jié)果/錯(cuò)誤,投遞到主線程中,供回調(diào)函數(shù)處理:

public interface ResponseDelivery {
    /**
     * 傳遞從網(wǎng)絡(luò)或者緩存中解析的Response對象.
     */
    void postResponse(Request<?> request, Response<?> response);

    /**
     * 傳遞從網(wǎng)絡(luò)或者緩存中解析的Response對象.提供一個(gè)Runnable對象,會在傳遞之后執(zhí)行
     */
    void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * 傳遞給定請求的Error
     */
    void postError(Request<?> request, VolleyError error);
}

它的內(nèi)部實(shí)現(xiàn)類為 ExecutorDelivery,讓我們來看看 ExecutorDelivery 中的具體實(shí)現(xiàn):

 private final Executor mResponsePoster;
 
 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));
}

我們會在 postResponse 方法中調(diào)用 mResponsePoster 對象的 execute 方法,緊接著通過 handler 對象發(fā)送一個(gè)消息,這個(gè)消息是 ResponseDeliveryRunnable 對象,它是 Runnable 的實(shí)現(xiàn)類,并且這個(gè) Runnable 對象會在主線程被執(zhí)行,為什么呢?這是因?yàn)?handler 是在 ExecutorDelivery 初始化的時(shí)候作為參數(shù)傳遞出來的,我們可以看一下該構(gòu)造函數(shù)調(diào)用的時(shí)機(jī):

 public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

可以看見我們使用主線的 Looper 在構(gòu)建一個(gè) Handler對象,所以由該 Handler 對象發(fā)送的消息,都會在主線程被執(zhí)行,不熟悉 Handler 機(jī)制的可以看下這篇文章 Android消息機(jī)制。

接著我們看看這個(gè) ResponseDeliveryRunnable 類:

private class ResponseDeliveryRunnable implements Runnable {
    //......省略
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        // If this request has canceled, finish it and don't deliver.
        /** 如果請求已經(jīng)取消的話,就不用在投遞了*/
        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();
        }
      }
}

這個(gè) Runnable 會在主線程執(zhí)行,然后將響應(yīng)結(jié)果傳遞給請求的回調(diào)函數(shù)。在代碼中的15,17行就是我們的回調(diào)函數(shù),每一個(gè)請求的響應(yīng)結(jié)果,不論是成功或者是失敗,都會傳遞到這兩個(gè)方法中,第15行的 deliverResponse 方法也是一個(gè)抽象方法,由子類實(shí)現(xiàn),參照 Volley 提供的 StringRequest 可以看見,在這個(gè)方法中,我們最終將期望的對象傳遞給了 Response 中的 onResponse() 方法中,可以看 創(chuàng)建 RequestQueue 對象 這一段落的第一段代碼中的12行

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

請求的結(jié)果最終就會被傳遞這 onResponse,onErrorResponse 中。在這里貼一張 Volley 內(nèi)部的 Response 轉(zhuǎn)換流程圖:

Response 處理流程圖
Response 處理流程圖

圖片取自codeKK Volley 源碼分析

請求完成 Request#finish(String)

在執(zhí)行完我們的回調(diào)函數(shù)之后,會調(diào)用 Request 中的 finish() 方法標(biāo)記請求完成,然后會將我們的請求從請求隊(duì)列中移除,以下代碼展示了兩處調(diào)用 finish() 的地方:

if (mResponse.intermediate) {
    mRequest.addMarker("intermediate-response");
} else {
    mRequest.finish("done");
}
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
    request.finish("not-modified");
    continue;
}

他們的區(qū)別是,第一段代碼的 Response 是從網(wǎng)絡(luò)返回的數(shù)據(jù),而第二段代碼是代表我們之前傳遞了需要驗(yàn)證緩存新鮮度的緩存實(shí)體,經(jīng)驗(yàn)證后緩存新鮮,標(biāo)記請求完成,大家可以查看一下 Response 的 intermediate 屬性被賦值的時(shí)機(jī)就明白了。

// Request中的finish()會調(diào)用 RequestQueue中的finish()方法
void finish(final String tag) {
    if (mRequestQueue != null) {
         mRequestQueue.finish(this);
    }
}
// RequestQueue#finish(Request)
<T> void finish(Request<T> request) {
    //從當(dāng)前的請求隊(duì)列中移除該請求
    synchronized (mCurrentRequests) {
        mCurrentRequests.remove(request);
    }
    //調(diào)用該請求設(shè)置的回調(diào)函數(shù)
    synchronized (mFinishedListeners) {
        for (RequestFinishedListener<T> listener : mFinishedListeners) {
        listener.onRequestFinished(request);
        }
    }
    if (request.shouldCache()) {
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
            if (waitingRequests != null) {
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                            waitingRequests.size(), cacheKey);
                }
                // Process all queued up requests. They won't be considered as in flight, but
                // that's not a problem as the cache has been primed by 'request'.
                mCacheQueue.addAll(waitingRequests);
            }
        }
    }
}

在移除已經(jīng)完成的請求之后,如果該請求是可以緩存的,并且存在著等待該請求的等待隊(duì)列,就將隊(duì)列中的所有請求加入緩存隊(duì)列(mCacheQueue) 中去,讓緩存線程接著處理。

補(bǔ)充一點(diǎn)---取消請求

  • 可以調(diào)用 Request 的 cancel() 方法來標(biāo)記請求取消,這樣我們的回調(diào)函數(shù)永遠(yuǎn)不會被調(diào)用
  • 可以調(diào)用 RequstQueue 的 cancelAll(Object) 的方法來批量取消被打上 Object 標(biāo)記的請求
  • 可以調(diào)用 RequstQueue 的 cancelAll(RequestFilter) 的方法,按照自定義的過濾方法來取消符合過濾條件的請求

抽象的處理流程圖

抽象的流程圖.png-43.3kB
抽象的流程圖.png-43.3kB

通過 CacheDispatcher 和 NetworkDispatcher 兩種線程不斷的從 RequestQueue 中取出請求來處理,然后將獲取的數(shù)據(jù)在子線程解析成我們需要的結(jié)果,通過 ResponseDelivery 的 postResponse 方法將結(jié)果投遞到主線程中去,觸發(fā)回調(diào)。

請求緩存和重試機(jī)制

再此之前我們先看一下 Request 類中的一些重要屬性和方法:
Request<T>
所有請求的抽象類,T 類型代表請求期望的類型,也是響應(yīng)最終被解析成的類型。支持 Get,Post,Put,Delete,Options,Trace,Head,Patch 共8種請求,提供 Low,Normal,Hight,Imediate 4種優(yōu)先級。下面會挑出一些比較重要的字段和方法進(jìn)行講解:

  • mShouldCache,用于標(biāo)識請求是否允許緩存,緩存需要客戶端和服務(wù)器的支持,這個(gè)字段僅僅代表客戶端是否支持緩存
  • mShouldRetryServerErrors,默認(rèn)值為 false,代表在服務(wù)器返回響應(yīng)碼在 500~599的范圍內(nèi)的話(服務(wù)器錯(cuò)誤),不進(jìn)行請求重試
  • mCacheEntry,該請求的緩存實(shí)體,在 CacheDispatcher 處理 CacheQueue 中請求的時(shí)候,會判斷該請求之前是否有緩存存在,如果存在的話將緩存實(shí)體賦值給該字段。用于在服務(wù)器返回 304 響應(yīng)的時(shí)候構(gòu)建 NetworkResponse 對象
  • mResponseDelivered,代表該請求的響應(yīng)結(jié)果已經(jīng)被投遞到主線程,只有在響應(yīng)被傳遞給主線程的時(shí)候標(biāo)記為 true,它的作用同樣是用來驗(yàn)證緩存一致性
  • abstract protected Response<T> parseNetworkResponse(NetworkResponse response);抽象方法,將請求返回的結(jié)果解析成請求期望的類型,具體的解析方式需要子類實(shí)現(xiàn)
  • abstract protected void deliverResponse(T response);抽象方法,也同樣需要子類自行實(shí)現(xiàn),將解析后的結(jié)果傳遞給請求的回調(diào)函數(shù)
    之前我們有提到過要實(shí)現(xiàn)請求緩存需要客戶端和服務(wù)器端共同的支持才行。

請求緩存

之前我們說過,緩存機(jī)制是需要客戶端和服務(wù)器端共同支持的。從客戶端的角度來說:需要實(shí)現(xiàn) Http 緩存相關(guān)的語義;從服務(wù)器的角度來說:需要允許請求的資源被緩存;我們先來看一些有關(guān)于 Http 請求頭和響應(yīng)頭的概念:

HTTP 響應(yīng)頭

Cache-Control:指明當(dāng)前資源的有效期,用來控制從緩存中取數(shù)據(jù),還是需要將請求發(fā)送到服務(wù)器進(jìn)行驗(yàn)證,重新取回?cái)?shù)據(jù),該頭部有以下幾個(gè)值:

  • no-cache:使用緩存前必須先向服務(wù)器確認(rèn)其有效性
  • no-store:不緩存響應(yīng)的任何內(nèi)容,相同的請求都會發(fā)送給服務(wù)器
  • max-age:緩存有效性的最長時(shí)間
  • must-revalidate:可緩存,但是使用的時(shí)候必需向源服務(wù)器驗(yàn)證
  • proxy-revalidate:要求中間緩存服務(wù)器對緩存的有效性進(jìn)行確認(rèn)
  • stale-while-revalidate:在這段時(shí)間內(nèi),允許先使用緩存,但需要向服務(wù)器驗(yàn)證緩存的有效性

Expires:資源失效的日期,如果和 max-age 同時(shí)存在,以 max-age 時(shí)間為準(zhǔn)
ETag:可將資源以字符串形式做唯一性標(biāo)識的方式,服務(wù)器會為每份資源分配對應(yīng)的 Etag 值
Last-Modified:資源最終被修改的時(shí)間

HTTP 請求頭

If-None-Match:如果上一次響應(yīng)的的響應(yīng)頭部中帶有 ETag 響應(yīng)頭,再次請求的時(shí)候會將 ETag 的值作為 If-None-Match 請求頭的值,當(dāng) If-None-Match 的值與請求資源的 Etag 不一致時(shí),服務(wù)器會處理該請求,該字段用來獲取最新的資源
If-Modified-Since:如果上一次響應(yīng)的響應(yīng)頭部中帶有 Last-Modified 響應(yīng)頭,那么再次請求的時(shí)候會將 Last-Modified 的值作為 If-Modified-Since 請求頭的值,在If-Modified-Since 字段指定的之后,資源發(fā)生了更新,服務(wù)器會接受該請求,否則返回 304 響應(yīng)

Entry

Cache接口中的內(nèi)部類,代表著緩存實(shí)體

  • data,這是一個(gè)字節(jié)數(shù)組,其實(shí)也就是我們響應(yīng)的 Content 部分
  • etag,用來驗(yàn)證緩存一致性的標(biāo)記
  • serverDate,數(shù)據(jù)從服務(wù)器返回的時(shí)間
  • lastModified,訪問的服務(wù)器資源上次被修改的時(shí)間
  • ttl,數(shù)據(jù)的過期時(shí)間,在(softTtl-ttl)這段時(shí)間內(nèi)我們可以使用緩存,但是必須向服務(wù)器驗(yàn)證緩存的有效性
  • softTtl,數(shù)據(jù)的新鮮時(shí)間,緩存再次之前一直有效
  • responseHeaders,響應(yīng)頭部
  • boolean isExpired(),判斷是否過期,ttl代表的時(shí)間小于當(dāng)前時(shí)間就意味著過期了
  • boolean refreshNeeded(),顧名思義,當(dāng)softTtl代表的時(shí)間小于當(dāng)前時(shí)間,就代表數(shù)據(jù)不新鮮了,需要刷新數(shù)據(jù)

下面我們來看看 Volley 中是怎么利用這些頭部信息來對響應(yīng)結(jié)果進(jìn)行緩存處理的:
在 NetworkDispatcher 的 run() 方法中有這樣一樣代碼

if (request.shouldCache() && response.cacheEntry != null) {
    mCache.put(request.getCacheKey(), response.cacheEntry);
    request.addMarker("network-cache-written");
}

想要緩存響應(yīng)結(jié)果需要滿足兩個(gè)條件,第一條該請求允許緩存,我們創(chuàng)建的每一條請求都是默認(rèn)支持緩存的;第二條就是響應(yīng)對象中的緩存實(shí)體不為空。那么我們需要看一下緩存實(shí)體是在什么時(shí)候被創(chuàng)建的,在執(zhí)行上述的 if 語句判斷之前會執(zhí)行這么一句代碼 Response<?> response = request.parseNetworkResponse(networkResponse); 通過parseNetworkResponse 方法將 NetworkResponse 對象轉(zhuǎn)化為 Response 對象,parseNetworkResponse 方法是一個(gè)抽象方法,我們看一下 StringRequest 中是如何重寫該方法的:

@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
    String parsed;
    try {
        parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
    } catch (UnsupportedEncodingException e) {
        parsed = new String(response.data);
    }
    return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}

在代碼的最后一行,我們會發(fā)現(xiàn) Response 中的 cacheEntry 字段的值來自于 HttpHeaderParser.parseCacheHeaders(response)方法的返回值,下面我們來看看該方法的內(nèi)部實(shí)現(xiàn):

public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
    long now = System.currentTimeMillis();

    //提取響應(yīng)頭部信息
    Map<String, String> headers = response.headers;

    long serverDate = 0;//資源返回時(shí)間
    long lastModified = 0;//資源上一次被修改的時(shí)間
    long serverExpires = 0;//資源的有效時(shí)間,以maxAge為主
    long softExpire = 0;//資源的新鮮時(shí)間,如果在該時(shí)間內(nèi),發(fā)起相同的請求,那么可以允許使用緩存的信息,不需要將請求發(fā)送給服務(wù)器。
    long finalExpire = 0;//緩存過期時(shí)間,在該時(shí)間之后的請求,都將發(fā)送給服務(wù)器,無法使用緩存。
    long maxAge = 0;//資源的有效時(shí)間,
    //在 staleWhileRevalidate 時(shí)間內(nèi),我們可以先用緩存數(shù)據(jù)展示給用戶,在向服務(wù)器驗(yàn)證緩存的有效性
    long staleWhileRevalidate = 0;
    boolean hasCacheControl = false;//代表是否有 Cache-Control 頭部
    boolean mustRevalidate = false;

    String serverEtag = null;//資源在服務(wù)器中的標(biāo)識
    String headerValue;

    headerValue = headers.get("Date");
    if (headerValue != null) {
        serverDate = parseDateAsEpoch(headerValue);
    }
    //獲取 Cache-Contral 頭部的相關(guān)信息
    headerValue = headers.get("Cache-Control");
    if (headerValue != null) {
        hasCacheControl = true;
        String[] tokens = headerValue.split(",");
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i].trim();
            if (token.equals("no-cache") || token.equals("no-store")) {
            //如果出現(xiàn)了 no-cache,no-store指令,代表服務(wù)器不允許緩存響應(yīng),返回的 entry 對象為空
                return null;
            } else if (token.startsWith("max-age=")) {
            //如果出現(xiàn)了 max-age指令,那么服務(wù)器允許緩存該響應(yīng),并且給出響應(yīng)的過期時(shí)間
                try {
                    maxAge = Long.parseLong(token.substring(8));
                } catch (Exception e) {
                }
            } else if (token.startsWith("stale-while-revalidate=")) {
            //這段時(shí)間處于新鮮時(shí)間和過期時(shí)間之間,在這段時(shí)間內(nèi)發(fā)起的請求,都可以利用之前的緩存信息,但是需要將請求發(fā)送給服務(wù)器做驗(yàn)證,如果是304 響應(yīng),則請求結(jié)束,不然將重新傳遞響應(yīng)結(jié)果。
                try {
                    staleWhileRevalidate = Long.parseLong(token.substring(23));
                } catch (Exception e) {
                }
            } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
            //代表不區(qū)分新鮮時(shí)間與過期時(shí)間,到了max-age指定的時(shí)間之后,請求都將發(fā)送給服務(wù)器用來驗(yàn)證緩存的有效性。
                mustRevalidate = true;
            }
        }
    }

    headerValue = headers.get("Expires");
    if (headerValue != null) {
        serverExpires = parseDateAsEpoch(headerValue);
    }

    headerValue = headers.get("Last-Modified");
    if (headerValue != null) {
        lastModified = parseDateAsEpoch(headerValue);
    }

    serverEtag = headers.get("ETag");

    // 在 Cache-Control 和 Expires 頭部都存在的情況下,以Cache-Control為準(zhǔn)
    if (hasCacheControl) {
        softExpire = now + maxAge * 1000;
        finalExpire = mustRevalidate
                    ? softExpire
                    : softExpire + staleWhileRevalidate * 1000;
    } else if (serverDate > 0 && serverExpires >= serverDate) {
    // Default semantic for Expire header in HTTP specification is softExpire.
        softExpire = now + (serverExpires - serverDate);
        finalExpire = softExpire;
    }

    Cache.Entry entry = new Cache.Entry();
    entry.data = response.data;
    entry.etag = serverEtag;
    entry.softTtl = softExpire;
    entry.ttl = finalExpire;
    entry.serverDate = serverDate;
    entry.lastModified = lastModified;
    entry.responseHeaders = headers;

    return entry;
}

這是一個(gè)靜態(tài)工具方法,用于提取響應(yīng)頭部的信息,來構(gòu)建一個(gè) Cache.Entry 類型的緩存對象,針對該方法的分析都已經(jīng)寫在注釋當(dāng)中?,F(xiàn)在我們已經(jīng)了解了將響應(yīng)轉(zhuǎn)化為緩存的部分,下面我們來看看,Volley 是如何使用緩存的,在上面我們介紹 CacheDispatcher 工作流程的時(shí)候已經(jīng)大致看過了處理緩存請求的過程,下面我再針對緩存的部分具體分析一下,下面是 CacheDispatcher run() 方法的部分代碼:

Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
    mNetworkQueue.put(request);
    continue;
}
if (entry.isExpired()) {
    request.addMarker("cache-hit-expired");
    request.setCacheEntry(entry);
    mNetworkQueue.put(request);
    continue;
}
Response<?> response = request.parseNetworkResponse(
            new NetworkResponse(entry.data, entry.responseHeaders));
if (!entry.refreshNeeded()) {
    mDelivery.postResponse(request, response);
} else {
    request.setCacheEntry(entry);
    response.intermediate = true;
    mDelivery.postResponse(request, response, new Runnable() {
        @Override
        public void run() {
            try {
            mNetworkQueue.put(request);
            } catch (InterruptedException e) {
            }
        }
    });
}

先從緩存中取出緩存實(shí)體,然后通過 isExpired() 判斷該緩存有無過期,內(nèi)部是通過 return this.ttl < System.currentTimeMillis(); 的形式來進(jìn)行比較,ttl 的含義,在??的代碼中我們已經(jīng)介紹過了,之后通過 return this.softTtl < System.currentTimeMillis();的方式來判斷實(shí)體是否需要刷新,softTtl的值我們同樣已經(jīng)介紹過了,如果不需要刷新,那么我們就可以直接使用緩存,不然的話就需要向服務(wù)器驗(yàn)證緩存的有效性。那么如何通知服務(wù)器來進(jìn)行驗(yàn)證呢,接下來我們看看執(zhí)行請求的時(shí)候,在 BasicNetwork # performRequest() 中調(diào)用 HttpStack 執(zhí)行請求執(zhí)行,會調(diào)用 addCacheHeaders(headers, request.getCacheEntry()); 方法,用來附加請求頭部信息,我們看看該方法內(nèi)部的實(shí)現(xiàn):

private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
    if (entry == null) {
    return;
    }

    if (entry.etag != null) {
        headers.put("If-None-Match", entry.etag);
    }
    if (entry.lastModified > 0) {
        Date refTime = new Date(entry.lastModified);
        headers.put("If-Modified-Since",DateUtils.formatDate(refTime));
    }
}

結(jié)合我們上面對請求頭的介紹,大家很容易明白這段代碼的意思,如果資源沒有發(fā)生改變,就會返回 304 響應(yīng)碼,告訴我們可以使用之前的緩存,對緩存的分析就到這里。

Byte[] 緩存

再次看一下下面這段代碼:

// Some responses such as 204s do not have content.  We must check.
if (httpResponse.getEntity() != null) {
    responseContents = entityToBytes(httpResponse.getEntity());
} else {
    // Add 0 byte response as a way of honestly representing a
    // no-content request.
    responseContents = new byte[0];
}

這段代碼位于 BasicNetwork 的 performRequest() 方法中,用于將返回的 HttpEntity 對象轉(zhuǎn)化為 byte[] 對象,這個(gè)字節(jié)數(shù)組最后將用于被轉(zhuǎn)化為 T 類型的對象,也就是請求期望的對象,我們之所以沒有直接返回 HttpEntity 對象,而把它解析成 byte[] 就是為之后 Request 的子類進(jìn)行解析提供便利。
接下來,我們看一下用于轉(zhuǎn)換數(shù)據(jù)的方法:

private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {
    PoolingByteArrayOutputStream bytes =
     new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength());
    byte[] buffer = null;
    try {
        InputStream in = entity.getContent();
        if (in == null) {
            throw new ServerError();
        }
        buffer = mPool.getBuf(1024);
        int count;
        while ((count = in.read(buffer)) != -1) {
            bytes.write(buffer, 0, count);
        }
        return bytes.toByteArray();
    } finally {
        try {
            // Close the InputStream and release the resources by "consuming the content".
            entity.consumeContent();
        } catch (IOException e) {
            // This can happen if there was an exception above that left the entity in
            // an invalid state.
            VolleyLog.v("Error occurred when calling consumingContent");
        }
        mPool.returnBuf(buffer);
        bytes.close();
    }
}

上面的代碼中有兩種類型對象我們需要注意,一個(gè)是 PoolingByteArrayOutputStream 對象 bytes,另一個(gè)是 ByteArrayPool 對象 mPool。我們先說一下沒有這兩個(gè)對象之前的轉(zhuǎn)化方式,首先我們從 HttpEntity 打開是一個(gè)輸入流,然后構(gòu)建一個(gè)緩沖字節(jié)數(shù)組 buffer,不斷的將輸入流的數(shù)據(jù)寫入 buffer 中,在通過 ByteArrayOutputStream 不斷的將 buffer 中的數(shù)據(jù)輸入到 ByteArrayOutputStream 中的 buf 字節(jié)數(shù)組中,如果 buf 的大小不夠,將會 new 出新的 byte[] 對象,賦值給 buf

在這個(gè)過程中,byte[] 對象被不斷的創(chuàng)建和銷毀,內(nèi)存不斷的分配和回收,如果處理不斷可能會造成內(nèi)存泄漏,消耗了系統(tǒng)資源,Volley 通過 ByteArrayPoolPoolingByteArrayOutputStream 來解決這個(gè)問題,先看一下 ByteArrayPool 的代碼:

public class ByteArrayPool {
    private final List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();//按照使用順序排列的Buffer緩存
    private final List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);//按照 byte[]大小排列的Buffer緩存

    /** 當(dāng)前緩存池中緩存的總大小 */
    private int mCurrentSize = 0;

    /**
     * 緩存池上限,達(dá)到這個(gè)限制之后,最近最長時(shí)間未使用的 byte[]       * 將被丟棄
     */
    private final int mSizeLimit;

    /** 通過 buffer 的大小進(jìn)行比較 */
    protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
        @Override
        public int compare(byte[] lhs, byte[] rhs) {
            return lhs.length - rhs.length;
        }
    };
    public ByteArrayPool(int sizeLimit) {
        mSizeLimit = sizeLimit;
    }

    // 從緩存池中獲取所需的 byte[],如果沒有大小合適的 byte[],將新new一個(gè) byte[] 對象
    public synchronized byte[] getBuf(int len) {
        for (int i = 0; i < mBuffersBySize.size(); i++) {
            byte[] buf = mBuffersBySize.get(i);
            if (buf.length >= len) {
                mCurrentSize -= buf.length;
                mBuffersBySize.remove(i);
                mBuffersByLastUse.remove(buf);
                return buf;
            }
        }
        return new byte[len];
    }

    // 將使用后的 byte[] 返還給 緩存池
    public synchronized void returnBuf(byte[] buf) {
        if (buf == null || buf.length > mSizeLimit) {
            return;
        }
        mBuffersByLastUse.add(buf);
        int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
        if (pos < 0) {
            pos = -pos - 1;
        }
        mBuffersBySize.add(pos, buf);
        mCurrentSize += buf.length;
        trim();
    }

    // 對緩存池?cái)?shù)據(jù)進(jìn)行修剪
    private synchronized void trim() {
        while (mCurrentSize > mSizeLimit) {
            byte[] buf = mBuffersByLastUse.remove(0);
            mBuffersBySize.remove(buf);
            mCurrentSize -= buf.length;
        }
    }

}

簡單的來說就是,ByteArrayPool 通過在內(nèi)存中維護(hù)了兩組 byte[] 對象,來減少重復(fù)創(chuàng)建 byte[] 的次數(shù)。當(dāng)我們需要使用 byte[] 的時(shí)候,通過 Pool 來獲取一個(gè) byte[],當(dāng)使用完畢的時(shí)候,再將該字節(jié)數(shù)組返回給 Pool。

在來看看 PoolingByteArrayOutputStream 的代碼,他是 ByteArrayOutputStream 的子類,內(nèi)部使用了 ByteArrayPool 來代替 new Byte[]操作,提高性能:

public class PoolingByteArrayOutputStream extends ByteArrayOutputStream {
    /**
     * 默認(rèn)的 buf 大小
     */
    private static final int DEFAULT_SIZE = 256;

    private final ByteArrayPool mPool;

    /**
     * 如果寫入的數(shù)據(jù)超出 buf 的大小,將會擴(kuò)展 buf 的大小,之前通過 new Byte[] 來分配更大的空間,現(xiàn)在通過 ByteArrayPool 提供,避免創(chuàng)建對象
     */
    public PoolingByteArrayOutputStream(ByteArrayPool pool) {
        this(pool, DEFAULT_SIZE);
    }
    public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) {
        mPool = pool;
        buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE));
    }
    //關(guān)閉輸出流,將使用的 buf 歸還到緩沖池中
    @Override
    public void close() throws IOException {
        mPool.returnBuf(buf);
        buf = null;
        super.close();
    }

    // GC 的時(shí)候調(diào)用,我們不能保證高方法的觸發(fā)時(shí)機(jī),所以最好手動調(diào)用 close 方法
    @Override
    public void finalize() {
        mPool.returnBuf(buf);
    }

    /**
     * 擴(kuò)展 Buf 的大小
     */
    private void expand(int i) {
        // 判斷 buffer 能否處理更多的byte,不能的話將要擴(kuò)展 buffer 的大小
        if (count + i <= buf.length) {
            return;
        }
        byte[] newbuf = mPool.getBuf((count + i) * 2);
        System.arraycopy(buf, 0, newbuf, 0, count);
        mPool.returnBuf(buf);
        buf = newbuf;
    }

    @Override
    public synchronized void write(byte[] buffer, int offset, int len) {
        expand(len);
        super.write(buffer, offset, len);
    }

    @Override
    public synchronized void write(int oneByte) {
        expand(1);
        super.write(oneByte);
    }
}

內(nèi)部的操作很簡單,在每次寫入的時(shí)候都會檢查 buffer 大小是否合適,是否需要擴(kuò)展,在輸出流結(jié)束的時(shí)候,我們需要手動顯示調(diào)用 close 方法,來歸還從 ByteArrayPool 中擴(kuò)展的 byte[]

請求重試

RetryPolicy

RetryPolicy 接口,代表著請求重試的行為:

public interface RetryPolicy {
    //當(dāng)前超時(shí)的時(shí)間
    int getCurrentTimeout();
    //當(dāng)前重試的次數(shù)
    int getCurrentRetryCount();

    /**
     * 準(zhǔn)備重試
     * 當(dāng)拋出 VolleyError 即意味著停止重試
     */
    void retry(VolleyError error) throws VolleyError;
}

在我們初始化 Request 的時(shí)候,會給 Request 設(shè)置一個(gè)默認(rèn)的重試策略 DefaultRetryPolicy 下面我們來看看它的代碼:

DefaultRetryPolicy

public class DefaultRetryPolicy implements RetryPolicy {
    /** 當(dāng)前超時(shí)毫秒數(shù). */
    private int mCurrentTimeoutMs;

    /** 當(dāng)前重試次數(shù). */
    private int mCurrentRetryCount;

    /** 最大重試次數(shù). */
    private final int mMaxNumRetries;

    /** 超時(shí)乘積因子,用來累計(jì)計(jì)算超時(shí)時(shí)間. */
    private final float mBackoffMultiplier;

    /** 默認(rèn)超時(shí)時(shí)間 */
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /** 默認(rèn)重試次數(shù) */
    public static final int DEFAULT_MAX_RETRIES = 1;

    /** 默認(rèn)超時(shí)乘積因子 */
    public static final float DEFAULT_BACKOFF_MULT = 1f;

    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }

    /**
     * 返回當(dāng)前超時(shí)時(shí)間
     */
    @Override
    public int getCurrentTimeout() {
        return mCurrentTimeoutMs;
    }

    /**
     * 返回當(dāng)前重試次數(shù).
     */
    @Override
    public int getCurrentRetryCount() {
        return mCurrentRetryCount;
    }

    /**
     * 返回超時(shí)乘積因子.
     */
    public float getBackoffMultiplier() {
        return mBackoffMultiplier;
    }

    /**
     * 為下一次重試計(jì)算重試時(shí)間
     * @param error 上一次請求的錯(cuò)誤.
     */
    @Override
    public void retry(VolleyError error) throws VolleyError {
        mCurrentRetryCount++;
        //累計(jì)下一次重試的時(shí)間
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {
            throw error;
        }
        //拋出參數(shù)中傳入的錯(cuò)誤,就代表停止重試
    }

    /**
     * 判斷是否允許下一次重試
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

默認(rèn)的重試策略也挺簡單的,每一次累計(jì)超時(shí)的時(shí)間,然后判斷是否到達(dá)重試的上限,如果達(dá)到上限,就拋出入?yún)⒌?VolleyError 代表停止重試,那么為什么拋出傳入的參數(shù),就可以停止重試了呢,我們繼續(xù)看看 BasicNetwork 中的 performRequest() 方法:

public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    while (true) {
        try{
        
              //........省略
              
        } catch (SocketTimeoutException e) {
            attemptRetryOnException("socket", request, new TimeoutError());
        } catch (ConnectTimeoutException e) {
            attemptRetryOnException("connection", request, new TimeoutError());
        } catch (MalformedURLException e) {
            throw new RuntimeException("Bad URL " + request.getUrl(), e);
        } catch (IOException e) {
            int statusCode;
            if (httpResponse != null) {
                statusCode = httpResponse.getStatusLine().getStatusCode();
            } else {
                throw new NoConnectionError(e);
            }
            VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
            NetworkResponse networkResponse;
            if (responseContents != null) {
                networkResponse = new NetworkResponse(statusCode, responseContents,responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                    statusCode == HttpStatus.SC_FORBIDDEN) {
                    attemptRetryOnException("auth",
                    request, new AuthFailureError(networkResponse));
                } else if (statusCode >= 400 && statusCode <= 499) {
                    // Don't retry other client errors.
                    throw new ClientError(networkResponse);
                } else if (statusCode >= 500 && statusCode <= 599) {
                    if (request.shouldRetryServerErrors()) {
                        attemptRetryOnException("server",
                        request, new ServerError(networkResponse));
                    } else {
                        throw new ServerError(networkResponse);
                    }
                } else {
                    // 3xx? No reason to retry.
                    throw new ServerError(networkResponse);
                }
            } else {
                attemptRetryOnException("network", request, new NetworkError());
            }
        }
    }
}

這部分的代碼我們在上面分析 BasicNetwork 的代碼的時(shí)候已經(jīng)介紹過了,當(dāng)時(shí)省略了 catch 塊中的代碼,catch 塊中的代碼就是用來實(shí)現(xiàn)請求重試的。

當(dāng)捕獲到 SocketTimeoutExceptionConnectTimeoutException 異常的時(shí)候調(diào)用 attemptRetryOnException() 方法來進(jìn)行重試,該方法中會調(diào)用 retryPolicy.retry(exception);方法,該方法我們已經(jīng)分析過了,是用來計(jì)算請求超時(shí)時(shí)間,以及是否達(dá)到重試上限,如果可以重試,那么該方法執(zhí)行完,會繼續(xù)下一次循環(huán),再次發(fā)起請求;當(dāng)達(dá)到重試上線,無法進(jìn)行重試的時(shí)候我們會拋出 VolleyError 的實(shí)例,在 BasicNetwork#performRequest() 中我們沒有捕獲 VolleyError 異常,因次會跳出循環(huán),停止重試,該方法執(zhí)行結(jié)束,在外部 NetworkDispatcher 的 run() 方法中捕獲了該異常,將異常結(jié)果傳遞到主線程中供回調(diào)函數(shù)處理。

  • ConnectTimeoutException 表示請求超時(shí)
  • SocketTimeoutException 表示響應(yīng)超時(shí)

在代碼中同樣對 AuthFailureError 以及服務(wù)器異常提供了重試操作。

一些總結(jié)

  • Volley 可以幫助我們完成請求的自動調(diào)度處理,我們只需要將 Request 加入 RequestQueue 就可以了
  • 提供了多個(gè)并發(fā)線程幫助處理請求,但是不適合大文件下載,因?yàn)樵陧憫?yīng)解析的過程中,會將所有響應(yīng)的數(shù)據(jù)保存在內(nèi)存中
  • 提供了緩存(一定程度上符合 HTTP 語義)
  • 支持請求的優(yōu)先級,可以方便的取消請求(根據(jù) Tag 或者自定義的過濾規(guī)則)
  • 提供了請求重試機(jī)制,可以自定義重試機(jī)制(簡單,方便)
  • Volley 面向接口編程,采用組合(少用繼承)的形式提供功能,可以自定義 Network,HttpStack,Cache 等實(shí)現(xiàn),擴(kuò)展性很高
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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