Volley源碼分析

一、Volley框架圖


    根據圖簡單猜測Volley工作的流程,見右下角的註釋,藍色表示主線程(main thread),綠色表示緩存線程(cache thread),黃色表示網絡線程(network threads);
再尋找圖中的關鍵字:queue(RequestQueue),cache queue,CacheDispatcher,NetworkDispatcher;
    流程可簡單那描述爲:RequestQueue的add()操作將Request添加到緩存隊列cache queue中。CacheDispatcher將Request從queue中取出,如果發現緩存中已經保存了相應的結果,則直接從緩存中讀取並解析,將response結果回調給主線程。如果緩存中未發現,則將Request添加到網絡隊列中,進行相應的HTTP transaction等事務處理,將網絡請求的結果返回給主線程。

二、Volley系統流程設計圖


    DispatchThread(Cache層對應CacheDispatcher,Network層對應NetworkDispatcher),不斷從RequestQueue獲取用戶請求,根據是否已經存儲在Cache中分別從內存緩存或服務器中來請求數據,然後交由ResponseDelivery進行結果分發和回調處理。

三、上面涉及到的RequestQueue,ResponseDelivery,CacheDispatcher,NetworkDispatcher等概念,對其作用做了簡單總結:

Volley                   :Volley 對外暴露的 API,類中只有兩個函數

                                通過 newRequestQueue(…) 函數新建並啓動一個請求隊列RequestQueue

Request<T>          :表示一個請求的抽象類。

                                StringRequestJsonRequestImageRequest 都是它的子類,表示某種類型的請求。

                                也可自定義自己的Request

RequestQueue      :表示請求隊列,一個RequestQueue對象包含:

                               一個CacheDispatcher(用於處理走緩存請求的調度線程)、

                               一個NetworkDispatcher數組(默認數組大小爲4,用於處理走網絡請求的調度線程),

                               一個ResponseDelivery(返回結果分發接口),

                               在start() 函數啓動時會創建啓動CacheDispatcherNetworkDispatchers

CacheDispatcher   :Cache層中的一個線程,用於調度處理緩存的請求。

                               啓動後會不斷從緩存請求隊列中取請求處理,隊列爲空則等待,請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理。

                               當結果未緩存過、緩存失效或緩存需要刷新的情況下,該請求都需要重新進入NetworkDispatcher去調度處理。

NetworkDispatcher:NetWork層中的一個線程,用於調度處理走網絡的請求。

                                啓動後會不斷從網絡請求隊列中取請求處理,隊列爲空則等待,

                                請求處理結束則將結果傳遞給ResponseDelivery去執行後續處理,並判斷結果是否要進行緩存。

ResponseDelivery :返回結果分發接口,在創建RequestQueue對象時進行了初始化

                              在目前只有基於ExecutorDelivery的在入參 handler 對應線程內進行分發。

HttpStack             :處理 Http 請求,返回請求結果。在newRequestQueue中被初始化。

                              目前 Volley 中有基於 HttpURLConnection 的HurlStack和 基於 Apache HttpClient 的HttpClientStack

                              上一篇中已經對其如何根據Android版本進行選擇做了解析。

Network               :調用HttpStack處理請求,並將結果轉換爲可被ResponseDelivery處理的NetworkResponse

                               在newRequestQueue中被初始化

Cache                   :緩存請求結果,Volley 默認使用的是基於 sdcard 的DiskBasedCache

                  NetworkDispatcher得到請求結果後判斷是否需要存儲在 Cache,CacheDispatcher會從 Cache 中取緩存結果。

下面附上每個類之間的關係圖:


四、源碼分析:
    由上圖可以得出流程圖的入口在於RequestQueue的add()方法,先從RequestQueue的創建看起:
(一)RequestQueue的使用
    RequestQueue mRequestQueue = Volley.newRequestQueue(this);
看一下Volley.newRequestQueue的事務邏輯,Volley類中總共就兩個方法:
    /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
