Volley的原理解析

前言

在 Android 開發編程的過程中,無可避免地須要涉及到網絡編程,這就須要咱們開發者比較熟練地掌握網絡編程。哈哈,今天就先從解析 Volley 開始吧!java

1.0 Volley是什麼

Volley 是在 2013 年的 Googel I/O 大會上面推出的一個 HTTP 庫,它能夠幫助 Android 應用更加方便地執行網絡請求。它既能夠訪問網絡取得數據,同時還能夠訪問網絡取得圖片。git

2.0 Volley的優缺點

Volley 有如下的優勢:
  • 自動調度網絡請求
  • 高併發網絡鏈接
  • 經過標準的 HTTP cache coherence(高速緩存一致性)緩存磁盤的內存透明的響應
  • 支持指定請求的優先級
  • 撤銷請求 API,或者指定取消請求隊列中的一個區域
  • 框架容易被定製。例如,定製重試或者回調功能
  • 強大的指令(Strong ordering)能夠使得異步加載網絡數據並正確地顯示到 UI 的操做更加簡單
  • 包含了調試與追蹤工具
Volley 的缺點:
  • 不適合用來下載大的數據文件

3.0 Volley的網絡請求隊列

使用 Volley 的流程是,建立一個 RequestQueue(請求隊列)對象,而後把 Request(請求)提交給它。github

// 代碼[1]

final TextView textView = (TextView) MainActivity.this.findViewById(R.id.tv);

//[1.0]建立 RequestQueue 的實例
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this); // 1

String url = "http://gank.io/api/data/Android/10/1";

//[2.0]構造一個 request(請求)
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        textView.setText("Response is: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        textView.setText("Error is happenning");
    }
});

//[3.0]把 request 添加進請求隊列 RequestQueue 裏面
requestQueue.add(request);複製代碼

上面代碼邏輯主要是經過構造一個 StringRequest 的實例,而後把這個實例添加進請求隊列 RequestQueue 裏面。咱們來查看註釋 1 的 Volley.newRequestQueue(Context) 方法的源碼:編程

// 源碼[2]

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);  // 1

        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) {  // 2
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); // 3
        queue.start(); // 4

        return queue;
    }

public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}複製代碼

從上面的源碼能夠看出,newRequestQueue(Context)newRequestQueue(Context, HttpStack) 的重載方法,下面咱們主要來看 newRequestQueue(Context, HttpStack) 方法,在註釋 1 處經過 new File(File, String) 初始化構建一個 cacheDir 的緩存(這是一個名詞),在註釋 3 處,new DiskBasedCache(cacheDir) 爲這個緩存分配了 5M 的存儲空間;
回到註釋 2, 當 SDK 的版本大於或等於 9 的時候,也就是 Android 的版本號大於或等於 2.3,則建立基於 HttpURLConnectionHurlStack,不然就建立基於執行請求的 HttpClientHttpClientStack,而後在註釋 3 處,經過 new RequestQueue(Cache, Network) 建立請求隊列,咱們來查看下 new RequestQueue(Cache, Network) 的源碼:api

// 源碼[3]

    ...

private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    ...

public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }
    ...複製代碼

構造方法 new RequestQueue(Cache, Network) 爲網絡請求分配了 4 條線程進行請求。對於 RequestQueue 這個類,主要的做用是做爲一個線程池用於調度隊列中的請求:當調用 add(Request) 將會把傳進的請求(Request)在緩存隊列(cache)或者網絡隊列(network)中解析,而後傳遞迴主線程,也就是 代碼[1] 裏面的回調函數 onResponse(String)onErrorResponse(VolleyError) 拿到回調的請求內容;
在 代碼[1] 的註釋 4 處,調用了 add(Request) 方法:緩存

// 源碼[4]

public <T> Request<T> add(Request<T> request) {
        request.setRequestQueue(this);
        //同步代碼塊,保證 mCurrentRequests.add(request) 在一個進程裏面的全部線程裏面,有且只能在
        //一個線程裏面執行
        synchronized (mCurrentRequests) { 
            mCurrentRequests.add(request);
        }

        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        //若是不能夠存儲,就把請求(request)添加進網絡調度隊列裏面
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);  
            return request;
        }

        //若是能夠存儲:
        //同步代碼塊
        synchronized (mWaitingRequests) {  
            String cacheKey = request.getCacheKey();
            //若是以前有相同的請求而且尚未返回結果的,就把此請求加入到 mWaitingRequests 裏面
            if (mWaitingRequests.containsKey(cacheKey)) {   
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                //若是沒有請求在進行中,從新初始化 Queue<Request<?>> 的實例
                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 {
                //若是沒有的話,就把請求添加到 mCacheQueue 隊列裏面
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);  
            }
            return request;
        }
    }
}複製代碼

