Volley系列之流程簡析

最近實習一直都沒有寫博客了,如今實習也穩定了,決定從新開始堅持寫博客,爭取每週寫一篇。
打算寫一個Volley系列,雖然網上已經有許多關於Volley的文章,而且好文章也很多,但仍是要寫。一方面是由於Volley太經典了,另外一方面閱讀源碼也不是很困難,能從Volley源碼中學到許多知識,設計模式、緩存策略、打log方法、網絡知識等等。就把它看成課本同樣,一行一行代碼去閱讀,去學習,去思考它爲何這樣作,這樣作的好處以及弊端。好了,廢話少說,讓咱們開始Volley之旅吧!
這篇文章重在激發你的興趣,帶着問題和興趣去看,效果會更好。看看Volley有什麼厲害的地方,令那麼多人都去看去學:html

一、首先擴展性強,主要是基於接口設計,可配置性也強。
二、許多地方都體現了單一職責,後面會娓娓道來
三、Volley的緩存設計有一些地方設計的至關巧妙,真的很贊!有一種中單和打野聯動起來的妙趣橫生。
四、網絡問題的處理,例如地址重定向,重複請求等等。
五、如何設置日誌來記錄請求信息,用於調試?
六、怎麼管理那麼多的請求,又是怎麼來分發他們?
七、怎麼來進行組合而不是繼承,讓代碼更加靈活,更加易擴展。
上面列舉了幾個有趣的地方,還有許許多多的祕密等着咱們去探索。不過仍是建議先去看看別的博客Volley的基本使用,不過下面我會簡單的介紹Volley的框架,以及請求的分發流程。這些都是爲之後的文章打基礎。android


先看Volley最基礎的用法:

RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
mQueue.add(stringRequest);複製代碼

先從Volley中得到一個RequestQueue,看名字知道是請求隊列,而後建立一個Request,將Request加入到RequestQueue中就完成了一次網絡請求。看起來是真簡單,可是背後是怎麼發送網絡的,又是怎麼將傳回的數據交給咱們在Request中兩個監聽來處理的?接着往下看:設計模式

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;
    }複製代碼

咱們先挑重點的看,細節咱們後面都會說到的。先看返回結果,是一個RequstQueue,緩存

queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
或者是
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);複製代碼

能夠看到他須要兩個參數,DiskBasedCache和Network,Network是一個接口,能夠看到他真正穿進來的實現類是BasicNetwork。安全

Network network = new BasicNetwork(stack);複製代碼

上面的代碼都是爲了製造這兩個參數,先略過,挑重點的看,先把總體脈絡掌握,細節就好說了。而後在倒數第二行還執行了queue.start();上面的代碼都是爲了建立一個RequestQueue,而後又執行了這個方法,確定不簡單,讓咱們跟進去看看。bash

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();
        }
    }複製代碼

滑上去看看上面的圖,是否是感受有點眼熟。在這個方法裏面也僅僅是建立CacheDispatcher和NetworkDispatcher,而後執行本身相應的start方法,其實它兩本質上都是線程,執行start方法就是將線程開啓,而mDispatchers.length默認值爲4,也就是說開啓了四個NetworkDispatcher線程(不要太操心爲何是4,默認值就是4,可能通過一些測試吧。或者說4更適合通常狀況,後面會說到這是什麼)具體線程裏面在執行什麼方法,咱們待會在看。網絡

public class CacheDispatcher extends Thread
public class NetworkDispatcher extends Thread複製代碼

好了,讓咱們先緩一會,讓咱們回到最初的起點,一個ResquestQueue引起的血案,讓咱們知道了併發

RequestQueue mQueue = Volley.newRequestQueue(context);複製代碼

這一行代碼最後是開啓了幾個線程。順着主路線往下走app

StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
mQueue.add(stringRequest);複製代碼

這一大段代碼前面都是爲了創造一個StringRequest ,他須要3個參數,一個url,兩個監聽器。一個是處理返回正確的數據,另外一個是處理錯誤的結果。這兒先劇透一下,不要慌,後面咱們都會說到。最後把StringRequest添加到隊列當中。讓咱們跟進去瞧一瞧。框架