代碼的事務主體在這裏:
    /** Default on-disk cache directory. */
    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) {
        //創建cache
        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) {
        }
 
        /** 根據博文http://blog.csdn.net/guolin_blog/article/details/12452307,HurlStack是用HttpURLConnection實現的;
            HttpClintStack是由HttpClient實現的;由Android2.3之前的版本宜使用HttpClient,因爲其Bug較少;
            Android2.3之後版本宜使用HttpURLConnection,因其較輕量級且API簡單;
            故會有此HurlStack和HttpURLConnection的使用分類 */
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //創建以stack爲參數的Network對象
        Network network = new BasicNetwork(stack);
        //創建RequestQueue對象
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();//繼續向下分析的入口
 
        return queue;
    }
附I) 、HurlStack中的部分代碼,可以看出其是基於HttpURLClient實現的:
    private static HttpEntity entityFromConnection(HttpURLConnection connection)
    對應的HttpClientStack的構造函數可以看出其實基於HttpClient實現的:    
   public HttpClientStack(HttpClient client) {
        mClient = client;
    }
    而兩者都是基於HttpStack接口的:
    /** An HTTP stack abstraction.*/
    public interface HttpStack {
        public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError; 
    }

   由於Android 2.3版本之前,因爲HttpURLConnection的BUG較多,HttpClient的API已經較完備,故宜使用HttpClient,故這裏版本9之前,選擇使用HttpClientStack;

 Android2.3之後版本,HttpURLConnection不斷髮展,因其較爲輕量級,且API使用較爲簡單,其也在不斷優化性能等,故這裏使用基於其的HurlStack;


附II) 這裏引出一個Network對象,看一下構造函數,其用以處理stack傳來的網絡請求,與主線關係不大,可以不看
/**
 * A network performing Volley requests over an {@link HttpStack}.
 */
public class BasicNetwork implements Network {
    ...   
    private static int DEFAULT_POOL_SIZE = 4096; 
    protected final HttpStack mHttpStack; 
    protected final ByteArrayPool mPool;
 
    public BasicNetwork(HttpStack httpStack) {
        // If a pool isn't passed in, then build a small default pool that will give us a lot of
        // benefit and not use too much memory.
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    } 
    /**
     * @param httpStack HTTP stack to be used
     * @param pool a buffer pool that improves GC performance in copy operations
     */
    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }
    ...
}
    保存了創建的stack,並創建一個字節數組池(ByteArrayPool

附III) 回到重要的RequestQueue,其構造函數:
    /** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
 
    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }
   
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
   
    /**
     * 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;
    }
    在這裏創建了之前分析中一個重要的對象:NetworkDispatcher;並且可以看到其類似線程池似的,創建了大小爲threadPoolSize的NetworkDispatcher數組;其中的處理邏輯暫且不看,首先可以知道其是一個線程:
<span style="font-size:14px;">    public class NetworkDispatcher extends Thread</span>
    總結第一部RequestQueue中add方法所作的工作:
1)創建了Cache;
2)創建了HttpStack,並由HttpStack爲基創建了Network對象;
3)創建RequestQueue對象,並在RequestQueue構造函數中創建了大小爲threadPoolSize的NetworkDispatcher數組(注並未創建相應NetworkDispatcher對象)
4)創建ResponseDelivery對象(new ExecutorDelivery(new Handler(Looper.getMainLooper()))
5)調用RequestQueue.start()函數

(二)從start方法看起:
1、RequestQueue.start():
    /**
     * Starts the dispatchers in this queue.
     */
    publicvoid 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();
        }
    }

    /** Stops the cache and network dispatchers.*/
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }
    start()依然在做初始化,可以看到創建了一個CacheDispatcher線程(它也是繼承Thread的);又創建了threadPoolSize(默認爲4)個NetworkDispatcher線程;則start()後加上主線程,一共有六個線程在運行;回顧之前的流程圖,黃色、綠色、藍色對應的線程都已集齊;黃色線程和綠色線程運行下後臺一直在等待網絡Request並進行dispatch;
    則下面學習的主體落到了兩個主要的處理線程CacheDispatcher和NetworkDispathcer上來;試了下,直接看源代碼有些困難;先把之前使用Volley的流程走一遍;創建好RequestQueue之後,是創建自己的Request,前面文章已經做了學習;而後是將request通過RequestQueue的add()方法添加進來;

