volley(4)

3.6 CacheDispatcher & Cache

從volley的工作流程圖中耍铜,我們已經(jīng)知道volley的請(qǐng)求工作流程可以分為兩個(gè)部分弟疆,一個(gè)是先通過(guò)緩存查找請(qǐng)求的數(shù)據(jù)钧排,如果沒(méi)有查找成功再通過(guò)網(wǎng)絡(luò)請(qǐng)求括勺。而上面一節(jié)已經(jīng)介紹NetworkDispatcher灯谣,已經(jīng)知道NetworkDispatcher就是負(fù)責(zé)volley中網(wǎng)絡(luò)請(qǐng)求的工作潜秋,而這節(jié)介紹的CacheDispatcher從名字就可以想象得到工作和NetworkDispatcher是類(lèi)似的,而它正是負(fù)責(zé)volley的緩存工作胎许,下面就來(lái)介紹CacheDispatcher的工作流程峻呛。

3.6.1 CacheDispatcher

CacheDispatcher的工作流程跟NetworkDispatcher是類(lèi)似的,因此參考NetworkDispatcher的工作流程可以將CacheDispatcher的工作流程分為以下幾步:

  1. 通過(guò)存儲(chǔ)Request的CacheQueue(繼承BlockingQueue)中取出Request
  2. 通過(guò)Cacha對(duì)象獲取Request相應(yīng)的緩存數(shù)據(jù)Cache.Entry
  3. 如果Entry為空辜窑,則說(shuō)明該Request的響應(yīng)數(shù)據(jù)沒(méi)有緩存在Cache當(dāng)中杀饵,則將Request添加到NetworkQueue當(dāng)中被NetworkDispatcher處理
  4. 如果Entry過(guò)期了,同樣將Request添加到NetworkQueue當(dāng)中
  5. 如果Entry可使用谬擦,則通過(guò)Entry構(gòu)建NetworkResponse,然后通過(guò)Request.parseNetworkResponse將NetworkResponse轉(zhuǎn)換成Response對(duì)象(這里跟NetworkDispatcher一樣)
  6. 通過(guò)ResponseDelivery將Response送回到主線(xiàn)程處理(還是跟NetworkDispatcher一樣)

可以看到朽缎,CacheDispatcher最后兩步和NetworkDispatcher是一樣的惨远,只不過(guò)前面獲取到NetworkResponse的方式不一樣,NetworkDispatcher是通過(guò)Network執(zhí)行Request請(qǐng)求從網(wǎng)絡(luò)上獲取到NetworkResponse话肖,而CacheDispatcher是通過(guò)Cache從緩存中提取Request對(duì)應(yīng)的NetworkResponse北秽,下面通過(guò)流程圖來(lái)看一下。

CacheDispatcher工作流程.PNG

從流程圖中可以看到形式和NetworkDispatcher幾乎是一樣的最筒,因此接下來(lái)看源碼也是十分簡(jiǎn)單的贺氓。

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;

    /** The queue of requests going out to the network. */
    private final BlockingQueue<Request> mNetworkQueue;

    /** The cache to read from. */
    private final Cache mCache;

    /** For posting responses. */
    private final ResponseDelivery mDelivery;

    /** Used for telling us to die. */
    private volatile boolean mQuit = false;

    /**
     * Creates a new cache triage dispatcher thread.  You must call {@link #start()}
     * in order to begin processing.
     *
     * @param cacheQueue Queue of incoming requests for triage
     * @param networkQueue Queue to post requests that require network to
     * @param cache Cache interface to use for resolution
     * @param delivery Delivery interface to use for posting responses
     */
    public CacheDispatcher(
            BlockingQueue<Request> cacheQueue, BlockingQueue<Request> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * Forces this dispatcher to quit immediately.  If any requests are still in
     * the queue, they are not guaranteed to be processed.
     */
    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.
                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;
            }
        }
    }
}

