Volley 源碼解析及對 Volley 的擴展(二)

Volley 源碼解析及對 Volley 的擴展系列的第一篇文章中,介紹了一種通過繼承 StringRequestJsonObjectRequest等自定義類,只需要重寫其中的一個方法,即可獲得網(wǎng)絡請求的耗時和網(wǎng)絡請求的結(jié)果,詳見第一篇文章

在這篇文章中將對 Volley 的源碼進行解析,只有真正去研究 Volley 的源碼之后,才會發(fā)現(xiàn) Volley 設計的真是太精妙了。面向接口編程 在 Volley 中體現(xiàn)的非常徹底。

創(chuàng)建 RequestQueue 請求隊列對象

了解 Volley 用法的人都知道,使用 Volley 進行網(wǎng)絡請求的第一步就是創(chuàng)建一個請求隊列 RequestQueue 對象,創(chuàng)建 RequestQueue 對象的代碼如下所示:

RequestQueue mQueue = Volley.newRequestQueue(this);

在 Volley 中是通過靜態(tài)工廠方法的方式創(chuàng)建 RequestQueue 對象的,Volley 類的源碼如下所示:

public class Volley {

    /** 默認的文件緩存路徑 */
    private static final String DEFAULT_CACHE_DIR = "volley";

    /**
     * 創(chuàng)建一個默認的請求隊列對象,并調(diào)用 {@link RequestQueue#start()} 方法啟動它。
     *
     * @param context 一個 {@link Context} 對象用于創(chuàng)建緩存文件對象
     * @param stack   一個用于執(zhí)行網(wǎng)絡請求的 {@link HttpStack} 對象,若為 null,則使用默認的網(wǎng)絡請求對象
     * @return 一個已經(jīng)啟動的 {@link RequestQueue} 對象
     */
    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 {
                // 在 Android SDK 9 之前,HttpUrlConnection 有 Bug,不可靠,所以使用 HttpClient
                // 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;
    }

    /**
     * 創(chuàng)建一個默認的請求隊列對象,并調(diào)用 {@link RequestQueue#start()} 方法啟動它。
     *
     * @param context 一個 {@link Context} 對象用于創(chuàng)建緩存文件對象
     * @return 一個已經(jīng)啟動的 {@link RequestQueue} 對象
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
}
  1. Volley 類中有兩個重載的靜態(tài)方法
public static RequestQueue newRequestQueue(Context context);

public static RequestQueue newRequestQueue(Context context, HttpStack stack);

第一個方法的實現(xiàn)調(diào)用了第二個方法,傳入的 HttpStack 對象為 null

  1. 通過包名和版本號創(chuàng)建 userAgent 對象
  2. 如果傳入的 HttpStack 對象為 null, 則根據(jù) SDK 版本號創(chuàng)建默認的 HttpStack 對象,若 SDK 版本號大于等于 9,則創(chuàng)建 HurlStack 對象(內(nèi)部使用 HttpUrlConnection 實現(xiàn));否則創(chuàng)建 HttpClientStack 對象(內(nèi)部使用 HttpClient 實現(xiàn))。
  3. 通過已經(jīng)創(chuàng)建的 HttpStack 對象創(chuàng)建一個 Network 具體實現(xiàn)類 BasicNetwork 的對象
  4. 通過 BasicNetwork 對象和 DiskBasedCache 磁盤緩存對象創(chuàng)建一個 RequestQueue 對象,并啟動。

創(chuàng)建一個 Request 對象

Request 是代表網(wǎng)絡請求的一個抽象類,其中有兩個抽象方法,子類必須實現(xiàn)這兩個方法:

/**
 * 子類必須實現(xiàn)此方法,用于解析網(wǎng)絡請求的響應,并返回合適的類型對象。這個方法會在一個
 * 工作線程中被調(diào)用(即不會在 UI 線程中調(diào)用此方法),如果此方法返回 null,則結(jié)果并不會被發(fā)送。
 *
 * @param response 來自于網(wǎng)絡請求的響應
 * @return 解析的結(jié)果,如果發(fā)生錯誤則返回 null
 */
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

/**
 * 子類必須實現(xiàn)這個方法,把解析的結(jié)果發(fā)送給監(jiān)聽器。其中 T 類型的參數(shù) response 要保證
 * 不可以為 null,如果解析失敗,則解析的結(jié)果不會通過此方法發(fā)送。
 *
 * @param response 通過 {@link #parseNetworkResponse(NetworkResponse)} 方法解析的結(jié)果
 */
abstract protected void deliverResponse(T response);
  1. 默認實現(xiàn) Request 的子類有:StringRequestJsonObjectRequest
    JsonArrayRequestImageRequest
  2. 我們也可以自定義一個實現(xiàn) Request 的類,實現(xiàn)上面兩個方法,將其加入到網(wǎng)絡請求隊列中進行網(wǎng)絡請求。在下一篇博客中將會舉兩個自定義 Request 的例子
  3. Volley 中包括 8 種 Http 網(wǎng)絡請求方式:GETPOSTPUTDELETEHEAD
    OPTIONSTRACEPATCH
  4. Request 類中包含了網(wǎng)絡請求的 url,請求方式,請求 Header,請求 Body 和請求的優(yōu)先級等信息。
  5. 以下三個方法也經(jīng)常被子類重寫
    /**
     * 返回一個 Map 類型的參數(shù),為這個請求添加網(wǎng)絡請求頭信息 Http Header。
     * 最常用的就是可以把 Cookie 信息通過此方法添加
     */
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