2、下面看一下RequestQueue.add()方法,它是前面流程圖運行的入口函數
   /**
     * The set of all requests currently being processed by this RequestQueue. A Request
     * will be in this set if it is waiting in any queue or currently being processed by any dispatcher.
     */
    private final Set<Request> mCurrentRequests = new HashSet<Request>();
    
    /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public Request add(Request request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);  //見附I Request設置其對應的RequestQueue
        synchronized (mCurrentRequests) { //mCurrentRequests表示當前該RequestQueue持有的requests,由HashSet來保存
            mCurrentRequests.add(request);
        }
 
        // 爲新添加的request進行一系列的初始化設置
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");
 
        // 見附II 判斷request是否允許緩存
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
 
        //request如果允許緩存 
        //Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {  // 見附III
            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;
        }
    }
附:mCurrentRequests維護了一個正在進行中,尚未完成的請求集合。
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
附I)、Request.setRequestQueue() 字面上可以看出是Request設置其對應的RequestQueue,簡單的setter函數:
    /** The request queue this request is associated with. */
    private RequestQueue mRequestQueue;   
    /**
     * Associates this request with the given queue. The request queue will be notified when this
     * request has finished.
     */
    public void setRequestQueue(RequestQueue requestQueue) {
        mRequestQueue = requestQueue;
    }

附II)request.shouldCache()用以判斷該request是否允許緩存(默認允許,可使用setShouldCache(false)來禁止緩存);如果不允許緩存,則直接將其添加到mNetworkQueue中返回。
    /** The queue of requests that are actually going out to the network. */
    private final PriorityBlockingQueue<Request> mNetworkQueue = new PriorityBlockingQueue<Request>();
RequetQueue其實並不是一個真正的Queue,真正存儲Request供處理線程去讀取和操作的Queue是mNetworkQueue,其類型是PriorityBlockingQueue;

附III)mWaitingRequests

    /**
     * Staging area for requests that already have a duplicate request in flight.
     * <ul>
     *     <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache
     *          key.</li>
     *     <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request
     *          is <em>not</em> contained in that list. Is null if no requests are staged.</li>
     * </ul>
     */
    private final Map<String, Queue<Request>> mWaitingRequests = new HashMap<String, Queue<Request>>();

維護了一個等待請求的集合,如果一個請求正在被處理並且可以被緩存,後續的相同 url 的請求,將進入此等待隊列。
函數:containsKey(cacheKey):  true表明對於給定的cache key,已經存在了一個request
         get(cacheKey)            :  返回對於給定cache key對應的waiting requests,即Queue<Request>
其存儲request的整個工作流程爲:

1)對於每個新add的request,先獲取它的CacheKey;

2)如果當前mWaitingRequests不存在當前cachekey,則會put(cacheKey, null);null表示當前Map中已經存在了一個對應cacheKey的請求;

3)如果mWaitingRequests已經存在了對應的cacheKey,通過get(Key)獲取cacheKey對應的Queue;如果Queue爲null,由第二步知,當前cacheKey僅僅對應一個request,則新建對應的Map Value值——Queue<Request>(這裏由LinkedList來實現),然後添加進去即可;

附IV)
mCacheQueue
mNetworkQueue是想對應存在的:
mCacheQueue 放在緩存請求隊列中的 Request,將通過緩存獲取數據;

mNetworkQueue放在網絡請求隊列中的 Request,將通過網絡獲取數據。
private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();


****************************************************************** 下面是彙總,具體參看剩下兩篇 **********************************************************

一、Volley工作流程圖:


繼續從CacheDispatcher和NetworkDispatcher開始看起。


二、CacheDispatcher:

    一個線程,用於調度處理走緩存的請求。啓動後會不斷從緩存請求隊列中取請求處理,隊列爲空則等待,請求處理結束則將結果傳遞給ResponseDelivery 去執行後續處理。當結果未緩存過、緩存失效或緩存需要刷新的情況下,該請求都需要重新進入NetworkDispatcher去調度處理。

(一)看源碼前,先看一下從其成員變量與處理流程

(1). 成員變量

