volley(4)

3.6 CacheDispatcher & Cache

从volley的工作流程图中,我们已经知道volley的请求工作流程可以分为两个部分,一个是先通过缓存查找请求的数据,如果没有查找成功再通过网络请求。而上面一节已经介绍NetworkDispatcher,已经知道NetworkDispatcher就是负责volley中网络请求的工作,而这节介绍的CacheDispatcher从名字就可以想象得到工作和NetworkDispatcher是类似的,而它正是负责volley的缓存工作,下面就来介绍CacheDispatcher的工作流程。

3.6.1 CacheDispatcher

CacheDispatcher的工作流程跟NetworkDispatcher是类似的,因此参考NetworkDispatcher的工作流程可以将CacheDispatcher的工作流程分为以下几步:

  1. 通过存储Request的CacheQueue(继承BlockingQueue)中取出Request
  2. 通过Cacha对象获取Request相应的缓存数据Cache.Entry
  3. 如果Entry为空,则说明该Request的响应数据没有缓存在Cache当中,则将Request添加到NetworkQueue当中被NetworkDispatcher处理
  4. 如果Entry过期了,同样将Request添加到NetworkQueue当中
  5. 如果Entry可使用,则通过Entry构建NetworkResponse,然后通过Request.parseNetworkResponse将NetworkResponse转换成Response对象(这里跟NetworkDispatcher一样)
  6. 通过ResponseDelivery将Response送回到主线程处理(还是跟NetworkDispatcher一样)

可以看到,CacheDispatcher最后两步和NetworkDispatcher是一样的,只不过前面获取到NetworkResponse的方式不一样,NetworkDispatcher是通过Network执行Request请求从网络上获取到NetworkResponse,而CacheDispatcher是通过Cache从缓存中提取Request对应的NetworkResponse,下面通过流程图来看一下。

CacheDispatcher工作流程.PNG

从流程图中可以看到形式和NetworkDispatcher几乎是一样的,因此接下来看源码也是十分简单的。

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源码中所需要的重点主要有2点:

  1. 类成员及构造方法,这些成员是CacheDispatcher工作不可缺少的,也不难,只要了解工作流程便可记住了,首先要从队列CacheQueue中取出Request对象,然后通过Cache获取相应的数据,若为空或过期则将Request添加到NetworkQueue中通过网络获取,若不为空则通过ResponseDelivery将数据传送到主线程
  2. CacheDispatcher也是一个Thread,工作都是在run中进行的,结合上面的流程,可以提取出关键的代码,如下所示
//1.先取出Request对象
final Request request = mCacheQueue.take();
//2.Cache获取request相应的缓存数据
Cache.Entry entry = mCache.get(request.getCacheKey());
//3.如果数据为空或者过期则将request添加到NetworkQueue中
if (entry == null) {
    mNetworkQueue.put(request);
    continue;
}
if (entry.isExpired()) {
    request.setCacheEntry(entry);
    mNetworkQueue.put(request);
    continue;
}
//4.Request调用接口方法将NetworkResponse解析成Response对象
Response<?> response = request.parseNetworkResponse(
        new NetworkResponse(entry.data, entry.responseHeaders));
//5.ResponseDelivery将response回传到主线程处理        
mDelivery.postResponse(request, response);

3.6.2 Cache

我们从上面的分析中可以得知,在volley中的请求流程主要是分为两个流程,一个是从缓存里提取数据的缓存流程,由CacheDispatcher实现,另一个是从网络中获取数据,由NetworkDispatcher实现。在CacheDispatcher的工作流程中,提供缓存功能的便是Cache对象,因此我们这节来学习volley中的Cache接口以及它的实现类,学习流程可以对照着NetworkDispatcher中的Network类的学习。

Cache只是一个接口,源码很简单,主要是注意Cache里面的Entry类,该类是主要的缓存信息存储类。

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

}


从代码中可以发现Cache就是提供了简单的增删查基本的数据操作方法,没什么难理解的,接下来看看volley中Cache的实现类DiskBasedCache。

在看DiskBasedCache源码之前,先来回忆一下缓存一般都怎么实现的。我们知道缓存一般分为两种,一种是内存缓存,一种是磁盘缓存,而内存缓存一般使用LruCache实现,磁盘缓存使用DiskLruCache实现。在LruCache中底层则是用accessOrder为true的LinkedHashMap实现的,DiskLruCache是利用文件的读写来实现缓存的实现。

