Volley還算比較深入的分析

一、閑話

Volley 分析主要講解其對網(wǎng)絡(luò)框架的封裝,以及其主要涉及到的一些技術(shù)細(xì)節(jié)。二話不說先盜個圖來看看。


Volley.jpeg

看起來就是個老生常談的話題,先請求緩存,緩存沒有再去請求網(wǎng)絡(luò)。然而在實(shí)際編碼過程中發(fā)現(xiàn)有很多零零碎碎的細(xì)節(jié)需要我們?nèi)タ紤],例如緩存的管理,如何根據(jù)請求能找到相應(yīng)的緩存等很多緩存相關(guān)的問題。再比如網(wǎng)絡(luò)的線程池是如何管理的,緩存什么時候決定是否要去發(fā)起網(wǎng)絡(luò)請求,請求回來的數(shù)據(jù)又如何處理等諸多的網(wǎng)絡(luò)問題。如何優(yōu)雅的解決這些問題,形成封裝,并且還可以保持靈活的拓展性,這就是Volley在這里所做的事情了。
秉著向大神學(xué)習(xí)以及致敬的態(tài)度,當(dāng)然也是為了讓自己的代碼更有Bigger,就來認(rèn)認(rèn)真真的分析分析Volley的源碼,學(xué)習(xí)學(xué)習(xí)架構(gòu)以及設(shè)計(jì)模式等的應(yīng)用。和其他分析Volley的思路是一致的,以一條Request 發(fā)出到得到一個 Response 為線索來進(jìn)行分析。所謂萬事都得開個頭,頭開好了就能為后面做更多的事情了。對于Volley來說,這個頭莫過于就是隊(duì)列的初始化了。

二、隊(duì)列的初始化

下面這個圖是我自己畫的,丑是丑了點(diǎn),但湊合著還是可以看的,看起來想表達(dá)的意思還是清楚了的。隊(duì)列的創(chuàng)建主要初始化緩存管理,緩存分發(fā)器,網(wǎng)絡(luò)分發(fā)器,真正執(zhí)行網(wǎng)絡(luò)的Network 以及結(jié)果返回器。似乎整個架構(gòu)就這么點(diǎn)事兒了。


RequestQueue.jpg

而所謂的初始化,到底初始化了什么?

1.RequestQueue的創(chuàng)建。

RequestQueue是可以創(chuàng)建多個的,每個請求對列里面只有一個緩存分發(fā)器,默認(rèn)情況下只有4個網(wǎng)絡(luò)分發(fā)器。

一般調(diào)用的是這個唯一參數(shù)為 Context 的 newRequestQueue() 方法
 /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }
而其內(nèi)部其調(diào)用了其私有的 newRequestQueue()。RequestQueue是被直接 new 出來的對象,可見隊(duì)列是可以被創(chuàng)建多個的。一個進(jìn)程里不宜創(chuàng)建過多的隊(duì)列。
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();
      

上面代碼中最后句 start() 就是啟動了緩存以及網(wǎng)絡(luò)的分發(fā)器。

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

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

2.緩存的創(chuàng)建。

緩存策略用的也是LRU,底層依賴LinkedHashMap來實(shí)現(xiàn)。主要是設(shè)置了其參數(shù)accessorder為true。每訪問一次,對應(yīng)的節(jié)點(diǎn)modCount次數(shù)就會加1,從而在最后通過過取迭代器時依據(jù)modCount排序獲得一個排序迭代器。

/** Map of the Key, CacheHeader pairs */
    private final Map<String, CacheHeader> mEntries = new LinkedHashMap<>(16, .75f, true);

3.Network的創(chuàng)建

Network其實(shí)是一個Interface。其只有一個唯一的方法performRequest()。這個用戶可以自己實(shí)現(xiàn),也就是說用戶可以自己設(shè)置通過什么方式去真正執(zhí)行網(wǎng)絡(luò)連接以獲取數(shù)據(jù)。來看一下默認(rèn)的實(shí)現(xiàn)吧。

public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
        BasicNetwork network;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                // At some point in the future we'll move our minSdkVersion past Froyo and can
                // delete this fallback (along with all Apache HTTP code).
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info =
                            context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
                    userAgent = packageName + "/" + info.versionCode;
                } catch (NameNotFoundException e) {
                }

                network =
                        new BasicNetwork(
                                new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
            }
        } else {
            network = new BasicNetwork(stack);
        }

        return newRequestQueue(context, network);
    }