    /**
     * 返回一個字節(jié)數(shù)組的對象作為 POST 或 PUT 請求的 Body 內(nèi)容。
     *
     * 當重寫此方法時,也需要考慮重寫 {@link #getBodyContentType()} 方法
     */
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    /**
     * 在 {@link #getBody()} 沒有被重寫的情況下,可以通過此方法返回一個
     * Map 類型的參數(shù),用于構(gòu)建 POST 或 PUT 請求方式的 Body 內(nèi)容
     *
     * 注意:也可以通過直接重寫 {@link #getBody()} 方法自定義 Body 數(shù)據(jù)。
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }

RequestQueue 源碼分析

RequestQueue 是 Volley 中的核心類,主要用于處理添加進來的網(wǎng)絡請求。
在本小節(jié)中將會分三部分介紹 RequestQueue 的類,分別是:RequestQueue 中的主要屬性、RequestQueue 類的構(gòu)造方法和 RequestQueue 的主要方法。

RequestQueue 中的主要屬性


    /**
     * 維護了一個等待請求的集合,如果有一個請求正在被處理并且可以被緩存,如果有新的相同
     * URL 請求被添加進來以后,則會新的請求則會進入此集合中。此集合主要是為了避免相同的
     * 且不必要的網(wǎng)絡請求
     */
    private final Map<String, Queue<Request<?>>> mWaitingRequests =
        new HashMap<String, Queue<Request<?>>>();

    /**
     * 正在被此 RequestQueue 處理的請求的集合,如果一個請求正在等待被處理或者正在被
     * 某個調(diào)度線程處理,則它會在此集合中
     */
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

    /** 緩存請求隊列,在此隊列中的請求,將通過緩存獲取數(shù)據(jù) */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** 網(wǎng)絡請求隊列,在此隊列中的請求,將通過網(wǎng)絡向服務器發(fā)送請求獲取數(shù)據(jù) */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

RequestQueue 中有兩個 基于優(yōu)先級 Request 的隊列:mCacheQueue 緩存請求隊列和 mNetworkQueue 網(wǎng)絡請求隊列

RequestQueue 的構(gòu)造方法


    /** 默認的網(wǎng)絡請求調(diào)度線程數(shù)量 4 */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /** Cache 接口,用戶獲取和緩存響應結(jié)果,默認的實現(xiàn)類是 DiskBasedCache */
    private final Cache mCache;

    /** Network 接口,用于執(zhí)行網(wǎng)絡請求,默認的實現(xiàn)類是 BasicNetwork */
    private final Network mNetwork;

    /** 響應分發(fā)器,默認的實現(xiàn)類是 ExecutorDelivery */
    private final ResponseDelivery mDelivery;

    /** 網(wǎng)絡調(diào)度線程數(shù)組,NetworkDispatcher是 {@link Thread} 的子類*/
    private NetworkDispatcher[] mDispatchers;

    /** 緩存調(diào)度線程,是{@link Thread} 的子類*/
    private CacheDispatcher mCacheDispatcher;