BlockingQueue<Request<?>> mCacheQueue     緩存請求隊列
BlockingQueue<Request<?>> mNetworkQueue 網絡請求隊列
Cache mCache                                                緩存類,代表了一個可以獲取請求結果,存儲請求結果的緩存
ResponseDelivery mDelivery                        請求結果傳遞類

(2). 處理流程圖


(3)源碼:

1、構造函數:一系列賦值初始化操作

    /**
     * Creates a new cache triage dispatcher thread.  You must call {@link #start()}
     * in order to begin processing.
     */
    public CacheDispatcher(BlockingQueue<Request> cacheQueue, BlockingQueue<Request> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }
 提到使用CacheDispatcher時一定要調用start()方法;而CacheDispatcher的創建與線程start都是在RequestQueue中的add()函數中實現的:

    // Create the cache dispatcher and start it.
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();
2、既然是線程,重點看其run()函數:

    @Override
    public void run() {
        //設置優先級
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // Make a blocking call to initialize the cache.
        //這裏的Cache其實是DiskBasedCache,見附I
        mCache.initialize();
 
        /*****循環處理邏輯******/
        while (true) {
            try {
                // 從緩存隊列 mCacheQueue中取出一個Request;如果mCacheQueue爲空,則阻塞進行忙等待
                final Request request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
 
                // 如果取出的Request請求已經被取消,則直接finish,處理下一個request
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
 
                // 嘗試從緩存中獲取request對應的結果
                Cache.Entry entry = mCache.get(request.getCacheKey());
               
                //爲null,表示該cacheKey對應緩存結果不存在,則直接將request添加到mNetworkQueue中
                if (entry == null) {
                    request.addMarker("cache-miss");
                    mNetworkQueue.put(request);
                    continue;
                }
 
                //如果緩存結果存在,但是已過期,同樣也是將request添加到mNetworkQueue中
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }
 
                // 如果命中(hit)找到了對應的緩存結果,則解析其數據爲Response並返回給該request
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");
 
                // 還需判斷緩存結果是否時間過久已經不新鮮,是否需要refresh
                if (!entry.refreshNeeded()) {
                  // 不需要Refresh,則直接由mDelivery提交給相應的request
                    mDelivery.postResponse(request, response);
                } else {
                    // 如果已經不新鮮,mDelivery依舊提交結果給request,
                  // 但同時要將Request傳遞給mNetworkQueue進行新鮮度驗證
                    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) {
                            }
                        }
                    });
                }
 
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
 
附I)這裏的Cache實際是DiskBasedCache,mCache.initialize()實際上調用的是DiskBasedCache.initialize():

    /**
     * Initializes the DiskBasedCache by scanning for all files currently in the
     * specified root directory. Creates the root directory if necessary.
     */
    public synchronized void initialize()

三、NetworkDispatcher

    一個線程,用於調度處理網絡的請求。啓動後會不斷從網絡請求隊列中取請求處理,隊列爲空則等待,請求處理結束則將結果傳遞給 ResponseDelivery 去執行後續處理,並判斷結果是否要進行緩存。

(1). 成員變量

BlockingQueue<Request<?>> mQueue 網絡請求隊列
Network mNetwork                             網絡類,代表了一個可以執行請求的網絡
Cache mCache                                    緩存類,代表了一個可以獲取請求結果,存儲請求結果的緩存
ResponseDelivery mDelivery           請求結果傳遞類,可以傳遞請求的結果或者錯誤到調用者

(2). 處理流程圖



(3).先看其構造函數,與CacheDispatcher的邏輯處理大致相同:
    /**
     * Creates a new network dispatcher thread.  You must call {@link #start()}
     * in order to begin processing.
     */
    public NetworkDispatcher(BlockingQueue<Request> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }
(4).其run()函數:

    @Override
    public void run() {
    //設爲後臺進程
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request request;
        /*********循環處理邏輯********/
        while (true) {
            try {
                // 從網絡請求隊列中取出request,同理mQueue爲空時,也是忙等待
                request = mQueue.take();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
                continue;
            }
 
            try {
                request.addMarker("network-queue-take");
 
                // 如果取出的Request請求已經被取消,則直接finish,處理下一個request
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }
 
                // Tag the request (if API >= 14)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
                }
 
                // 通過Network執行Request,獲得NetworkResponse,故網絡請求的處理邏輯應該都封裝在了Network中
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
 
                // 用來驗證新鮮度,(notModified=304)響應爲304且請求已經有了Response傳輸情況
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }
 
                // 將NetworkResponse解析爲Response
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");
 
                // 如果request可以被緩存,並且其請求實體補位空,則添加到mCache中
                // 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");
                }
 
                // 傳輸Response
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                mDelivery.postError(request, new VolleyError(e));
            }
        }
    }


一、Volley工作流程圖:


二、Network
    在NetworkDispatcher中需要處理的網絡請求,由下面進行處理:
    NetworkResponse networkResponse = mNetwork.performRequest(request);
看一下mNetwork的定義:(定義在NetworkDispatcher中)

    /** The network interface for processing requests. */
    private final Network mNetwork;
NetworkDispatcher.mNetwork初始化發生在RequestQueue.start()中:

    NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
         mCache, mDelivery);
而RequestQueue.mNetwork是在其構造函數中傳入的:

    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }
由前面分析知RequestQueue的構建是在Volley.newRequestQueue中實現的:

    //創建以stack爲參數的Network對象
    Network network = new BasicNetwork(stack);
    //創建RequestQueue對象
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();//繼續向下分析的入口

    可以看出mNetwork其實是BasicNetwork對象。
    則NetworkResponse中mNetwork實際上調用的是BasicNetwork.performRequest(),這是一個專門用來處理網絡請求的函數,其作用爲調用HttpStack處理請求,並將結果轉換爲可被ResponseDelivery處理的NetworkResponse
看一下其源碼:
   @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = new HashMap<String, String>();
            try {
              /** 忽略網絡處理的細節*/
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
               
                /**執行網絡請求
                 * 這裏調用了HttpStack.performRequest,並得到一個HttpResponse返回結果*/
                httpResponse = mHttpStack.performRequest(request, headers);
               
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
 
                /**新鮮度驗證:
                 * 304 Not Modified:客戶端有緩衝的文件併發出了一個條件性的請求
                 * (一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。
                 * 服務器告訴客戶,原來緩衝的文檔還可以繼續使用。*/
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                  /** 解析成NetworkResponse,返回*/
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                            request.getCacheEntry().data, responseHeaders, true);
                }
 
                // 判斷responses是否有實體信息,一些響應如204,並不包含content,所以需要驗證
                if (httpResponse.getEntity() != null) {
                  //實體信息轉化成byte[]
                    responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // 無實體信息情況
                  responseContents = new byte[0];
                }
 
                // 超時情況處理.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);
 
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                ...
            }
        }
    }

 總結一下Network.performRequest所做的工作:

1、由傳入的HttpStack對象執行網絡請求:mHttpStack.performRequest()

2、解析響應結果,將HttpResponse解析成NetworkResponse;

3、對返回結果進行新鮮度驗證(304)

4、將response的實體信息轉化爲byte數組

5、超時情況處理,如果發生超時,認證失敗等錯誤,進行重試操作(attemptRetryOnException),直到成功、拋出異常(不滿足重試策略等)結束。


attemptRetryOnException()是根據重試策略進行請求重試操作:

    /**
     * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
     * request's retry policy, a timeout exception is thrown.
     */
    private static void attemptRetryOnException(String logPrefix, Request<?> request,
            VolleyError exception) throws VolleyError {
        RetryPolicy retryPolicy = request.getRetryPolicy();
        int oldTimeout = request.getTimeoutMs();
 
        try {
            retryPolicy.retry(exception);
        } catch (VolleyError e) {
            request.addMarker(
                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
            throw e;
        }
        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }
三、HttpClientStack、HurlStack
    據上面源碼知,網絡請求處理的邏輯實際上是交由傳進來的參數HttpStack進行處理。前面已經分析過,Android2.3之前使用 HttpClientStack,之後使用HurlStack;
1、先看兩者的父類HttpStack:
    public interface HttpStack {
        /**
         * Performs an HTTP request with the given parameters.
         * <p>A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise,
         * and the Content-Type header is set to request.getPostBodyContentType().</p>
         * @param request the request to perform
         * @param 發起請求之前,添加額外的請求 Headers {@link Request#getHeaders()}
         */
        public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError;
    }
2、HttpClientStack(使用HttpClient來實現)

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);//見附一
        addHeaders(httpRequest, additionalHeaders);
        addHeaders(httpRequest, request.getHeaders());
        onPrepareRequest(httpRequest);// Nothing.空函數,用於重寫;該函數在request被excute之前被調用
        //一些網絡設置
        HttpParams httpParams = httpRequest.getParams();
        int timeoutMs = request.getTimeoutMs();
        // TODO: Reevaluate this connection timeout based on more wide-scale
        // data collection and possibly different for wifi vs. 3G.
        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
        return mClient.execute(httpRequest);
    }