跟NetworkDispatcher一樣,CacheDispatcher源碼中所需要的重點(diǎn)主要有2點(diǎn):

  1. 類(lèi)成員及構(gòu)造方法床蜘,這些成員是CacheDispatcher工作不可缺少的辙培,也不難,只要了解工作流程便可記住了邢锯,首先要從隊(duì)列CacheQueue中取出Request對(duì)象扬蕊,然后通過(guò)Cache獲取相應(yīng)的數(shù)據(jù),若為空或過(guò)期則將Request添加到NetworkQueue中通過(guò)網(wǎng)絡(luò)獲取丹擎,若不為空則通過(guò)ResponseDelivery將數(shù)據(jù)傳送到主線(xiàn)程
  2. CacheDispatcher也是一個(gè)Thread尾抑,工作都是在run中進(jìn)行的歇父,結(jié)合上面的流程,可以提取出關(guān)鍵的代碼再愈,如下所示
//1.先取出Request對(duì)象
final Request request = mCacheQueue.take();
//2.Cache獲取request相應(yīng)的緩存數(shù)據(jù)
Cache.Entry entry = mCache.get(request.getCacheKey());
//3.如果數(shù)據(jù)為空或者過(guò)期則將request添加到NetworkQueue中
if (entry == null) {
    mNetworkQueue.put(request);
    continue;
}
if (entry.isExpired()) {
    request.setCacheEntry(entry);
    mNetworkQueue.put(request);
    continue;
}
//4.Request調(diào)用接口方法將NetworkResponse解析成Response對(duì)象
Response<?> response = request.parseNetworkResponse(
        new NetworkResponse(entry.data, entry.responseHeaders));
//5.ResponseDelivery將response回傳到主線(xiàn)程處理        
mDelivery.postResponse(request, response);

3.6.2 Cache

我們從上面的分析中可以得知榜苫,在volley中的請(qǐng)求流程主要是分為兩個(gè)流程,一個(gè)是從緩存里提取數(shù)據(jù)的緩存流程翎冲,由CacheDispatcher實(shí)現(xiàn)垂睬,另一個(gè)是從網(wǎng)絡(luò)中獲取數(shù)據(jù),由NetworkDispatcher實(shí)現(xiàn)府适。在CacheDispatcher的工作流程中羔飞,提供緩存功能的便是Cache對(duì)象,因此我們這節(jié)來(lái)學(xué)習(xí)volley中的Cache接口以及它的實(shí)現(xiàn)類(lèi)檐春,學(xué)習(xí)流程可以對(duì)照著NetworkDispatcher中的Network類(lèi)的學(xué)習(xí)逻淌。

Cache只是一個(gè)接口,源碼很簡(jiǎn)單疟暖,主要是注意Cache里面的Entry類(lèi)卡儒,該類(lèi)是主要的緩存信息存儲(chǔ)類(lèi)。

/**
 * An interface for a cache keyed by a String with a byte array as data.
 */
public interface Cache {

    public Entry get(String key);
    public void put(String key, Entry entry);
    public void initialize();
    public void invalidate(String key, boolean fullExpire);
    public void remove(String key);
    public void clear();

    /**
     * Data and metadata for an entry returned by the cache.
     */
    public static class Entry {
        /** The data returned from cache. */
        public byte[] data;

        /** ETag for cache coherency. */
        public String etag;

        /** Date of this response as reported by the server. */
        public long serverDate;

        /** TTL for this record. */
        public long ttl;

        /** Soft TTL for this record. */
        public long softTtl;

        /** Immutable response headers as received from server; must be non-null. */
        public Map<String, String> responseHeaders = Collections.emptyMap();

        /** True if the entry is expired. */
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }

        /** True if a refresh is needed from the original data source. */
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }

}


從代碼中可以發(fā)現(xiàn)Cache就是提供了簡(jiǎn)單的增刪查基本的數(shù)據(jù)操作方法俐巴,沒(méi)什么難理解的骨望,接下來(lái)看看volley中Cache的實(shí)現(xiàn)類(lèi)DiskBasedCache。

在看DiskBasedCache源碼之前欣舵,先來(lái)回憶一下緩存一般都怎么實(shí)現(xiàn)的擎鸠。我們知道緩存一般分為兩種,一種是內(nèi)存緩存缘圈,一種是磁盤(pán)緩存劣光,而內(nèi)存緩存一般使用LruCache實(shí)現(xiàn),磁盤(pán)緩存使用DiskLruCache實(shí)現(xiàn)糟把。在LruCache中底層則是用accessOrder為true的LinkedHashMap實(shí)現(xiàn)的绢涡,DiskLruCache是利用文件的讀寫(xiě)來(lái)實(shí)現(xiàn)緩存的實(shí)現(xiàn)。