    /**
     * 創(chuàng)建工作線程池,不調(diào)用 {@link #start()} 方法,就不會開始開始工作,所以創(chuàng)建完請求隊列以后,必須調(diào)用{@link #start()}
     *
     * @param cache           向磁盤持久化響應結(jié)果的緩存對象
     * @param network         執(zhí)行 Http 請求的對象
     * @param threadPoolSize  網(wǎng)絡請求調(diào)度線程的數(shù)量
     * @param delivery        一個負責分發(fā)響應結(jié)果和異常的分發(fā)器
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    /**
     * 調(diào)用 {@link #RequestQueue(Cache, Network, int, ResponseDelivery)} 實現(xiàn)
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    /**
     * 調(diào)用 {@link #RequestQueue(Cache, Network, int)} 實現(xiàn)
     */
    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }  
  1. 有三個構(gòu)造方法,最終調(diào)用的是 RequestQueue(Cache, Network, int,ResponseDelivery) 這個構(gòu)造方法
  2. 創(chuàng)建一個 ExecutorDelivery 對象并賦值給 mDelivery,其中在 ExecutorDelivery 構(gòu)造函數(shù)中傳入的 Handler 對象中的 Looper 對象是主線程的,這樣使用 mDelivery 發(fā)送的請求響應結(jié)果或者異常就被發(fā)送到主線程中了
  3. 創(chuàng)建一個 NetworkDispatcher 類型的數(shù)組對象 mDispatchers,默認長度是4
  4. 可以看到所依賴的屬性 mCachemNetworkmDelivery 都是接口類型的,而不是具體的實現(xiàn)類,這充分體現(xiàn)了面向接口編程的思想

RequestQueue 中的主要方法

還記得在 Volley 類中的 newRequestQueue(Context, HttpStack) 創(chuàng)建完成 RequestQueue 對象 queue 以后,還調(diào)用的了 queue.start() 方法,start() 相關(guān)方法如下所示:

    /**
     * 啟動在此隊列中的線程
     */
    public void start() {
        stop();  // 在啟動之前,需要確保現(xiàn)在正在運行 mCacheDispatcher 線程和 mDispatchers[] 中的線程被終止
        // 創(chuàng)建緩存調(diào)度線程并啟動
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 根據(jù) mDispatchers[] 的長度,創(chuàng)建對應數(shù)量的網(wǎng)絡調(diào)度線程添加進 mDispatchers[] 并啟動
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /**
     * 停止緩存調(diào)度線程 mCacheDispatcher 和網(wǎng)絡調(diào)度線程 mDispatchers[]
     */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }
  1. NetworkDispatcherCacheDispatcher 都是 Thread 的子類,都是線程,創(chuàng)建完該對象以后都需要進行調(diào)用 start() 方法啟動該線程。到這塊兒的代碼,意識到有必要看一下 NetworkDispatcherCacheDispatcher 這兩個類的代碼了,先不著急,我們先分析完 RequestQueue 的代碼。

通過 Volley 進行網(wǎng)絡請求時,創(chuàng)建完網(wǎng)絡請求之后,需要將網(wǎng)絡請求通過 RequestQueue.add(Request) 方法,將網(wǎng)絡請求添加進網(wǎng)絡請求隊列,那么來分析下 add(Request) 方法,這個方法是 RequestQueue 中非常重要的一個方法。


    /** 用于為請求生成一個自動增長的序列號 */
    private AtomicInteger mSequenceGenerator = new AtomicInteger();

    .....

    /**
     * 得到一個序列號
     */
    public int getSequenceNumber() {
        return mSequenceGenerator.incrementAndGet();
    }

    .....

    /**
     * 向請求隊列中添加一個請求
     * @param request 向服務器發(fā)送的請求
     * @return 已經(jīng)發(fā)送經(jīng)過處理的請求
     */
    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 設置請求隊列,并將其添加進 mCurrentRequests 隊列中
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        // 根據(jù)他們添加進來的順序設置唯一的序列號
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        // 如果該請求 request 不可以緩存的,則跳過緩存隊列,直接進入網(wǎng)絡請求隊列
        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.
        // 首先根據(jù) cacheKey(其實就是url)判斷 mWaitingRequests 中是否有相同的請求正在進行,如果有,則將其添加進 mWaitingRequests 隊列中
        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 中沒有相同的請求正在進行,則在 mWaitingRequests 插入一個 null 值
                mWaitingRequests.put(cacheKey, null);
                // 將該 request 添加進 mCacheQueue 隊列中
                mCacheQueue.add(request);
            }
            return request;
        }
    }
  1. 通過 add(Request) 添加一個網(wǎng)絡請求,首先需要將該請求 request 添加到mCurrentRequests 隊列中
  2. 如果該請求不走緩存,則直接將該請求 request 添加到網(wǎng)絡請求隊列 mNetworkQueue 中,結(jié)束該方法
  3. 如果該請求 request 可以走緩存,根據(jù) cacheKey (其實就是 url)判斷 mWaitingRequests 中是否有相同的請求正在進行,如果有,則將其添加進 mWaitingRequests 隊列中,如果沒有則在 mWaitingRequests 中添加值為 null 的值,并將其添加進緩存請求隊列 mCacheQueue
  4. RequestQueue.add(Request) 方法的流程圖(該圖出自 Volley 源碼解析)如下所示:
RequestQueue-add-flow-chart.png

RequestQueue還有一個常用的方法:RequestQueue.cancelAll(Object)


    /**
     * 一個在 {@link RequestQueue#cancelAll(RequestFilter)} 方法中使用的判斷或過濾接口
     */
    public interface RequestFilter {
        public boolean apply(Request<?> request);
    }

    /**
     * 將此隊列中符合 filter 條件的所有請求取消
     * @param filter 使用的過濾條件
     */
    public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                if (filter.apply(request)) {
                    request.cancel();
                }
            }
        }
    }

    /**
     * 通過給定的 tag 取消在此隊列中所有 tag 相同的請求,tag絕對不可以為 bull
     */
    public void cancelAll(final Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("Cannot cancelAll with a null tag");
        }
        cancelAll(new RequestFilter() {
            @Override
            public boolean apply(Request<?> request) {
                return request.getTag() == tag;
            }
        });
    }
  1. 一般我都會重寫每個請求對象 ***RequestsetTag() 方法返回一個該請求的 TAG,在合適的地方(比如:Activity.onDestory() 方法中)通過 cancelAll(Object) 取消該 TAG 對應的請求,以防止發(fā)生意外(比如:內(nèi)存泄露)。

NetworkDispatcher 源碼分析

由于 NetworkDispatcher 源碼也并不算長,只有100+行,直接上源碼,里面的注釋也很詳細了,后面會配有相應的說明和流程圖。

public class NetworkDispatcher extends Thread {
    /** 請求服務器的網(wǎng)絡請求隊列 */
    private final BlockingQueue<Request<?>> mQueue;
    /** 處理網(wǎng)絡請求的實現(xiàn) Network 接口類的對象 */
    private final Network mNetwork;
    /** 寫緩存的對象 */
    private final Cache mCache;
    /** 用于發(fā)送響應和異常的分發(fā)器 */
    private final ResponseDelivery mDelivery;
    /** 用于標志此線程是否中斷 Used for telling us to die. */
    private volatile boolean mQuit = false;

    /**
     * 創(chuàng)建一個網(wǎng)絡調(diào)度線程,必須調(diào)用 {@link #start()} 啟動此線程
     *
     * @param queue     網(wǎng)絡請求隊列
     * @param network   執(zhí)行網(wǎng)絡請求的 Network 接口實現(xiàn)類
     * @param cache     將響應寫進緩存的 Cache 接口實現(xiàn)類
     * @param delivery  用于分發(fā)請求結(jié)果的分發(fā)器
     */
    public NetworkDispatcher(BlockingQueue<Request<?>> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 強制此調(diào)度線程立即停止。如果在隊列中仍然有請求,它們不能保證一定會被處理
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    ......

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            // 記錄網(wǎng)絡請求開始的時間
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // Take a request from the queue.
                // 從隊列中取出一個網(wǎng)絡請求
                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.
                // 如果該請求已經(jīng)被取消,則不會進行網(wǎng)絡請求
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                // 執(zhí)行網(wǎng)絡請求
                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.
                // 如果服務器返回的 304,并且該請求之前已經(jīng)得到過并發(fā)送過響應結(jié)果,則響應結(jié)果可以復用,沒必要進行新的網(wǎng)絡請求,結(jié)束本次循環(huán)
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                // 在工作線程中解析得到的網(wǎng)絡請求響應結(jié)果
                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.
                // 根據(jù) request 的 {@link #shouldCache()} 方法判斷此請求是否需要進行緩存,如果需要進行緩存處理,則將其放到 mCache 中
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back
                // 將響應通過 mDelivery 發(fā)送到 UI 線程中
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                // 如果拋出 VolleyError 異常,則將網(wǎng)絡請求耗時通過{@link volleyError#setNetworkTimeMs(long)}
                // 放進 volleyError 中,并通過 mDelivery 將異常發(fā)送出去
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            }
            catch (Exception e) {
                // 若發(fā)生其他異常,則生成 VolleyError 對象,并將網(wǎng)絡請求耗時放進 volleyError 對象中,
                // 并通過 mDelivery 將異常發(fā)送出去
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }
}
  1. run() 方法中有一個 while(true){ ...... } 代碼段,可見啟動此線程以后,它就進入一直在循環(huán)的狀態(tài),不斷的從網(wǎng)絡請求隊列 mQueue 中取出網(wǎng)絡請求并執(zhí)行,除非通過 quit() 方法改變 mQuit 成為 true,該方法才會停止
  2. NetworkDispatcher 類中所依賴的屬性,mNetworkmCachemDelivery 都是接口類型的,而不是具體的實現(xiàn)類,這也充分體現(xiàn)了面向接口編程的思想
  3. 在上面的代碼片段中,注釋已經(jīng)很清楚了。下面是一張 NetworkDispatcher 進行網(wǎng)絡請求的流程圖,出自 Volley 源碼解析