嗯,就是在創(chuàng)建RequestQueue時,如果用戶自己設(shè)置了HttpStack就會用用戶的HttpStack來包裝出一個Network。如果沒有則分版本進(jìn)行,sdk > 9,也就是Android 2.3 時就用 HurlStack ,其實(shí)就是 HttpUrlConnection的實(shí)現(xiàn)。如果比它還小——這樣的系統(tǒng)還在嗎,就用封裝Apache的AndroidHttpClient。原因呢是因?yàn)镠ttpUrlConnection 對于 keep-alive處理的bug,協(xié)議相關(guān),具體后面還會有分析系統(tǒng)原生網(wǎng)絡(luò)庫的文章再說明。后面Andrioid網(wǎng)絡(luò)庫棄用了Apache,建議用HttpUrlConnection或者Volley。而其實(shí)Android 4.4 之后,HttpUrlConnection的底層又換成了鼎鼎大名的OkHttp。

4.啟動緩存和網(wǎng)絡(luò)分發(fā)器。

對列啟動后會啟動緩存分發(fā)器和網(wǎng)絡(luò)分發(fā)器,而所謂的分發(fā)器就是Java的線程。緩存分發(fā)器主動從緩存對列里面撈請求,如果沒有撈到就會進(jìn)入await狀態(tài)。這一底層機(jī)制的實(shí)現(xiàn)是依賴于PriorityBlockingQueue的實(shí)現(xiàn),其本身就是一個阻塞式隊(duì)列,其內(nèi)部實(shí)現(xiàn)了生產(chǎn)者與消費(fèi)者的模型。網(wǎng)絡(luò)分發(fā)器與緩存分發(fā)器的實(shí)現(xiàn)一致,都是基于PriorityBlockingQueue的實(shí)現(xiàn)。流程上來說,一個請求是先進(jìn)入到了緩存對列,由緩存隊(duì)列處理完后仍需網(wǎng)絡(luò)請求或者更新就會進(jìn)入到網(wǎng)絡(luò)隊(duì)列。

private void processRequest() throws InterruptedException {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.
        final Request<?> request = mCacheQueue.take();
        processRequest(request);
    }

上面這點(diǎn)代碼拷的緩存分發(fā)器的,網(wǎng)絡(luò)分發(fā)器和其非常的雷同。上面主要是調(diào)用了PriorityBlockingQueue#take()方法??纯催@個take()方法的實(shí)現(xiàn)。

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        E result;
        try {
            while ( (result = dequeue()) == null)
                notEmpty.await();
        } finally {
            lock.unlock();
        }
        return result;
    }

上面可以看到,如果隊(duì)列為空就會進(jìn)入等待狀態(tài)notEmpty.await()。再來看一下PriorityBlockingQueue#put()方法

public void put(E e) {
        offer(e); // never need to block
}
public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        int n, cap;
        Object[] array;
        while ((n = size) >= (cap = (array = queue).length))
            tryGrow(array, cap);
        try {
            Comparator<? super E> cmp = comparator;
            if (cmp == null)
                siftUpComparable(n, e, array);
            else
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }

put()方法非常簡單,其繼續(xù)調(diào)用了 offer()方法。這個方法里面其他亂七八糟的先不管,其最關(guān)鍵的一句是 notEmpty.signal() 。這個信號的發(fā)射就喚醒了前面take()因隊(duì)列為空的await狀態(tài)。這是一個典型的生產(chǎn)者-消費(fèi)者模型了吧。

4.稍微扯一下優(yōu)先級

既然知道了分發(fā)器是PriorityBlockingQueue,那也應(yīng)該知道了為什么Volley能控制網(wǎng)絡(luò)請求的優(yōu)先級了,就是這個優(yōu)先級阻塞隊(duì)列了。而優(yōu)先級的依據(jù)就是Request的mSequence的值。這就是一個簡單的整型值,Request的CompareTo()方法決定了其排序。這個優(yōu)先級用戶可以自行進(jìn)行設(shè)置,如果用戶沒有設(shè)置,那么隊(duì)列自己會維護(hù)一個,每添加一個 Request 到隊(duì)列就會自增 1。

 /** Gets a sequence number. */
    public int getSequenceNumber() {
        return mSequenceGenerator.incrementAndGet();
    }

環(huán)境都初始化好了,接下來發(fā)起Request以及返回Response就是水到渠成的事了。