因此可以猜想,DiskBasedCache无非也是用这两种方法进行缓存的,下面具体看一下关键的代码。

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

    /**
     * 判断缓存是否超过阈值,如果超过则删除缓存中的数据
     */
    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进行缓存,而是使用了两者的结合,利用LinkedHashMap来存储头部关键信息,相当于索引,实际存储数据是利用的文件进行操作的。下面通过其中的关键方法来进行解释。

  1. put:拿put方法来说明,首先调用pruneIfNeeded来保证磁盘中的缓存没有超出阈值,然后根据key获取到相应的文件,将entry.data输出到文件当中,并且通过putEntry(key, e)方法将该key对应的关键信息存到LinkedHashMap中即内存缓存中。这里的LinkedHashMap虽然没有存储实际数据,但它相当于建了一个索引,在通过get获取某缓存数据的时候,判断LinkedHashMap有没有该key的信息,如果有则说明磁盘中有该key对应的数据,此时再去磁盘中获取实际数据。因为LinkedHashMap是存在内存中的,因此先查找LinkedHashMap比在磁盘中查找效率高很多。
  2. get:上面put方法已经介绍了LinkedHashMap的作用了,就是先通过mEntries.get(key)判断内存中有没有该key对应的数据的关键信息,如果有则说明磁盘中存在该数据,此时再去磁盘中查找,提高了效率。
  3. pruneIfNeeded:这个方法是在put方法中调用的,目的是保证磁盘缓存不超过阈值,从该方法的源码中可以得知,删除缓存的依据是根据LinkedHashMap的iterator来删除对应数据的,这里充分利用到了LinkedHashMap的LRU算法,因为accessOrder为true的LinkedHashMap会将使用频率少的键值对留在队头,因此最先删除的就是最近最少使用的。

因此,DiskBasedCache的实现原理关键就在于利用了一个LinkedHashMap在内存当中当磁盘缓存文件的索引,实际缓存是通过磁盘来缓存的,这样避免了每次查找都去进行磁盘操作大大提高了效率。

3.7 RequestQueue

从volley的工作流程中可以看出,NetworkDispatcher和CacheDispatcher的分析其实就是volley的全部工作了,但是前面一直没有介绍它们两个是什么时候被调用工作的,这就是本节的内容RequestQueue,回顾一下volley的使用方法,就会发现Request同时也是volley框架工作的整个入口,先看一下volley的使用方法。

volley的使用方法

//1. 创建RequestQueue对象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

//2. 创建Request对象并在Listener中处理回调对象,在回调方法中更新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);

可以发现,RequestQueue中最主要就是构造方法和add方法,现在就来看一下RequestQueue的源码是怎么实现的,还是只挑选重要的部分。

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

    ……
}

源码中关键的点有3个:

  1. 类成员以及构造方法,可以看出,RequestQueue里面的成员其实就是NetworkDispatcher和CacheDispatcher两个类以及它们构造方法所需要的成员,比如NetworkQueue,CacheQueue,Network,Cache,ResponseDelivery,注意这里便是传入主线程Handler的地方,可以说volley框架主要的工作配置在这里进行的。
    还有看RequestQueue最简单的构造方法,可以发现创建RequestQueu对象最少需要一个Cache和一个Network就可以了,原因也不难理解,volley工作中最主要的类就是两个Dispatcher,而这两个Dispatcher的核心工作类就是Cache和Network。
  2. start()方法,该方法是RequestQueue的入口方法,在该方法中初始化并启动volley中两个主要工作类CacheDispathcer和多个NetworkDispatcher,此时两个工作类便循环的从各自的队列CacheQueue和NetworkQueue中取出Request进行处理。
  3. add(Request request)方法,上面说了,两个Dispatcher从各自队列中取出Request执行,那么它们各自的队列的Request是从哪里来的呢?答案就是RequestQueue的add方法,从该方法源码中可以看到,首先先判断添加的Request需不需要被缓存,如果不需要则将Request添加进NetworkQueue当中,否则便将其添加至CacheQueue当中。