NetworkDispatcher-run-flow-chart.png

CacheDispatcher 源碼分析

public class CacheDispatcher extends Thread {

    private static final boolean DEBUG = VolleyLog.DEBUG;

    /** 緩存請求隊列 The queue of requests coming in for triage. */
    private final BlockingQueue<Request<?>> mCacheQueue;

    /** 網(wǎng)絡請求隊列 The queue of requests going out to the network. */
    private final BlockingQueue<Request<?>> mNetworkQueue;

    /** 緩存接口 The cache to read from. */
    private final Cache mCache;

    /** 請求結(jié)果分發(fā)類 For posting responses. */
    private final ResponseDelivery mDelivery;

    /** 用于標志此線程是否中斷 Used for telling us to die. */
    private volatile boolean mQuit = false;

    /**
     * 創(chuàng)建一個 緩存調(diào)度線程,必須調(diào)用 {@link #start()} 方法,此線程才會開始工作
     *
     * @param cacheQueue   緩存請求隊列
     * @param networkQueue 網(wǎng)絡請求隊列
     * @param cache        處理緩存的對象
     * @param delivery     分發(fā)響應的結(jié)果
     */
    public CacheDispatcher(
            BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 強制此線程立即停止,如果在隊列中有請求,則不能保證請求一定會被處理
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @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.
                // 如果緩存請求已經(jīng)被取消,則不用處理它
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                // 通過 request.getCacheKey() 從緩存中取出對應的緩存記錄
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    // 如果沒有得到緩存結(jié)果,則將該請求加入到網(wǎng)絡請求隊列中
                    mNetworkQueue.put(request);
                    continue;
                }

                // If it is completely expired, just send it to the network.
                // 如果緩存已經(jīng)過期,則將該請求添加進網(wǎng)絡請求隊列中
                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.
                // 如果得到正確的緩存結(jié)果,則生成對應的響應
                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 將結(jié)果發(fā)送到 UI 線程中
                    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.
                    // 還存在這樣一種情況,緩存記錄存在,但是它約定的生存時間已經(jīng)到了(還未完全過期,
                    // 叫軟過期),可以將其發(fā)送到主線程去更新  
                    // 但同時,也要從網(wǎng)絡中更新它的數(shù)據(jù)
                    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.
                    // 將其傳回主線程的同時,將請求放到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;
            }
        }
    }
}
  1. CacheDispatcher 類和 NetworkDispatcher 類的代碼很相似,都是 Thread 的子類,創(chuàng)建該類的對象以后都需要通過 start() 方法啟動它
  2. CacheDispatcher 所依賴的對象 mCachemDelivery 都是接口類型的,而不是具體的實現(xiàn)類,這也是面向接口編程思想的體現(xiàn)
  3. 下面是一張 CacheDispatcher 進行緩存請求的流程圖,同樣出自 Volley 源碼解析
CacheDispatcher-run-flow-chart.png

BasicNetwork 源碼分析

BasicNetworkNetwork 接口的實現(xiàn)類,Network 接口是執(zhí)行網(wǎng)絡請求的接口,其中只有一個方法 performRequest(Request),該方法由于執(zhí)行網(wǎng)絡請求并返回 NetworkResponse 類型的請求結(jié)果,那來看一下在 BasicNetwork 中該方法是怎么實現(xiàn)的