附一:createHttpRequest函數:

    /**
     * 根據傳進來的request來構造合適的HttpUriRequest
     */
    static HttpUriRequest createHttpRequest(Request<?> request,
            Map<String, String> additionalHeaders) throws AuthFailureError {
        switch (request.getMethod()) {
            case Method.DEPRECATED_GET_OR_POST: {
                // This is the deprecated way that needs to be handled for backwards compatibility.
                // If the request's post body is null, then the assumption is that the request is
                // GET.  Otherwise, it is assumed that the request is a POST.
                byte[] postBody = request.getPostBody();
                if (postBody != null) {
                    HttpPost postRequest = new HttpPost(request.getUrl());
                    postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
                    HttpEntity entity;
                    entity = new ByteArrayEntity(postBody);
                    postRequest.setEntity(entity);
                    return postRequest;
                } else {
                    return new HttpGet(request.getUrl());
                }
            }
            /***********一般較多使用的是POST與GET,其等同於HttpClient的一般使用流程***************/
            case Method.GET:
                return new HttpGet(request.getUrl());
            case Method.DELETE:
                return new HttpDelete(request.getUrl());
            case Method.POST: {
                HttpPost postRequest = new HttpPost(request.getUrl());
                //這裏就看到了前面實現Request時,重寫getBodyContentType()函數的意義
                postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(postRequest, request);
                return postRequest;
            }
            case Method.PUT: {
                HttpPut putRequest = new HttpPut(request.getUrl());
                putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
                setEntityIfNonEmptyBody(putRequest, request);
                return putRequest;
            }
            default:
                throw new IllegalStateException("Unknown request method.");
        }
    }
3、HurlStack(由HttpURLConnection來實現)
    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        //UrlRewriter見附一
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                thrownew IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        /**************HttpURLConnection的一般使用流程*******************/
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            thrownew IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        response.setEntity(entityFromConnection(connection));
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }
附一:UrlRewriter

    /** 對URLs在使用前進行重寫轉換*/
    public interface UrlRewriter {
        /**
         * Returns a URL to use instead of the provided one, or null to indicate
         * this URL should not be used at all.
         */
        public String rewriteUrl(String originalUrl);
    }
參數mUrlRewriter通過HttpStack的構造函數傳入進來,故可以自行進行定義:
    public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
        mUrlRewriter = urlRewriter;
        mSslSocketFactory = sslSocketFactory;
    }

四、NetworkResponse
    回到起點NetworkDispatcher(Thread)中的run()函數,其中:

    NetworkResponse networkResponse = mNetwork.performRequest(request);

下面繼續看NetworkResponse的源碼:
NetworkResponse類很簡單,僅是用以在多個類中傳遞數據,其成員變量:
1)成員變量
int statusCode Http 響應狀態碼

byte[] data Body 數據
Map<String, String> headers 響應 Headers
boolean notModified 表示是否爲 304 響應
long networkTimeMs 請求耗時

2)其主體只爲幾個構造函數:
    public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
            boolean notModified) {
        this.statusCode = statusCode;
        this.data = data;
        this.headers = headers;
        this.notModified = notModified;
    }
 
    public NetworkResponse(byte[] data) {
        this(HttpStatus.SC_OK, data, Collections.<String, String>emptyMap(), false);
    }
 
    public NetworkResponse(byte[] data, Map<String, String> headers) {
        this(HttpStatus.SC_OK, data, headers, false);
    }