從上面的 add(Request) 方法的源碼能夠看出,主要的邏輯是:若是請求(request)不能夠緩存,就直接添加進緩存隊列,不然添加進緩存隊列; 當拿到 mNetworkQueuemCacheQueue 之後,就把請求返回而且調用 start() 方法(如 源碼[2] 的註釋 4),當查看 start() 方法的源碼時候:網絡

// 源碼[5]

public void start() {
        stop();  // 確保當前的緩存調度和網絡調度都已經中止
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }複製代碼

裏面主要是建立 CacheDispatcher(BlockingQueue<Request<?>>, BlockingQueue<Request<?>>, Cache, ResponseDelivery) 的實例並經過 mCacheDispatcher.start() 開啓緩存調度線程,和建立 NetworkDispatcher(BlockingQueue<Request<?>>,Network, Cache,ResponseDelivery) 的實例並經過 networkDispatcher.start() 開啓網絡調度線程。併發

4.0 網絡調度線程NetworkDispatcher

網絡調度線程 NetworkDispatcher 是一個繼承於 Thread 的線程,經過查看其任務方法 run() 的源碼:app

// 源碼[6]

@Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            try {  
                // 從網絡隊列中取出請求 
                request = mQueue.take();
            } catch (InterruptedException e) {           
                if (mQuit) {
                    return;
                }
                continue;
            }

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

                // 若是請求被取消
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

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

                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // 把網絡請求到的實體緩存到 源碼[2] 的註釋 1 處的本地緩存文件裏面
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                request.markDelivered();
                // 回調請求到的響應給主線程
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                ...

            } catch (Exception e) {
                ...

            }
        }
    }複製代碼

網絡調度的主要邏輯是,先判斷網絡請求有沒有被取消,若是沒有被取消,就經過 mNetwork.performRequest(request) 執行請求網絡響應,拿到響應之後,先緩存在本地,而後再回調給主線程。框架

5.0 緩存調度線程CacheDispatcher

緩存調度線程 CacheDispatcher 的源碼以下所示:

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

        mCache.initialize();

        while (true) {
            try {            
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                //若是請求被取消
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 嘗試在本地緩存中取回數據
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    //本地緩存丟失或者沒有
                    request.addMarker("cache-miss");                   
                    mNetworkQueue.put(request);
                    continue;
                }

                // 本地緩存過時
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // 命中緩存
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // 回調相應給主線程
                    mDelivery.postResponse(request, response);
                } else {

                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    response.intermediate = true;

                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                ...
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                ...
            }
        }
    }複製代碼

一樣地,若是請求沒有被取消,就開始在取回本地的緩存,當本地的緩存不存在、丟失或者已通過期,就把請求添加到網絡請求隊列,當命中本地緩存,就把緩存的響應回調給主線程;

6.0 Volley原理解析

爲了發送一個請求,你只須要構造一個請求並經過 add() 方法添加到 RequestQueue 中。一旦添加了這個請求,它會經過隊列,經過一系列的調度,而後獲得原始的響應數據並返回。

當執行 add()方法時,Volley觸發執行一個緩存處理線程以及一系列網絡處理線程。當添加一個請求到隊列中,它將被緩存線程所捕獲並觸發: 若是這個請求能夠被緩存處理,那麼會在緩存線程中執行響應數據的解析並返回到主線程。若是請求不能被緩存所處理,它會被放到網絡隊列中。網絡線程池中的第一個可用的網絡線程會從隊列中獲取到這個請求並執行HTTP操做,解析工做線程的響應數據,把數據寫到緩存中並把解析以後的數據返回到主線程。

一個請求的生命週期

The lifecycle of a request
The lifecycle of a request

小結

到此,Volley 庫的分析就到先到此暫時結束了。此次,筆者主要是就當一個請求添加進請求隊列以後,到其返回響應的過程進行了分析,瞭解到了 Volley 是如何把一個請求進行調度,而後獲得響應並返回的。哈哈,感謝你的閱讀...


源碼下載 TestVolley

相關文章
相關標籤/搜索