因此可以猜想遣疯,DiskBasedCache無(wú)非也是用這兩種方法進(jìn)行緩存的雄可,下面具體看一下關(guān)鍵的代碼。

DiskBasedCache

/**
 * Cache implementation that caches files directly onto the hard disk in the specified
 * directory. The default disk usage size is 5MB, but is configurable.
 */
public class DiskBasedCache implements Cache {

    /** Map of the Key, CacheHeader pairs */
    private final Map<String, CacheHeader> mEntries =
            new LinkedHashMap<String, CacheHeader>(16, .75f, true);

    /** Total amount of space currently used by the cache in bytes. */
    private long mTotalSize = 0;

    /** The root directory to use for the cache. */
    private final File mRootDirectory;

    /** The maximum size of the cache in bytes. */
    private final int mMaxCacheSizeInBytes;

    /** Default maximum disk usage in bytes. */
    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

    /** High water mark percentage for the cache */
    private static final float HYSTERESIS_FACTOR = 0.9f;

    /** Magic number for current version of cache file format. */
    private static final int CACHE_MAGIC = 0x20120504;

    /**
     * Constructs an instance of the DiskBasedCache at the specified directory.
     * @param rootDirectory The root directory of the cache.
     * @param maxCacheSizeInBytes The maximum size of the cache in bytes.
     */
    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        mRootDirectory = rootDirectory;
        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    /**
     * Constructs an instance of the DiskBasedCache at the specified directory using
     * the default maximum cache size of 5MB.
     * @param rootDirectory The root directory of the cache.
     */
    public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
    }


    @Override
    public synchronized Entry get(String key) {
        CacheHeader entry = mEntries.get(key);
        // if the entry does not exist, return.
        if (entry == null) {
            return null;
        }

        File file = getFileForKey(key);
        CountingInputStream cis = null;
        try {
            cis = new CountingInputStream(new FileInputStream(file));
            CacheHeader.readHeader(cis); // eat header
            byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
            return entry.toCacheEntry(data);
        } catch (IOException e) {
            VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
            remove(key);
            return null;
        } finally {
            if (cis != null) {
                try {
                    cis.close();
                } catch (IOException ioe) {
                    return null;
                }
            }
        }
    }


    @Override
    public synchronized void put(String key, Entry entry) {
        pruneIfNeeded(entry.data.length);
        File file = getFileForKey(key);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            CacheHeader e = new CacheHeader(key, entry);
            e.writeHeader(fos);
            fos.write(entry.data);
            fos.close();
            putEntry(key, e);
            return;
        } catch (IOException e) {
        }
        boolean deleted = file.delete();
        if (!deleted) {
            VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
        }
    }

    @Override
    public synchronized void remove(String key) {
        boolean deleted = getFileForKey(key).delete();
        removeEntry(key);
        if (!deleted) {
            VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
                    key, getFilenameForKey(key));
        }
    }

    public File getFileForKey(String key) {
        return new File(mRootDirectory, getFilenameForKey(key));
    }

    /**
     * 判斷緩存是否超過(guò)閾值缠犀,如果超過(guò)則刪除緩存中的數(shù)據(jù)
     */
    private void pruneIfNeeded(int neededSpace) {
        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
            return;
        }
        if (VolleyLog.DEBUG) {
            VolleyLog.v("Pruning old cache entries.");
        }

        long before = mTotalSize;
        int prunedFiles = 0;
        long startTime = SystemClock.elapsedRealtime();

        Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheHeader> entry = iterator.next();
            CacheHeader e = entry.getValue();
            boolean deleted = getFileForKey(e.key).delete();
            if (deleted) {
                mTotalSize -= e.size;
            } else {
               VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
                       e.key, getFilenameForKey(e.key));
            }
            iterator.remove();
            prunedFiles++;

            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                break;
            }
        }

        if (VolleyLog.DEBUG) {
            VolleyLog.v("pruned %d files, %d bytes, %d ms",
                    prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
        }
    }

    private void putEntry(String key, CacheHeader entry) {
        if (!mEntries.containsKey(key)) {
            mTotalSize += entry.size;
        } else {
            CacheHeader oldEntry = mEntries.get(key);
            mTotalSize += (entry.size - oldEntry.size);
        }
        mEntries.put(key, entry);
    }

    /**
     * Removes the entry identified by 'key' from the cache.
     */
    private void removeEntry(String key) {
        CacheHeader entry = mEntries.get(key);
        if (entry != null) {
            mTotalSize -= entry.size;
            mEntries.remove(key);
        }
    }


    ……

}