public class BasicNetwork implements Network {

    protected final HttpStack mHttpStack;

    ......

    /**
     * 通過 {@link HttpStack#performRequest(Request, Map)} 方法執(zhí)行網(wǎng)絡請求,將得到的網(wǎng)絡請求響應包裝成 NetworkResponse 類型的對象并將其返回
     */
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        // 記錄請求開始的時間
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            // 請求響應的對象引用
            HttpResponse httpResponse = null;
            // 請求響應中的 body
            byte[] responseContents = null;
            // 請求響應中的 header
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                // 請求頭
                Map<String, String> headers = new HashMap<String, String>();
                // 將請求中添加的 CacheEntry 添加到請求頭中,詳見 {@link addCacheHeaders(Map<String, String>, Cache.Entry)} 方法
                addCacheHeaders(headers, request.getCacheEntry());
                // 通過 mHttpStack.performRequest(Request, Map<String, String>) 方法執(zhí)行具體的網(wǎng)絡請求,并得到請求的響應
                httpResponse = mHttpStack.performRequest(request, headers);
                // 請求響應中的狀態(tài)行信息對象
                StatusLine statusLine = httpResponse.getStatusLine();
                // 請求響應中的 狀態(tài)碼
                int statusCode = statusLine.getStatusCode();
                // 響應中的響應頭 Header[] 轉(zhuǎn)換成 Map<String, String> 的形式
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());

                // Handle cache validation.
                // 如果響應中狀態(tài)碼是 304,則表示請求的內(nèi)容在服務器端沒有更改,使用本地的緩存即可
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    // 取出緩存 entry 對象
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        // 如果 entry 對象為 null,則返回一個 entry 為 null 的 NetworkResponse 對象
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                    // A HTTP 304 response does not have all header fields. We
                    // have to use the header fields from the cache entry plus
                    // the new ones from the response.
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                    // 即使返回 304 的響應,但是真正的響應中的響應頭包括兩部分信息:當前返回的響應頭和緩存中已經(jīng)緩存的響應頭
                    entry.responseHeaders.putAll(responseHeaders);
                    // 使用緩存對象 entry 生成一個 NetworkResponse 對象并返回
                    // 將請求耗時放入 NetworkResponse 對象中
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // Some responses such as 204s do not have content.  We must check.
                // 有一些響應(比如:204)并沒有 body 內(nèi)容,所以必須進行檢查
                if (httpResponse.getEntity() != null) {
                  // 將響應中的 HttpEntity 對象轉(zhuǎn)換成 byte[] 類型的 responseContents 對象,詳見 {@link entityToBytes(HttpEntity)} 方法
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  // 如果響應中的 HttpEntity 為空,也要給 responseContents 對象賦值
                  responseContents = new byte[0];
                }

                // if the request is slow, log it.
                // 如果請求的耗時太久則打印請求相關(guān)的信息,詳見 {@link logSlowRequests(long, Request ,byte[], StatusLine)} 方法
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                // 如果響應狀態(tài)碼超出 200-299 的范圍,則拋出 IOException 異常
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                // 如果響應狀態(tài)碼在 200-299 之內(nèi),則生成 NetworkResponse 對象并返回
                // 將請求耗時放入 NetworkResponse 對象中
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                // 處理 SocketTimeout,重復請求
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                // 處理 ConnectTimeout,重復請求
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                // 處理 MalformedURLException,重復請求
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                // 處理 IOException,重復請求
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        // Don't retry other client errors.
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        if (request.shouldRetryServerErrors()) {
                            attemptRetryOnException("server",
                                    request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx? No reason to retry.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }

    ......
}
  1. BasicNetwork 類中最重要的一個屬性就是 mHttpStackHttpStack 類型的,HttpStack 也是接口類型的,它是具體的執(zhí)行網(wǎng)絡請求的接口,在 Volley 中有兩個實現(xiàn)了 HttpStack 接口的類:HurlStackHttpClientStackHurlStack 內(nèi)部是使用 HttpUrlConnection 實現(xiàn)的,而 HttpClientStack 內(nèi)部是使用 HttpClient 實現(xiàn)的
  2. BasicNetwork 中,對響應狀態(tài)碼為 304204 等特殊情況做了一定的處理,如果狀態(tài)碼在 200-299 之外則拋出 IOException,在 200-299 之內(nèi)則生成 NetworkResponse 對象并返回,并對各種異常 SocketTimeoutExceptionConnectTimeoutException 等異常做了特殊的處理
  3. performRequest(Request) 中起始的位置記錄請求開始的時間,在生成 NetworkResponse 對象或者拋出 VolleyError 異常中都代碼網(wǎng)絡請求的時間,這是第一篇博客中請求耗時的原始值