注意,在RequestQueue成员中的NetworkDispatcher是一个数组形式,即一个RequestQueue中有多个NetworkDispathcer同时在工作,每个NetworkDispatcher实际上就是一个线程Thread,因此NetworkDispathcer数组其实就是一个线程池!而且该线程池大小为4。所以不要认为没有使用ThreadPoolExecutor就是没有用线程池了。

以上就是RequestQueue的工作流程,是不是很简单,就是启动两个主要工作流程Dispatcher,然后通过add方法将Request添加至相应的Queue当中。

3.8 Volley

上面几节已经将volley框架的整个工作流程都介绍清楚了,最后只剩下整个工作流程最开始的入口点也就是Volley对象,它的作用是在框架工作之前配置好工作所需的参数,比如缓存的目录,使用的网络请求的引擎HttpStack类型等等,工作比较简单,我们来直接看Volley类的源码看它的工作是什么。

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这么一个方法,在这个方法中,最主要的工作就是构建RequestQueue并启动它,而在RequestQueue那节可以知道,构建RequestQueue最主要就是构建Cache和Network对象,而构建Cache主要就是配置缓存目录,构建Network就是选择HttpStack请求引擎。综上所述,Volley的工作就是配置缓存目录和HttpStack以构建RequestQueue并启动它。

4 volley总结

最后在介绍完volley框架中主要的工作类之后,再来看看volley的工作流程便很清晰了。

volley工作流程图.PNG

可以看出来volley的流程主要分为3步,这里结合上面实际的类仔细分析volley的工作流程:

  1. 通过Volley.newRequestQueue()创建RequestQueue对象,然后再RequestQueue.start()中配置并启动一个CacheDispatcher也就是负责缓存工作的Cache Thread,同时启动4个NetworkDispatcher用于负责网络工作的Network Threads,也就是线程池。
    然后通过RequestQueuet通过add(Request r)添加Request请求到队列中,首先是判断该Reqeust是否应该被缓存,如果不需要则将Request添加到NetworkQueue当中给NetworkDispatcher执行,否则将Request添加进CacheQueue通过CacheDispatcher进行处理。
  2. CacheDispatcher的执行流程为:从CacheQueue当中不断取出Request进行处理,取出一个Reuqest之后先通过Cache获取对应的响应数据,若数据为空或者过期,则将Request添加进NetworkQueue当中;如果数据可用,则通过request.parseNetworkResponse将数据封装成Response类对象,最后通过ResponseDelivery.postResponse将Response传回主线程处理。
  3. 由上面可以看到,当CacheDispatcher处理不了Request时便会将Request转移到NetworkQueue中让NetworkDispatcher处理。NetworkDispatcher的处理流程为:先从NetworkQueue中取出Request(就是在CacheDispatcher中放进来的),然后通过Network执行该请求获取到NetworkResponse,然后通过request.parseNetworkResponse转化为Response,然后根据需要将Response中的数据缓存到Cache当中(也就是CacheDispatcher中的那个Cache),最后也是通过ResponseDelivery将Response传回到主线程处理。

以上就是volley的实现原理。最后提一下volley中一些小细节的地方吧。

  1. 线程:缓存线程有1个,网络线程有4个
  2. 缓存大小:5M
  3. 使用情景:十分频繁的小数据的网络通信

与Okhttp的关系:volley是一个封装了处理请求、加载、缓存、线程和回调等过程,而Okhttp可以用来提供传输层的功能,也就是处理加载的功能,而且Okhttp的功能更强大,因此可以使用Okhttp代替volley中的传输层功能(如用OKHttpStack代替HurlStack),这样可以使得volley加载功能更强大,所以这就是volley+Okhttp

注意事项:

  1. Android 6.0之后就将apache的包从SDK中移除了,而volley中又实用了apache的类,因此在6.0之后使用volley时,需要引入手动添加apache的包
  2. 在使用第三方的网络工具包比如volley或者okhttp时,不管使用的是什么作为网络请求的处理,都应该自己再进行封装一层,而不是直接调用第三方包里面的方法,这样免得在修改第三方网络库的时候大改特改;如果自己封装了一层,则主程序中几乎不用做改动,而只需在自己封装那一个类里面进行替换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容