從源碼中可以看出数苫,DiskBasedCache并不是單純的使用LruCache和DiskLruCache進(jìn)行緩存,而是使用了兩者的結(jié)合辨液,利用LinkedHashMap來(lái)存儲(chǔ)頭部關(guān)鍵信息文判,相當(dāng)于索引,實(shí)際存儲(chǔ)數(shù)據(jù)是利用的文件進(jìn)行操作的室梅。下面通過(guò)其中的關(guān)鍵方法來(lái)進(jìn)行解釋戏仓。

  1. put:拿put方法來(lái)說(shuō)明疚宇,首先調(diào)用pruneIfNeeded來(lái)保證磁盤(pán)中的緩存沒(méi)有超出閾值,然后根據(jù)key獲取到相應(yīng)的文件赏殃,將entry.data輸出到文件當(dāng)中敷待,并且通過(guò)putEntry(key, e)方法將該key對(duì)應(yīng)的關(guān)鍵信息存到LinkedHashMap中即內(nèi)存緩存中。這里的LinkedHashMap雖然沒(méi)有存儲(chǔ)實(shí)際數(shù)據(jù)仁热,但它相當(dāng)于建了一個(gè)索引榜揖,在通過(guò)get獲取某緩存數(shù)據(jù)的時(shí)候,判斷LinkedHashMap有沒(méi)有該key的信息抗蠢,如果有則說(shuō)明磁盤(pán)中有該key對(duì)應(yīng)的數(shù)據(jù)举哟,此時(shí)再去磁盤(pán)中獲取實(shí)際數(shù)據(jù)。因?yàn)長(zhǎng)inkedHashMap是存在內(nèi)存中的迅矛,因此先查找LinkedHashMap比在磁盤(pán)中查找效率高很多妨猩。
  2. get:上面put方法已經(jīng)介紹了LinkedHashMap的作用了,就是先通過(guò)mEntries.get(key)判斷內(nèi)存中有沒(méi)有該key對(duì)應(yīng)的數(shù)據(jù)的關(guān)鍵信息秽褒,如果有則說(shuō)明磁盤(pán)中存在該數(shù)據(jù)壶硅,此時(shí)再去磁盤(pán)中查找,提高了效率销斟。
  3. pruneIfNeeded:這個(gè)方法是在put方法中調(diào)用的庐椒,目的是保證磁盤(pán)緩存不超過(guò)閾值,從該方法的源碼中可以得知蚂踊,刪除緩存的依據(jù)是根據(jù)LinkedHashMap的iterator來(lái)刪除對(duì)應(yīng)數(shù)據(jù)的约谈,這里充分利用到了LinkedHashMap的LRU算法,因?yàn)閍ccessOrder為true的LinkedHashMap會(huì)將使用頻率少的鍵值對(duì)留在隊(duì)頭犁钟,因此最先刪除的就是最近最少使用的棱诱。

因此,DiskBasedCache的實(shí)現(xiàn)原理關(guān)鍵就在于利用了一個(gè)LinkedHashMap在內(nèi)存當(dāng)中當(dāng)磁盤(pán)緩存文件的索引特纤,實(shí)際緩存是通過(guò)磁盤(pán)來(lái)緩存的,這樣避免了每次查找都去進(jìn)行磁盤(pán)操作大大提高了效率侥加。

3.7 RequestQueue