public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight. synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); if (mWaitingRequests.containsKey(cacheKey)) { // There is already a request in flight. Queue up. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new LinkedList<Request<?>>(); } stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); if (VolleyLog.DEBUG) { VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); } } else { // Insert 'null' queue for this cacheKey, indicating there is now a request in // flight. mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; } }複製代碼

代碼比較長,不要怕,咱們一點一點看。

// Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }複製代碼

先將當前隊列設置到requset成爲他的成員變量,這兒先不深究,後面會涉及到的。而後將request添加到當前隊列當中。注意上面還有一個synchronized,爲了保證線程安全。

// Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }複製代碼

而後給request設置序列號,這兒也是爲請求的順序以及優先級考慮,後面也會一一說到,因此說仍是好好的把這個系列看完(逃)。而後就判斷他是否須要緩存,若是不須要直接將它添加到網絡的這個隊列,還記得star方法裏面初始化那兩類分發器,把mNetworkQueue做爲參數傳進了網絡分發器,沒錯,NetworkDispatcher 會在這個隊列取request而後進行網絡請求。而後返回該request。不過通常都默認它須要緩存。

// Insert request into stage if there's already a request with the same cache key in flight. synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); if (mWaitingRequests.containsKey(cacheKey)) { // There is already a request in flight. Queue up. Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new LinkedList<Request<?>>(); } stagedRequests.add(request); mWaitingRequests.put(cacheKey, stagedRequests); if (VolleyLog.DEBUG) { VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); } } else { // Insert 'null' queue for this cacheKey, indicating there is now a request in // flight. mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; } }複製代碼

這段代碼又是加了synchronized,你會看見只要涉及到集合操做幾乎都要設置線程安全,往下看,先獲取到他的cacheKey,其實就是和url有關的一個字符串。下面一大堆代碼都是爲了減小重複請求,若是有相同的請求就會從緩存裏面獲取response。而後看看她是怎麼實現的。首先看mWaitingRequests是否包含它,包含的話就獲取他對應的queue,而後將它添加進去。若是queue爲空就建立一個而後再把它添加進去。而後cacheKey爲key,對應的queue爲value,添加到mWaitingRequests中。若是mWaitingRequests不包含它,說明緩存裏面沒有對應他的值。而後也是cacheKey爲key,不過null爲value,添加到mWaitingRequests中。最後放進mCacheQueue裏面,而mCacheQueue又是做爲參數穿進了CacheDispatcher分發器裏面。分發器裏面究竟是幹啥了,是否是已經火燒眉毛的想去看一看了。
先不着急,總結一下,從上面的volley直接用法咱們已經知道通過一系列的波折開啓了幾個線程,將request添加到mCacheQueue或者mNetworkQueue中。而後就沒了。下來咱們看看兩個分發器裏面究竟幹了什麼?首先這兩個分發器本質都是線程,上面也說過了,因此他們開啓start方法也就是運行線程的run方法。

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

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        Request<?> request;
        while (true) {
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // Take a request from the queue.
                request = mCacheQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it. if (request.isCanceled()) { request.finish("cache-discard-canceled"); continue; } // Attempt to retrieve this item from cache. Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { request.addMarker("cache-miss"); // Cache miss; send off to the network dispatcher. mNetworkQueue.put(request); continue; } // If it is completely expired, just send it to the network. if (entry.isExpired()) { request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; } // We have a cache hit; parse its data for delivery back to the request. request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { // Completely unexpired cache hit. Just deliver the response. mDelivery.postResponse(request, response); } else { // Soft-expired cache hit. We can deliver the cached response, // but we need to also send the request to the network for // refreshing. request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // Mark the response as intermediate. response.intermediate = true; // Post the intermediate response back to the user and have // the delivery then forward the request along to the network. final Request<?> finalRequest = request; mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(finalRequest); } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); } } }複製代碼

第一眼看上去好嚇人,這麼長,其實好多都是註釋,還有一些異常處理,除過這些基本剩下很少了。首先給線程設置優先級,而後初始化緩存,而後有一個while(true),說明線程是一直在執行,而後從隊列裏面取request,細心的同窗可能會發現,怎麼沒有線程同步呢?

/** The cache triage queue. */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** The queue of requests that are actually going out to the network. */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();複製代碼

由於它兩都是PriorityBlockingQueue,是一個基於優先級堆的無界的併發安全的優先級隊列(FIFO),有兩點,一是線程安全,因此不用加synchronized 關鍵字,二是它是有優先級的,能夠設置request的優先級,後面會說到怎麼來設置request的優先級。接着往下看,若是request設置取消了直接返回繼續,而後再根據request的cacheKey獲取它的緩存實體,若是爲空就將它加到mNetworkQueue,從網絡中獲取。若是緩存過時也讓他從網絡中獲取。若是都沒問題就從從緩存中獲取response,而後再判斷response是否須要刷新,若是不須要刷新,就將該response返回給request。若是須要刷新就從網絡獲取新的responseesponse而後返回給request。中間有一些細節可能沒說,咱們先抓住主幹,後面都會提到的。
總結一下,先進行一系列的判斷,而後若是緩存裏面有就從緩存裏面取,若是沒有就將request加入mNetworkQueue從網絡中獲取。而後看看NetworkDispatcher 怎麼進行網絡獲取的。

public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // Take a request from the queue.
                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 the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

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

                // 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);
            } 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);
            }
        }
    }複製代碼

仍是好嚇人,毛主席的話,都是紙老虎(真的是紙老虎)。讓咱們繼續看,發現其實和上一個是同樣的套路,只看一個:

// Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);複製代碼

能夠看出是從這來進行網絡獲取的。mNetwork是一個接口,他有直接的實現類,從他的子類來進行網絡請求的。就不繼續往下追了,後面的文章會一一介紹的。好了,這篇文章基本也接近尾聲了。讓咱們回憶一下,首先從Volley中獲取一個requestQueue,裏面會開啓幾個線程,而後建立一個request,添加到requestQueue,若是該request有緩存從緩存裏面取,若是沒有就從網絡中獲取,再看一下上面的圖,是否是感受清晰了好多。這篇文章說這麼可能是讓你瞭解一下volley的流程,爲後面的文章打基礎。看的不是很明白也沒有關係,讓你有一個概念。可能有的人已經發現圖是郭神的,文章也有點像,沒錯,我也是看郭神的文章過來的,站在巨人的肩膀上纔會看的更遠嘛。下篇文章會以緩存爲切入點,讓你瞭解Volley是怎麼巧妙的設計緩存的,真的很精彩,敬請期待吧!

相關文章
相關標籤/搜索