三、發(fā)起請求

又是一個比較丑的圖,而且相比起大神的圖來看,很亂的樣子,簡直就是什么玩意兒呀。亂的原因主要是因?yàn)樽鲌D的水平確實(shí)一般,就跟這個寫作水平一樣的。
其次是因?yàn)榧尤肓松a(chǎn)者-消費(fèi)者的等待-喚醒模型以及其中的條件判斷——懶的畫流程圖,而且還不連貫。能認(rèn)真看完還是會有不少收獲的。


SendRequest.jpg

1.緩存的初始化

隊(duì)列初始化時只是創(chuàng)建了緩存,真正初始化時是在緩存分發(fā)器跑起來時。還來看一點(diǎn)代碼吧。

@Override
    public synchronized void initialize() {
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }
        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            try {
                long entrySize = file.length();
                CountingInputStream cis =
                        new CountingInputStream(
                                new BufferedInputStream(createInputStream(file)), entrySize);
                try {
                    CacheHeader entry = CacheHeader.readHeader(cis);
                    // NOTE: When this entry was put, its size was recorded as data.length, but
                    // when the entry is initialized below, its size is recorded as file.length()
                    entry.size = entrySize;
                    putEntry(entry.key, entry);
                } finally {
                    // Any IOException thrown here is handled by the below catch block by design.
                    //noinspection ThrowFromFinallyBlock
                    cis.close();
                }
            } catch (IOException e) {
                //noinspection ResultOfMethodCallIgnored
                file.delete();
            }
        }
    }

代碼看起來很多,但其實(shí)關(guān)鍵就兩點(diǎn)。一方面如果當(dāng)前緩存文件夾為空則直接返回。另一方面如果當(dāng)前緩存文件夾不為空則遍歷所有的文件將其內(nèi)容解析成緩存的Entry(理解成一個單位),并put到緩存當(dāng)中。

2.緩存的key管理

緩存管理中另一個就是這個key的生成,如何保證它的唯一性這個是非常重要的,因?yàn)殛P(guān)系到了如何判斷一個請求是否有相應(yīng)的緩存。看看代碼。

/** Returns the cache key for this request. By default, this is the URL. */
    public String getCacheKey() {
        String url = getUrl();
        // If this is a GET request, just use the URL as the key.
        // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
        // legacy behavior where all methods had the same cache key. We can't determine which method
        // will be used because doing so requires calling getPostBody() which is expensive and may
        // throw AuthFailureError.
        // TODO(#190): Remove support for non-GET methods.
        int method = getMethod();
        if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
            return url;
        }
        return Integer.toString(method) + '-' + url;
    }

看起來并沒有那么的復(fù)雜,就是url或者h(yuǎn)ttp請求的方法所對應(yīng)的int加上下劃線,再加上url所構(gòu)成的。url當(dāng)然唯一了,好像沒什么可以叨叨了。但為了安全起見,將url轉(zhuǎn)成MD5是否更合適,而且還能省一些空間呢。

3.緩存的處理流程

其實(shí)上面的時序圖已經(jīng)很明顯了,緩存未命中或者已經(jīng)過期就將請求丟給網(wǎng)絡(luò)隊(duì)列。網(wǎng)絡(luò)隊(duì)列會喚醒網(wǎng)絡(luò)分發(fā)器進(jìn)行處理。網(wǎng)絡(luò)分發(fā)器有多個,當(dāng)然是誰空閑誰搶到誰就執(zhí)行了。緩存的處理邏輯里面還是有東西可以叨叨的,其結(jié)合了http協(xié)議本身對緩存的處理。先來看看 Entry 中對緩存相關(guān)的定義。

 /** ETag for cache coherency. */
        public String etag;

        /** Date of this response as reported by the server. */
        public long serverDate;

        /** The last modified date for the requested object. */
        public long lastModified;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

所認(rèn)識的好像就是etag,serverDate(服務(wù)器的時間),lastModified(上次修改時間),ttl 以及 softTtl ,有點(diǎn)不明所以。
關(guān)于 ttl 以及 softTtl 從下面的代碼中可以看到表示的是失效時間和需要刷新的時間。

/** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }

這里其實(shí)涉及到http協(xié)議緩存相關(guān)的東西,不感興趣其實(shí)可以跳過。下面代碼也有點(diǎn)長,如果不想看可以直接看代碼下面的結(jié)論。

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

        Map<String, String> headers = response.headers;

        long serverDate = 0;
        long lastModified = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long finalExpire = 0;
        long maxAge = 0;
        long staleWhileRevalidate = 0;
        boolean hasCacheControl = false;
        boolean mustRevalidate = false;

        String serverEtag = null;
        String headerValue;

        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }

        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",", 0);
            for (int i = 0; i < tokens.length; i++) {
                String token = tokens[i].trim();
                if (token.equals("no-cache") || token.equals("no-store")) {
                    return null;
                } else if (token.startsWith("max-age=")) {
                    try {
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception e) {
                    }
                } else if (token.startsWith("stale-while-revalidate=")) {
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring(23));
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    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 takes precedence over an Expires header, even if both exist and Expires
        // is more restrictive.
        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;
        entry.allResponseHeaders = response.allHeaders;

        return entry;
    }

前一部分都是對header中的關(guān)鍵字進(jìn)行讀取以及解析。能夠看到有:
"Date":服務(wù)器時間
"Cache-Control":緩存控制字段,其下還關(guān)注了"no-cache","max-age=","stale-while-revalidate=","must-revalidate"以及"proxy-revalidate"
"Expires":緩存期望失效時間
"Last-Modified":文件上次被修改時間
"ETag":文件是否被修改標(biāo)記字段
要全部理解這些字段以及它們的優(yōu)先級需要再花點(diǎn)功夫。這里主要關(guān)注一下softTtl 和 ttl 的計(jì)算。從代碼中可以看到 softTtl = softExpire ,而 ttl = finalExpire.
而這兩者的計(jì)算情況是:
(1) 有 cache-control字段的情況下——說明它的優(yōu)先級比其他緩存字段要高
softExpire = 現(xiàn)在開始 + 緩存最大能存活的時間
finalExpire = softExpire 或者 softExpire + stale-while-revalidate 的時間
(2)沒有 cache-control字段,但服務(wù)器時間存在,且緩存期望失效時間比服務(wù)器時間要大
softExpire = 現(xiàn)在開始 + (緩存失效時間與服務(wù)器時間的時間差)
finalExpire = softExpire
看完了上面這一部分可能還是會有點(diǎn)糊涂,那一張“借”來的請求時緩存處理的流程圖吧,說不定看了就會有幫助。


Cache.jpg

從上面的分析結(jié)果來看,softTtl 以及 ttl 是幫助框架提前判斷緩存是否有效以及是否需要更新的。而其他字段如ETag,Last-Modified 等協(xié)議字段是在執(zhí)行真正網(wǎng)絡(luò)請求時來決定緩存策略的。
緩存的理解與應(yīng)用都是難點(diǎn),所以花的篇幅有點(diǎn)長,也不免有點(diǎn)啰嗦了。還是繼續(xù)網(wǎng)絡(luò)的分發(fā)吧。

4.網(wǎng)絡(luò)分發(fā)器的處理

前面已經(jīng)說過,緩存未命中或者緩存已經(jīng)過期就會將請求送往網(wǎng)絡(luò)隊(duì)列,網(wǎng)絡(luò)隊(duì)列收到請求后就會依賴于PriorityBlockingQueue底層的實(shí)現(xiàn)機(jī)制喚醒網(wǎng)絡(luò)分發(fā)器繼續(xù)處理。

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

上面即是網(wǎng)絡(luò)分發(fā)器去撈請求,如果沒撈到就會進(jìn)入等待狀態(tài)。撈到了就會繼續(xù)處理請求。

void processRequest(Request<?> request) {
        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();
        }
    }

這段代碼看起來多,而關(guān)鍵點(diǎn)其實(shí)就那么幾點(diǎn):
(1)請求是否已經(jīng)被取消,如果已經(jīng)被取消則終止
(2)通過mNetwork.performRequest(request)發(fā)出真正的網(wǎng)絡(luò)請求。關(guān)于Network在RequestQueue初始化的時候已經(jīng)說過了,可以用戶自己設(shè)置,也可以用默認(rèn)的HttpUrlConnection的實(shí)現(xiàn),反正現(xiàn)在底層都是 OkHttp 的實(shí)現(xiàn)了。
(3)拿到請求后由相對應(yīng)的Request自行進(jìn)行Response解析以得到網(wǎng)絡(luò)請求的結(jié)果。緩存方面,如果需要緩存就先緩存一份,最后和 Request 一起通過 ResponseDelivery將結(jié)果傳遞回去。
(4)再者就是網(wǎng)絡(luò)出現(xiàn)錯誤或者其他異常,也是通過ResponseDelivery給傳遞回去。

四、傳遞返回結(jié)果

其實(shí)除了ResponseDelivery會返回傳遞結(jié)果,還有一個Request#notifyListenerResponseReceived()也是返回結(jié)果的。它們各返回給誰呢?先看一個比較丑的圖。


DeliveryResponse.jpg

(1) ResponseDelivery只是一個Interface,它的實(shí)現(xiàn)者是 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);
                    }
                };
    }

從它的初始化可以看到,其內(nèi)部又包含了一個 mResponsePoster,這個就是一個Executor,其實(shí)就是希望通過一個線程池來進(jìn)行發(fā)送。而最關(guān)鍵的就是這個參數(shù) Handler,也就是說結(jié)果最后是返回給了相應(yīng)的接收 Handler。而這個Handler默認(rèn)情況下包裝的就是 MainLooper,如下代碼,又回到了RequestQueue的初始化。前面的RequestQueue其實(shí)會間接地調(diào)用到這個版本的構(gòu)造函數(shù)。

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

被這個Handler處理的其實(shí)是ResponseDeliveryRunnable。在它的run()方法里面才真正地調(diào)用了對應(yīng)Request的deliverResponse()。這里借用了一個StringRequest類,不然這戲都沒法演了。
(2)再來看Request#notifyListenerResponseReceived()方法。其最終調(diào)用的是CacheDispatcher#WaitingRequestManager.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) {
                if (VolleyLog.DEBUG) {
                    VolleyLog.v(
                            "Releasing %d waiting requests for cacheKey=%s.",
                            waitingRequests.size(), cacheKey);
                }
                // Process all queued up requests.
                for (Request<?> waiting : waitingRequests) {
                    mCacheDispatcher.mDelivery.postResponse(waiting, response);
                }
            }
        }

這個通知主要是通過緩存分發(fā)器通知所有和當(dāng)前具有同一 cache key的請求結(jié)果已經(jīng)返回了。

五、總結(jié)

(1)Volley庫是一個比較完善的封裝,編碼具有比較高的Bigger,簡單,清晰,可擴(kuò)展性強(qiáng),而又不失強(qiáng)大的功能。
(2)RequestQueue是整個庫的核心,從發(fā)起請求-緩存處理-執(zhí)行網(wǎng)絡(luò)請求-解析結(jié)果-返回結(jié)果,一條龍服務(wù)。一個進(jìn)程里面是可以有多個RequestQueue實(shí)例的。
(3)Cache是這個庫的一大亮點(diǎn),其實(shí)現(xiàn)了Http協(xié)議的緩存,而Cache內(nèi)容到閃存上所用的也是類DiskLRUCache算法。但其依賴的LinkedHashMap的實(shí)現(xiàn),利用它的排序功能實(shí)現(xiàn)了LRU算法。
(4)最關(guān)鍵的隊(duì)列其實(shí)只有兩個,一個緩存隊(duì)列,一個網(wǎng)絡(luò)隊(duì)列。緩存隊(duì)列由緩存分發(fā)器處理,網(wǎng)絡(luò)隊(duì)列由網(wǎng)絡(luò)分發(fā)器處理。緩存隊(duì)列處理完之后才會視情況交給網(wǎng)絡(luò)隊(duì)列來處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 1. 功能介紹 1.1. Volley Volley 是 Google 推出的 Android 異步網(wǎng)絡(luò)請求框架和...
    愛碼士平頭哥閱讀 1,910評論 0 9
  • 注:本文轉(zhuǎn)自http://codekk.com/open-source-project-analysis/deta...
    Ten_Minutes閱讀 1,393評論 1 16
  • Volley已是老牌的網(wǎng)絡(luò)請求庫了(雖然也就3歲不到),雖然是Google官方的庫,但是目前OkHttp才正是大行...
    LeonXtp閱讀 449評論 0 1
  • Android 異步網(wǎng)絡(luò)請求框架-Volley 1. 功能介紹 1.1. Volley Volley 是 Goog...
    Brian512閱讀 4,018評論 2 40
  • 水木告白: 我所希望培養(yǎng)出的孩子------仁義、厚道、沉穩(wěn)、溫暖、陽光、樂觀、健康、向上!讓人喜歡。 相信家長已...
    水木明閱讀 210評論 0 1

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