從volley的工作流程中可以看出捧存,NetworkDispatcher和CacheDispatcher的分析其實(shí)就是volley的全部工作了,但是前面一直沒(méi)有介紹它們兩個(gè)是什么時(shí)候被調(diào)用工作的担败,這就是本節(jié)的內(nèi)容RequestQueue昔穴,回顧一下volley的使用方法,就會(huì)發(fā)現(xiàn)Request同時(shí)也是volley框架工作的整個(gè)入口提前,先看一下volley的使用方法吗货。

volley的使用方法

//1. 創(chuàng)建RequestQueue對(duì)象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

//2. 創(chuàng)建Request對(duì)象并在Listener中處理回調(diào)對(duì)象,在回調(diào)方法中更新UI
StringRequest stringRequest = new StringRequest(
    "http://10.132.25.104/JsonData/data1.php", 
    new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // TODO Auto-generated method stub
        textView.setText(response);
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        Log.e(TAG,error.getMessage());        
    }
}); 

//3. 將Request添加至RequestQueue
queue.add(stringRequest);

可以發(fā)現(xiàn)狈网,RequestQueue中最主要就是構(gòu)造方法和add方法宙搬,現(xiàn)在就來(lái)看一下RequestQueue的源碼是怎么實(shí)現(xiàn)的笨腥,還是只挑選重要的部分。

public class RequestQueue {

    private AtomicInteger mSequenceGenerator = new AtomicInteger();

    private final Map<String, Queue<Request>> mWaitingRequests =
            new HashMap<String, Queue<Request>>();
    private final Set<Request> mCurrentRequests = new HashSet<Request>();


    private final PriorityBlockingQueue<Request> mCacheQueue =
        new PriorityBlockingQueue<Request>();
    private final PriorityBlockingQueue<Request> mNetworkQueue =
        new PriorityBlockingQueue<Request>();

    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    private final Cache mCache;
    private final Network mNetwork;

    private final ResponseDelivery mDelivery;

    private NetworkDispatcher[] mDispatchers;
    private CacheDispatcher mCacheDispatcher;



    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }



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


    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

    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);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            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;
        }
    }

    ……
}

源碼中關(guān)鍵的點(diǎn)有3個(gè):

  1. 類(lèi)成員以及構(gòu)造方法勇垛,可以看出脖母,RequestQueue里面的成員其實(shí)就是NetworkDispatcher和CacheDispatcher兩個(gè)類(lèi)以及它們構(gòu)造方法所需要的成員,比如NetworkQueue闲孤,CacheQueue谆级,Network,Cache讼积,ResponseDelivery肥照,注意這里便是傳入主線(xiàn)程Handler的地方,可以說(shuō)volley框架主要的工作配置在這里進(jìn)行的勤众。
    還有看RequestQueue最簡(jiǎn)單的構(gòu)造方法舆绎,可以發(fā)現(xiàn)創(chuàng)建RequestQueu對(duì)象最少需要一個(gè)Cache和一個(gè)Network就可以了,原因也不難理解决摧,volley工作中最主要的類(lèi)就是兩個(gè)Dispatcher亿蒸,而這兩個(gè)Dispatcher的核心工作類(lèi)就是Cache和Network。
  2. start()方法掌桩,該方法是RequestQueue的入口方法边锁,在該方法中初始化并啟動(dòng)volley中兩個(gè)主要工作類(lèi)CacheDispathcer和多個(gè)NetworkDispatcher,此時(shí)兩個(gè)工作類(lèi)便循環(huán)的從各自的隊(duì)列CacheQueue和NetworkQueue中取出Request進(jìn)行處理波岛。
  3. add(Request request)方法茅坛,上面說(shuō)了,兩個(gè)Dispatcher從各自隊(duì)列中取出Request執(zhí)行则拷,那么它們各自的隊(duì)列的Request是從哪里來(lái)的呢贡蓖?答案就是RequestQueue的add方法,從該方法源碼中可以看到煌茬,首先先判斷添加的Request需不需要被緩存斥铺,如果不需要?jiǎng)t將Request添加進(jìn)NetworkQueue當(dāng)中,否則便將其添加至CacheQueue當(dāng)中坛善。