3)回顧一下前面分析的設計NetworkResponse的類之間數據的傳遞關係:



這裏的主體是根據NetworkDispatcher.run()函數進行分析的
0、函數中調用Network.performRequest();
     NetworkResponse networkResponse = mNetwork.performRequest(request);
     而Network.performRequest()是基於HttpStack實現的;
1、HttpClientStack與HurlStack(分別基於HttpClient與HttpURLConnection實現)中的public HttpResponse performRequest()函數返回HttpResponse ;
2、Network(實際爲BasicNetwork)中performRequest()方法,使用1中的兩個HttpStack類,獲取到其返回值HttpResponse,然後將其解析成爲NetworkResponse;
3、Request中 abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
    將NetworkResponse解析成Response;
    而該函數的調用是在NetworkDispatcher中的run()函數中調用的;                
4、在NetworkDispatcher.run()的最後一步:
    mDelivery.postResponse(request, response);
    將response傳遞給了ResponseDelivery
後面繼續看Delivery的邏輯;
ResponseDelivery mDelivery的實際類型是ExecutorDelivery:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
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);
        }
    };

}
可以看到很簡單,就是使用主線程的Looper構建一個Handler,下面所有的post操作都是調用這個Handler來執行Runnable;

比如:

@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
將傳遞來的Response轉化爲ResponseDeliveryRunnable ,顯然這是一個Runnable;

private class ResponseDeliveryRunnable implements Runnable {
    private final Request mRequest;
    private final Response mResponse;
    private final Runnable mRunnable;

    public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
        mRequest = request;
        mResponse = response;
        mRunnable = runnable;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        // If this request has canceled, finish it and don't deliver.
        if (mRequest.isCanceled()) {
            mRequest.finish("canceled-at-delivery");
            return;
        }

        // Deliver a normal response or error, depending.
        if (mResponse.isSuccess()) {
            mRequest.deliverResponse(mResponse.result);
        } else {
            mRequest.deliverError(mResponse.error);
        }

        // If this is an intermediate response, add a marker, otherwise we're done
        // and the request can be finished.
        if (mResponse.intermediate) {
            mRequest.addMarker("intermediate-response");
        } else {
            mRequest.finish("done");
        }

        // If we have been provided a post-delivery runnable, run it.
        if (mRunnable != null) {
            mRunnable.run();
        }
   }

}
在這個子線程中,轉而調用 Request來deliverResponse:

以StringRequest爲例,來看這個函數:

@Override
protected void deliverResponse(String response) {
    if (mListener != null) {
        mListener.onResponse(response);
    }
}
這個Listener就是自己在定義Request的時候聲明的ResponseListener,可以看到這個Listener工作在子線程中,所以如果要更新界面,注意使用Handler把消息傳遞主線程進行處理。

***************************************************** Volley圖片加載的實現 *******************************************************
Volley的圖片加載主要還是基於上面的原理來實現的,具體如下:
ImageLoader的使用:
//創建ImageLoader
imageLoader = new ImageLoader(httpUtils.getRequestQueue(), imageCache);
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
    mRequestQueue = queue;
    mCache = imageCache;

}
這裏面的ImageCache是自定義的:
// 獲取最大內存緩存大小
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
MAX_CACHE_SIZE = maxMemory / 8; // 定義爲應用最大緩存的1/8

mImageLruCache = new LruCache<String, Bitmap>(MAX_CACHE_SIZE){
    @Override
    protected int sizeOf(String url, Bitmap bitmap){
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }

};

// 創建ImageCache
imageCache = new ImageLoader.ImageCache() {
    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        mImageLruCache.put(url, bitmap);
    }

    @Override
    public Bitmap getBitmap(String url) {
        return mImageLruCache.get(url);
    }
};
使用LruCache來實現ImageCache接口,實現圖片的內存緩存:
public interface ImageCache {
    public Bitmap getBitmap(String url);
    public void putBitmap(String url, Bitmap bitmap);
}
加載圖片時的用法:

imageListener = ImageLoader.getImageListener(myImageView, default_pg, failed_pg);
imageLoader.get(imageUrl, imageListener);
來到ImageLoader#get:

public ImageContainer get(String requestUrl, final ImageListener listener) {
    return get(requestUrl, listener, 0, 0);

}

public ImageContainer get(String requestUrl, ImageListener imageListener,
                          int maxWidth, int maxHeight) {
    return get(requestUrl, imageListener, maxWidth, maxHeight, ImageView.ScaleType.CENTER_INSIDE);

}

public ImageContainer get(String requestUrl, ImageListener imageListener,
                          int maxWidth, int maxHeight, ImageView.ScaleType scaleType) {

    // 如果操作不是在主線程,則直接拋出異常
    throwIfNotOnMainThread();

    // 爲圖片的URL創建一個特定的cacheKey,注意這個cache還和圖片的大小及scaleType相關
    final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

    // 這裏會使用自定義的LruCache去獲取一個Bitmap實例
    Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
    // 如果緩存中已經存在,則直接返回
    if (cachedBitmap != null) {
        // Return the cached bitmap.
        ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
        imageListener.onResponse(container, true);
        return container;
    }

    // 如果緩存中不存在,則進行獲取
    ImageContainer imageContainer =
            new ImageContainer(null, requestUrl, cacheKey, imageListener);

    // 通知Observer這時可以使用默認的圖片
    imageListener.onResponse(imageContainer, true);

    // 判斷是否已經有了一個相同的請求在等待
    BatchedImageRequest request = mInFlightRequests.get(cacheKey);
    if (request != null) {
        // If it is, add this request to the list of listeners.
        request.addContainer(imageContainer);
        return imageContainer;
    }

    // 創建一個Request,重複之前的流程
    Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
            cacheKey);

    mRequestQueue.add(newRequest);
    mInFlightRequests.put(cacheKey,
            new BatchedImageRequest(newRequest, imageContainer));
    return imageContainer;
}
處理邏輯大致和前面的addRequest相同,首先判斷緩存中是否已經存在該url對應的bitmap,如果存在直接返回;如果不存在,先判斷是否已經有了一個相同的請求在等待,如果是,把這個請求添加到監聽者鏈表中;如果不存在,則創建一個Request<Bitmap>,添加到RequestQueue中,從網絡中去獲取;從網絡中獲取的流程和前面分析的相同。


先來看Request<Bitmap>:

protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight,
        ScaleType scaleType, final String cacheKey) {
    return new ImageRequest(requestUrl, new Listener<Bitmap>() {
        @Override
        public void onResponse(Bitmap response) {
            onGetImageSuccess(cacheKey, response);
        }
    }, maxWidth, maxHeight, scaleType, Config.RGB_565, new ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            onGetImageError(cacheKey, error);
        }
    });
}
實際上返回一個ImageRequest類型,來看其請求成功的響應:即把獲得的圖片存儲到緩存中;

protected void onGetImageSuccess(String cacheKey, Bitmap response) {
    // 把獲取到的圖片存儲到緩存中
    mCache.putBitmap(cacheKey, response);
    // 可以看到如果是多個相同請求在等待,則可以同時進行更新處理
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

    if (request != null) {
        // Update the response bitmap.
        request.mResponseBitmap = response;
        // Send the batched response
        batchResponse(cacheKey, request);
    }
}
最後NetWork執行的結果會封裝成NetWorkResponse,通過ResponseDelivery進行轉發,這個類最後會調用Request中deliverResponse方法:

@Override
protected void deliverResponse(Bitmap response) {
    mListener.onResponse(response);
}
這個Listener就是最初定義的ImageListener:

public static ImageListener getImageListener(final ImageView view,
        final int defaultImageResId, final int errorImageResId) {
    return new ImageListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            if (errorImageResId != 0) {
                view.setImageResource(errorImageResId);
            }
        }

        @Override
        public void onResponse(ImageContainer response, boolean isImmediate) {
            if (response.getBitmap() != null) {
                view.setImageBitmap(response.getBitmap());
            } else if (defaultImageResId != 0) {
                view.setImageResource(defaultImageResId);
            }
        }
    };
}

可以看到這裏最終給View空間設置了圖片,以上就是Volley實現圖片加載的流程。



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