ExecutorDelivery 源碼分析

ExecutorDeliveryResponseDelivery 接口的實現(xiàn)類,ResponseDelivery 接口主要有三個方法:

public interface ResponseDelivery {
    /**
     * 解析一個來自網(wǎng)絡或者緩存的響應結(jié)果并發(fā)送
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * 解析一個來自網(wǎng)絡或者緩存的響應結(jié)果并發(fā)送,提供的 Runnable 對象會在發(fā)送完結(jié)果之后被執(zhí)行
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * 向該 request 對象發(fā)送一個異常
     */
    public void postError(Request<?> request, VolleyError error);
}

接著看一下 ExecutorDelivery 實現(xiàn)類的代碼

public class ExecutorDelivery implements ResponseDelivery {
    /** 向主線程中發(fā)送結(jié)果的線程池對象 */
    private final Executor mResponsePoster;

    /**
     * ExecutorDelivery 的構(gòu)造方法
     * @param handler {@link Handler} 對象決定了是向哪個線程發(fā)送結(jié)果
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        // 使用提供的 handler 對象實現(xiàn)一個 Executor 對象
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    /**
     * ExecutorDelivery 的構(gòu)造方法
     * @param executor 用于發(fā)送結(jié)果的線程池
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        // 根據(jù)傳進來的 request 、response 和 runnable 對象,生成一個 ResponseDeliveryRunnable 對象,并使用 mResponsePoster 線程池運行
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /**
     * 一個用于將網(wǎng)絡請求的響應結(jié)果發(fā)送到主線程的線程對象
     */
    @SuppressWarnings("rawtypes")
    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.
            // 如果請求已經(jīng)被取消,則結(jié)束它并不會發(fā)送該請求的結(jié)果
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            // 視情況通過不同的方法發(fā)送正確的響應或異常
            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.
            // 如果 mRunnable 對象不為 null, 則執(zhí)行它
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
    }
}
  1. ExecutorDelivery 的構(gòu)造方法中,需要傳入一個 Handler 對象,這個 Handler 對象是非常重要的。都知道每個 Handler 對象都會持有一個 Looper 對象,該對象決定了 Handler 發(fā)送的消息或者任務在那個線程中處理。在分析 RequestQueue 源碼時,是這樣 new ExecutorDelivery(new Handler(Looper.getMainLooper())) 生成的 ExecutorDelivery 默認的對象,所以默認情況下,通過 ExecutorDelivery 對象發(fā)送的消息都是在主線程中處理的。這也符合我們的習慣,具體的網(wǎng)絡請求結(jié)果都是在 UI 線程中直接處理,這樣更方便一些
  2. ExecutorDelivery 中的內(nèi)部類 ResponseDeliveryRunnable ,是非常重要的,在它的 run() 方法中,如果成功則調(diào)用 requestdeliverResponse(T) 方法,否則調(diào)用 deliverError(VolleyError)
    方法。這里的 deliverResponse(T) 方法內(nèi)部最終會回調(diào)我們在構(gòu)建 Request 時設置的 Response.Listener 對象 onResponse(T) 方法的。

至此,關(guān)于Volley 源碼解析及對 Volley 的擴展系列的第二篇文章就結(jié)束了,從這邊文章中也可以知道為什么都說 Volley 具有很強的擴展性,因為很多地方依賴的屬性都是接口,而不是具體的實現(xiàn)類。接下來在第三篇文章中就會對 Volley 做一些擴展。如果有什么問題歡迎指出。我的工作郵箱:jiankunli24@gmail.com


參考資料:

Volley 源碼解析 -- grumoon

Volley學習筆記之簡單使用及部分源碼詳解 -- Yongyu

Volley源碼分析【面向接口編程的典范】 -- 王世暉

Volley源碼解析<四> RequestQueue請求隊列 -- fenggit

Android Volley完全解析(四),帶你從源碼的角度理解Volley -- 郭霖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

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