注意晾蜘,在RequestQueue成員中的NetworkDispatcher是一個(gè)數(shù)組形式,即一個(gè)RequestQueue中有多個(gè)NetworkDispathcer同時(shí)在工作眠屎,每個(gè)NetworkDispatcher實(shí)際上就是一個(gè)線(xiàn)程Thread剔交,因此NetworkDispathcer數(shù)組其實(shí)就是一個(gè)線(xiàn)程池!而且該線(xiàn)程池大小為4改衩。所以不要認(rèn)為沒(méi)有使用ThreadPoolExecutor就是沒(méi)有用線(xiàn)程池了岖常。

以上就是RequestQueue的工作流程,是不是很簡(jiǎn)單葫督,就是啟動(dòng)兩個(gè)主要工作流程Dispatcher竭鞍,然后通過(guò)add方法將Request添加至相應(yīng)的Queue當(dāng)中板惑。

3.8 Volley

上面幾節(jié)已經(jīng)將volley框架的整個(gè)工作流程都介紹清楚了,最后只剩下整個(gè)工作流程最開(kāi)始的入口點(diǎn)也就是Volley對(duì)象笼蛛,它的作用是在框架工作之前配置好工作所需的參數(shù)洒放,比如緩存的目錄,使用的網(wǎng)絡(luò)請(qǐng)求的引擎HttpStack類(lèi)型等等滨砍,工作比較簡(jiǎn)單往湿,我們來(lái)直接看Volley類(lèi)的源碼看它的工作是什么。

public class Volley {

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

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

可以看到惋戏,Volley中只有newRequestQueue這么一個(gè)方法领追,在這個(gè)方法中,最主要的工作就是構(gòu)建RequestQueue并啟動(dòng)它响逢,而在RequestQueue那節(jié)可以知道绒窑,構(gòu)建RequestQueue最主要就是構(gòu)建Cache和Network對(duì)象,而構(gòu)建Cache主要就是配置緩存目錄舔亭,構(gòu)建Network就是選擇HttpStack請(qǐng)求引擎些膨。綜上所述,Volley的工作就是配置緩存目錄和HttpStack以構(gòu)建RequestQueue并啟動(dòng)它钦铺。

4 volley總結(jié)

最后在介紹完volley框架中主要的工作類(lèi)之后订雾,再來(lái)看看volley的工作流程便很清晰了。

volley工作流程圖.PNG

可以看出來(lái)volley的流程主要分為3步矛洞,這里結(jié)合上面實(shí)際的類(lèi)仔細(xì)分析volley的工作流程:

  1. 通過(guò)Volley.newRequestQueue()創(chuàng)建RequestQueue對(duì)象洼哎,然后再RequestQueue.start()中配置并啟動(dòng)一個(gè)CacheDispatcher也就是負(fù)責(zé)緩存工作的Cache Thread,同時(shí)啟動(dòng)4個(gè)NetworkDispatcher用于負(fù)責(zé)網(wǎng)絡(luò)工作的Network Threads沼本,也就是線(xiàn)程池噩峦。
    然后通過(guò)RequestQueuet通過(guò)add(Request r)添加Request請(qǐng)求到隊(duì)列中,首先是判斷該Reqeust是否應(yīng)該被緩存抽兆,如果不需要?jiǎng)t將Request添加到NetworkQueue當(dāng)中給NetworkDispatcher執(zhí)行识补,否則將Request添加進(jìn)CacheQueue通過(guò)CacheDispatcher進(jìn)行處理。
  2. CacheDispatcher的執(zhí)行流程為:從CacheQueue當(dāng)中不斷取出Request進(jìn)行處理辫红,取出一個(gè)Reuqest之后先通過(guò)Cache獲取對(duì)應(yīng)的響應(yīng)數(shù)據(jù)凭涂,若數(shù)據(jù)為空或者過(guò)期,則將Request添加進(jìn)NetworkQueue當(dāng)中厉熟;如果數(shù)據(jù)可用导盅,則通過(guò)request.parseNetworkResponse將數(shù)據(jù)封裝成Response類(lèi)對(duì)象较幌,最后通過(guò)ResponseDelivery.postResponse將Response傳回主線(xiàn)程處理揍瑟。
  3. 由上面可以看到,當(dāng)CacheDispatcher處理不了Request時(shí)便會(huì)將Request轉(zhuǎn)移到NetworkQueue中讓NetworkDispatcher處理乍炉。NetworkDispatcher的處理流程為:先從NetworkQueue中取出Request(就是在CacheDispatcher中放進(jìn)來(lái)的)绢片,然后通過(guò)Network執(zhí)行該請(qǐng)求獲取到NetworkResponse滤馍,然后通過(guò)request.parseNetworkResponse轉(zhuǎn)化為Response,然后根據(jù)需要將Response中的數(shù)據(jù)緩存到Cache當(dāng)中(也就是CacheDispatcher中的那個(gè)Cache)底循,最后也是通過(guò)ResponseDelivery將Response傳回到主線(xiàn)程處理巢株。

