Volley 源碼分析

Volley概述
Volley是Google 2013年I/O大會推出的針對Android的HTTP網絡請求框架,讓網絡請求更簡單,更快。

今天我們來點乾貨 分析下Volley源碼 不介紹Volley基本用法 概念等等
因爲 張鴻洋 郭霖 等大神
博客都寫的很清晰了 非常通俗易懂 贊!

IDE AndroidStudio 直接在AS上分析就行

開始分析

1.緩存文件與網絡

我們一個 mRequestQueue 是通過 newRequestQueue拿到的 所以我們直接點進去
這裏寫圖片描述
他裏面調用的是這個方法 直接點
這裏寫圖片描述
點擊進去 我們看到了這樣一個方法

 private static final String DEFAULT_CACHE_DIR = "volley";

    /**
     * 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.
     * @param stack An {@link HttpStack} to use for the network, or null for default.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        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 = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

不要慌 我們一行一行分析 看看這個方法到底做了些什麼 從第一行開始

Volley 默認的緩存地址是從這裏設置的

 File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

首先我們先來看 這個局部變量的名字 cacheDir 緩存的的文件夾

getCacheDir 指向於 data/data/包名/cache/ 加DEFAULT_CACHE_DIR ==volley
這裏就得到了Volley的緩存地址爲 data/data/包名/cache/volley

Volley官方文檔說明 Volley 易於定製 擴展性強 所以說我們可以隨便改源碼

我們拿到了緩存文件夾 在來看他用在哪裏了

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

通過代碼我們看到他傳給了 DiskBasedCache
每一個響應有一個緩存文件 而這些都是由DiskBasedCache 來提供支持

官方文檔說明 你的RequestQueue 需要兩個東西去維持他的工作 緩存與網絡
現在我們拿到了緩存

網絡

 Network network = new BasicNetwork(stack);

由BasicNetwork 來提供網絡的傳輸
BasicNetwork 可以根據你喜歡的http client 來選擇

官方說明

這裏寫圖片描述


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

接着繼續看stack Volley內部做了個判斷 如果你的當前SDK版本大於9(2.3.3)則使用 HurlStack HttpURLConnection
如果小於2.3 則使用 HttpClientStack

源碼中有個鏈接是 谷歌開發人員的一個博客 介紹了利弊
這裏寫圖片描述
大概意思就是說在2.3以前用HttpClient最好 因爲BUG少
2.3之後的HttpURLConnection 是最好的選擇 其簡單的API和代碼量很少使其非常適合Android 透明的壓縮和緩存緩存減少網絡使用,提高速度並節省電池
HttpClient都被google廢棄了 在6.0 SDK中直接拿掉了

接着又回到上頭 我們說過可以自定義Stack 選擇自己喜歡的http 但其實這個參數 一般都爲空 …
倒序回到調用
這裏寫圖片描述
Stack 默認爲null
這裏寫圖片描述
我們傳的是單參數的方法 傳到源碼就是一個上下文 所以說還是會走系統默認 通過SDK自行選擇
mRequestQueue = Volley.newRequestQueue(this);

我們的volley 是通過一個 queue.start()方法開啓的
這裏寫圖片描述

開啓

點進start 我們看到了這些代碼

/**
     * 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();
        }
    }

我們看到了 在start前他先進行了stop 其實這是一種很好的代碼實現方式
避免你這個應用被start兩次
接着創建緩存調度程序並啓動它。
爲什麼這麼說 new CacheDispatcher 緩存線程 我們點進去看下

 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();
        }

我們來找一找 for循環的length值爲多少吧 點擊mDispatchers
Shift+F 全局查找
這裏寫圖片描述
來到了這裏 threadPoolSize 是通過構造方法傳過來的
Alt+F7 查看RequestQueue在哪裏被調用了

這裏寫圖片描述
到了這 在快捷鍵
這裏寫圖片描述
找到了調用地址 雙擊點進去
這裏寫圖片描述
上面可能有點花眼 重點記住快捷鍵的使用

也就是說當queue.start() 開啓的時候我們 創建了一個緩存線程 並創建了
4個網絡線程 並且分別把它們給開啓

12.30了 睡覺了 寫博客真費時間…. 好在是週末

我們接着回到緩存線程 CacheDispatcher繼承於 Thread 肯定會執行run方法
這裏寫圖片描述

我們來看看 緩存線程的run方法都幹了些什麼

 @Override
    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();

        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                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.
                    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;
            }
        }
    }

我們看到 他維護了一個while true 的死循環

 // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

從mCacheQueue 中拿到request
他這個死循環不是一直運行的而是一個阻塞式運行take()
如果有我就取 沒有就等着

  // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

他拿到了request 做了一個判斷你的request是不是被人給取消掉了 如果取消掉了就不做任何操作 如果沒有接着往下走

 // 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;
                }

通過 request.getCacheKey() 嘗試從緩存的集合中拿到他緩存的數據
getCacheKey getUrl 通過這個url服務器地址爲一個key 進行判斷

/**
     * Returns the cache key for this request.  By default, this is the URL.
     */
    public String getCacheKey() {
        return getUrl();
    }

如果沒有request緩存數據 就把這個request添加到網絡隊列裏

 mNetworkQueue.put(request);

如果有緩存數據則不會走之前的if判斷 就走到了這裏

  // 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;
                }

entry.isExpired() 判斷你緩存數據是否過期了 如果過期
接這把你添加到網絡對列裏 mNetworkQueue.put(request); 等待網絡請求

如果有緩存並且沒有過期就接着往下走

   Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));

解析與響應 他把你緩存的數據存進去 並最終拿到了response 想要的數據
Volley可以把原生的響應數據 entry.data 解析成你想要的數據 也就是io流帶過來的二進制

接着繼續走到了這句

  mDelivery.postResponse(request, response);

把request響應的數據分發到主線程 這就是緩存大概流程

接着我們看網絡線程的run方法
這裏寫圖片描述
網絡線程的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);

                // 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);
            }
        }
    }

首先看到的依舊是一個 阻塞 死循環 依舊是從一個mQueue取request

 // Take a request from the queue.
                request = mQueue.take();

也做了一個判斷 取消了不做任何操作

 if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

沒取消就走到了這裏 去執行一個網絡請求 拿到了networkResponse

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

如果沒有304異常就把networkResponse 解析成了一個response

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

如果我的請求是被需要緩存的request.shouldCache() 那就寫緩存
mCache.put(request.getCacheKey(), response.cacheEntry);

寫完了緩存就走到了這裏 分發到主線程

 mDelivery.postResponse(request, response);

我們來看看postResponse怎麼發送到主線程的把
這裏寫圖片描述

我們來到了這裏 正常的線程池execute 裏面應該是開線程 處理事件 Volley重寫了這個方法 直接分發到主線程

這裏寫圖片描述

因此,我們的Response響應最終會在主線程被調用方接收並進行處理。

總結圖片

這裏寫圖片描述

Volley只是個框架從源碼就能看到 最底層的操作還是由 HttpClient HttpURLConnection 來實現的 其實選擇網絡可以改成okhttp 可自行改造 自由度非常高

最後我感覺我寫的挺清晰了 有什麼不對的地方 希望留言 我會進行修改 詳細到寫的我快吐血了….

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章