Volley使用及其原理解析

前言

在現(xiàn)在的Android開發(fā)之中,已經(jīng)比較少人使用volley進(jìn)行網(wǎng)絡(luò)請求了,之所以現(xiàn)在還寫這篇關(guān)于Volley的文章,是因?yàn)関olley是一個(gè)優(yōu)秀的框架,其設(shè)計(jì)嚴(yán)格遵循了面向?qū)ο蟮脑O(shè)計(jì)原則,學(xué)習(xí)volley的設(shè)計(jì)原則,對自己的項(xiàng)目開發(fā)有比較好的提示作用。

使用方式

  1. 導(dǎo)入
    在AndroidStudio里面,只需要在Projrct structure里面添加依賴,在搜索框里輸入“volley”,直接搜索v,然后點(diǎn)擊添加即可。


    添加volley
  2. 使用
    使用相對來說比較簡單,首先需要?jiǎng)?chuàng)建一個(gè)RequestQueue,然后添加Request即可
        RequestQueue queue = Volley.newRequestQueue(this);
        queue.add(new StringRequest(Request.Method.POST, "URL", new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

            }
        }));

對于添加的Request,官方默認(rèn)實(shí)現(xiàn)的有以下幾種請求方式,當(dāng)然可以自定制Request,后續(xù)會(huì)講到。


Request的實(shí)現(xiàn)類

原理解析

在詳細(xì)解釋每個(gè)步驟的原理之前,先看一下volley的整個(gè)UML圖

Volley整體UML圖

如圖,紅框部分是整個(gè)Volley主要部分,可以看到,最中間的RequestQueue是把所有功能組合起來的類,而整個(gè)Volley設(shè)計(jì)是遵守了依賴倒轉(zhuǎn)原則,即針對接口編程,而不是針對實(shí)現(xiàn)編程,由此對于功能的拓展將很容易實(shí)現(xiàn)。接下來講述整個(gè)Volley的運(yùn)作過程。

  1. 創(chuàng)建
    在詳細(xì)解釋之前,先看一下創(chuàng)建的整體流程,當(dāng)熟悉整個(gè)流程之后,對源碼的理解會(huì)容易很多。
    Volley.newRequestQueue流程

    對于Volley而言,創(chuàng)建是由Volley.newRequestQueue()開始的,返回一個(gè)RequestQueue實(shí)例,該靜態(tài)方法有兩個(gè)重載,如下
RequestQueue newRequestQueue(Context context);
RequestQueue newRequestQueue(Context context, HttpStack stack);

當(dāng)使用第一個(gè)重載方法時(shí),其實(shí)也是調(diào)用到第二個(gè)方法。

 public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