以上就是volley的實(shí)現(xiàn)原理。最后提一下volley中一些小細(xì)節(jié)的地方吧熙涤。

  1. 線(xiàn)程:緩存線(xiàn)程有1個(gè)阁苞,網(wǎng)絡(luò)線(xiàn)程有4個(gè)
  2. 緩存大小:5M
  3. 使用情景:十分頻繁的小數(shù)據(jù)的網(wǎng)絡(luò)通信

與Okhttp的關(guān)系:volley是一個(gè)封裝了處理請(qǐng)求祠挫、加載那槽、緩存、線(xiàn)程和回調(diào)等過(guò)程等舔,而Okhttp可以用來(lái)提供傳輸層的功能骚灸,也就是處理加載的功能,而且Okhttp的功能更強(qiáng)大慌植,因此可以使用Okhttp代替volley中的傳輸層功能(如用OKHttpStack代替HurlStack)甚牲,這樣可以使得volley加載功能更強(qiáng)大,所以這就是volley+Okhttp

注意事項(xiàng):

  1. Android 6.0之后就將apache的包從SDK中移除了蝶柿,而volley中又實(shí)用了apache的類(lèi)丈钙,因此在6.0之后使用volley時(shí),需要引入手動(dòng)添加apache的包
  2. 在使用第三方的網(wǎng)絡(luò)工具包比如volley或者okhttp時(shí)只锭,不管使用的是什么作為網(wǎng)絡(luò)請(qǐng)求的處理著恩,都應(yīng)該自己再進(jìn)行封裝一層,而不是直接調(diào)用第三方包里面的方法蜻展,這樣免得在修改第三方網(wǎng)絡(luò)庫(kù)的時(shí)候大改特改喉誊;如果自己封裝了一層,則主程序中幾乎不用做改動(dòng)纵顾,而只需在自己封裝那一個(gè)類(lèi)里面進(jìn)行替換
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伍茄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子施逾,更是在濱河造成了極大的恐慌敷矫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汉额,死亡現(xiàn)場(chǎng)離奇詭異曹仗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蠕搜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)怎茫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事轨蛤∶巯埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵祥山,是天一觀的道長(zhǎng)圃验。 經(jīng)常有香客問(wèn)我,道長(zhǎng)缝呕,這世上最難降的妖魔是什么澳窑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮供常,結(jié)果婚禮上照捡,老公的妹妹穿的比我還像新娘。我一直安慰自己话侧,他們只是感情好栗精,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瞻鹏,像睡著了一般悲立。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上新博,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天薪夕,我揣著相機(jī)與錄音,去河邊找鬼赫悄。 笑死原献,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埂淮。 我是一名探鬼主播姑隅,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼倔撞!你這毒婦竟也來(lái)了讲仰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤痪蝇,失蹤者是張志新(化名)和其女友劉穎鄙陡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體躏啰,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趁矾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了给僵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫捣。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出培漏,到底是詐尸還是另有隱情,我是刑警寧澤胡本,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布牌柄,位于F島的核電站,受9級(jí)特大地震影響侧甫,放射性物質(zhì)發(fā)生泄漏珊佣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一披粟、第九天 我趴在偏房一處隱蔽的房頂上張望咒锻。 院中可真熱鬧,春花似錦守屉、人聲如沸惑艇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滨巴。三九已至,卻和暖如春俺叭,著一層夾襖步出監(jiān)牢的瞬間恭取,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工熄守, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜈垮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓裕照,卻偏偏與公主長(zhǎng)得像攒发,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晋南,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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