第二個(gè)參數(shù)HttpStack,是用來進(jìn)行網(wǎng)路請求的,由Volley的整體框架圖,可以看出其有兩個(gè)實(shí)現(xiàn)子類,選擇哪個(gè)子類是有SDK的版本決定的,源碼如下:

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//放置緩存的地方
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        String userAgent = "volley/0";
        if (stack == null) {
               //如果版本號大于9(V2.3)
            if (Build.VERSION.SDK_INT >= 9) {
                //創(chuàng)建基于HttpURLConnection的HttpStack
                stack = new HurlStack();
            } else {
                //創(chuàng)建基于HttpClient的HttpStack
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //創(chuàng)建網(wǎng)絡(luò)請求和queue
        Network network = new BasicNetwork(stack);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

由代碼可以看出,完全可以自定義實(shí)現(xiàn)HttpStack,這樣就可以和其它的網(wǎng)絡(luò)請求框架結(jié)合起來或者是自定義的網(wǎng)絡(luò)請求結(jié)合起來了。
對于RequestQueue的構(gòu)造方法,最終都會(huì)調(diào)用到一下方法

    /**
     * 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;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

可以看第三個(gè)參數(shù),網(wǎng)絡(luò)請求的線程數(shù),默認(rèn)是4,當(dāng)然也可以自己完全定制一個(gè)自己想要的線程數(shù)。
RequestQueue#start方法,主要是創(chuàng)建緩存分發(fā)線程和網(wǎng)絡(luò)訪問分發(fā)線程,一個(gè)queue只有一條緩存線程,有threadPoolSize數(shù)量的網(wǎng)絡(luò)訪問線程,默認(rèn)是4,因此不適用于數(shù)據(jù)量大、通訊頻繁的網(wǎng)絡(luò)操作,因?yàn)闀?huì)占用網(wǎng)絡(luò)請求的訪問線程。

    public void start() {
        stop();  // 確定當(dāng)前線程已經(jīng)停下來了
        // 只創(chuàng)建一條緩存分發(fā)線程并且啟動(dòng),注入mNetWorkQueue,用于緩存獲取失          
        // 敗時(shí)進(jìn)行網(wǎng)絡(luò)請求
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        //創(chuàng)建多條網(wǎng)絡(luò)請求線程并啟動(dòng),在創(chuàng)建時(shí)注入mCache,用于緩存
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }
  1. 添加Request
    對于請求的添加,整體流程圖如下所示:


    添加請求

    RequestQueue.add()方法源碼如下:

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

        // 判斷是否可緩存,如果不可緩存,直接添加到網(wǎng)絡(luò)請求隊(duì)列
        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();
        //如果已經(jīng)存在當(dāng)前Request,那么只需要在等待隊(duì)列里面插入當(dāng)前請求即可,防止多次網(wǎng)絡(luò)訪問
            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);
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }
  1. 對Request進(jìn)行處理
    在上部分代碼中,可以看到add()方法只是簡單的把請求插入到了網(wǎng)絡(luò)請求對列或者緩存請求對列,按照插入請求之后就會(huì)進(jìn)行網(wǎng)絡(luò)請求,可以猜測這兩個(gè)線程都是在不斷的進(jìn)行著輪詢,先來看一下CacheDispatcher的處理流程
    流程圖


    CacheDispatcher處理流程

    CacheDispatcher的run()源碼如下

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            try {
                // 阻塞獲取一個(gè)Request,queue原型為BlockingQueue
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                // 如果請求已經(jīng)取消,則跳過該請求
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                // 嘗試從緩存里面獲取數(shù)據(jù)
                Cache.Entry entry = mCache.get(request.getCacheKey());
                //如果為空,表示沒有緩存,添加請求到網(wǎng)絡(luò)隊(duì)列
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }
                // 如果緩存過期了,添加到網(wǎng)絡(luò)請求對列
                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()) {
                    // 不需要刷新緩存,直接進(jìn)行結(jié)果傳遞
                    mDelivery.postResponse(request, response);
                } else {
                    // 需要刷新的緩存,在把緩存結(jié)果傳遞時(shí),同時(shí)應(yīng)該進(jìn)行緩存的刷新
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);
                    response.intermediate = true;
                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    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;
                }
                continue;
            }
        }
    }

可以看出run()是一直在循環(huán)中的,并且阻塞獲取Request,當(dāng)獲取到Request后分情況處理
對于NetWorkDIspatcher,主要是進(jìn)行網(wǎng)絡(luò)請求以及對請求結(jié)果的緩存,處理流程圖如下所示


網(wǎng)絡(luò)請求流程

run()的源代碼如下

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            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);

                // 此處進(jìn)行網(wǎng)絡(luò)請求,由mNetWork處理
                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;
                }

                // 對response解析,由Request解析
                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);
            }
        }
    }

可以看出請求是通過創(chuàng)建RequestQuesu傳入的Network進(jìn)行處理的, 然后對請求返回的結(jié)果,是通過我們的Request.parseNetworkResponse(NetworkResponse response)處理的,也就是說,如果是StringRequest,那么這個(gè)方法就是把請求結(jié)果轉(zhuǎn)換為String,所以我們自定義Request的時(shí)候,需要實(shí)現(xiàn)這個(gè)方法。

總結(jié)

這篇文章就寫到這里,雖然不一定會(huì)使用Volley來進(jìn)行網(wǎng)絡(luò)請求了(效率比較低),但是了解一下這個(gè)優(yōu)秀的框架,個(gè)人覺得還是很有必要的。現(xiàn)在進(jìn)行網(wǎng)絡(luò)請求,推薦使用RxJava + Retrofit的方式,其中Retrofit的網(wǎng)絡(luò)請求是通過okHttp實(shí)現(xiàn)的,在這里就不細(xì)說了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容