• Volley 框架解析(二)--RequestQueue核心解读


    主要围绕RequestQueue进行解读,它的两个请求队列CacheQueue、NetworkQueue是如何调用的,第一条请求的执行过程及如何处理重复请求?对RequestQueue及相关的类进行详细解读。

    1.RequestQueue:

    Volley 框架的核心类,将请求 Request 加入到一个运行的RequestQueue中,来完成请求操作。

    RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcherNetworkDispatchers

    CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。

    NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。

    ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。

    HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack

    Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse

    Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCacheNetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

     (1). 主要成员变量

    RequestQueue 中维护了两个基于优先级的 Request 队列,缓存请求队列和网络请求队列。
    放在缓存请求队列中的 Request,将通过缓存获取数据;放在网络请求队列中的 Request,将通过网络获取数据。

    mCacheQueue缓存请求队列,mNetworkQueue发起网络请求的队列。

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

    维护了一个正在进行中,尚未完成的请求集合。//add方法会把请求先加入到mCurrentRequests中,也就是说mCurrentRequests包含了当前所有的请求,Request 请求结束时,首先从正在进行中请求集合mCurrentRequests中移除该请求。

    this.mCurrentRequests = new HashSet();
    

    维护了一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。Request 请求结束(finish), 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中mCacheQueue,让缓存请求处理线程CacheDispatcher自动处理。

    this.mWaitingRequests = new HashMap();

    构造方法: threadPoolSize//默认是4,

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

    (2). 启动队列

    创建出 RequestQueue 以后,调用 start 方法,启动队列。一个cache调度线程,4个network调度线程,加上主线程共六个线程。

    public void start() {
            stop();  // Make sure any currently running dispatchers are stopped.
            // Create the cache dispatcher and start it.
            //mCache是硬盘缓存策略
            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();
            }
        }

     3)add加入请求

    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.setRequestQueue(this); //把requestQueue绑定到request中
            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();//就是他的url
                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);  //加入到cachequeue中
                }
                return request;
            }
        }

     4). 请求完成

    void finish(Request<?> request)
    <T> void finish(Request<T> request) {
            // Remove from the set of requests currently being processed.
            synchronized (mCurrentRequests) {
                mCurrentRequests.remove(request);
            }
            synchronized (mFinishedListeners) {  //这个接口是在requestQueue中写的
              for (RequestFinishedListener<T> listener : mFinishedListeners) {
                listener.onRequestFinished(request);
              }
            }
    
            if (request.shouldCache()) {
                synchronized (mWaitingRequests) {
                    String cacheKey = request.getCacheKey();
                    Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                    if (waitingRequests != null) {
                        if (VolleyLog.DEBUG) {
                            VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                    waitingRequests.size(), cacheKey);
                        }
                        // Process all queued up requests. They won't be considered as in flight, but
                        // that's not a problem as the cache has been primed by 'request'.
                        mCacheQueue.addAll(waitingRequests);
                    }
                }
            }
        }

    Request 请求结束

    (1). 首先从正在进行中请求集合mCurrentRequests中移除该请求。
    (2). 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中,让缓存请求处理线程CacheDispatcher自动处理。

    (5). 请求取消

    public void cancelAll(RequestFilter filter)
    public void cancelAll(final Object tag)
    

    取消当前请求集合中所有符合条件的请求。
    filter 参数表示可以按照自定义的过滤器过滤需要取消的请求。
    tag 表示按照Request.setTag设置好的 tag 取消请求,比如同属于某个 Activity 的。

    3.CacheDispatcher

    一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery 去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。

    (1). 成员变量

    BlockingQueue<Request<?>> mCacheQueue 缓存请求队列
    BlockingQueue<Request<?>> mNetworkQueue 网络请求队列
    Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存
    ResponseDelivery mDelivery 请求结果传递类

    run代码://无限遍历mCacheQueue,取出request,从cache中获取相应结果,如果结果为空或者过期,则发起网络请求。否则就将解析结果返回。

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

    (2). 处理流程图

     **在具体的请求子类中解析结果,用ExecutorDelivery(new Handler(MainLooper))传递结果,代码如下:

    //以StringRequest为例,就是把结果按照他的编码格式存到response中。
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String parsed;
            try {
                parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            } catch (UnsupportedEncodingException e) {
                parsed = new String(response.data);
            }
            return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
        } 
    

    ExecutorDelivery的postResponse(request,response):

    public void postError(Request<?> request, VolleyError error) {
            request.addMarker("post-error");
            Response<?> response = Response.error(error);
            mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
        }
    //用主线程的handler发送任务到主线程的messageQueue中。
    mResponsePoster = new Executor() {
                @Override
                public void execute(Runnable command) {
                    handler.post(command);
                }
            };

     4.NetworkDispatcher.java

    一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给 ResponseDelivery 去执行后续处理,并判断结果是否要进行缓存。

    (1). 成员变量

    BlockingQueue<Request<?>> mQueue 网络请求队列
    Network mNetwork 网络类,代表了一个可以执行请求的网络
    Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存
    ResponseDelivery mDelivery 请求结果传递类,可以传递请求的结果或者错误到调用者

    run代码:

    public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            while (true) {
                long startTimeMs = SystemClock.elapsedRealtime();
                Request<?> request;
                try {
                    // Take a request from the queue.
                    request = mQueue.take();
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                    continue;
                }
    
                try {
                    request.addMarker("network-queue-take");
    
                    // If the request was cancelled already, do not perform the
                    // network request.
                    if (request.isCanceled()) {
                        request.finish("network-discard-cancelled");
                        continue;
                    }
    
                    addTrafficStatsTag(request);
    
                    // Perform the network request.
                    NetworkResponse networkResponse = mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");
    
                    // If the server returned 304 AND we delivered a response already,
                    // we're done -- don't deliver a second identical response.
                    if (networkResponse.notModified && request.hasHadResponseDelivered()) {//notModified:HTTP 304,参考https://www.douban.com/note/161120791/,
                                                                                       //就是服务器告诉客户端,文件没有更新,不用在请求啦。
                        request.finish("not-modified");
                        continue;
                    }
    
                    // Parse the response here on the worker thread.
                    Response<?> response = request.parseNetworkResponse(networkResponse);
                    request.addMarker("network-parse-complete");
    
                    // Write to cache if applicable.
                    // TODO: Only update cache metadata instead of entire record for 304s.
                    if (request.shouldCache() && response.cacheEntry != null) {
                        mCache.put(request.getCacheKey(), response.cacheEntry);
                        request.addMarker("network-cache-written");
                    }
    
                    // Post the response back.
                    request.markDelivered();
                    mDelivery.postResponse(request, response);
                } catch (VolleyError volleyError) {
                    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                    parseAndDeliverNetworkError(request, volleyError);
                } catch (Exception e) {
                    VolleyLog.e(e, "Unhandled exception %s", e.toString());
                    VolleyError volleyError = new VolleyError(e);
                    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                    mDelivery.postError(request, volleyError);
                }
            }
        }

    (2). 处理流程图

    5. Cache.java

    缓存接口,代表了一个可以获取请求结果,存储请求结果的缓存。

    (1). 主要方法:

    public Entry get(String key); 通过 key 获取请求的缓存实体
    public void put(String key, Entry entry); 存入一个请求的缓存实体
    public void remove(String key); 移除指定的缓存实体
    public void clear(); 清空缓存

    (2). 代表缓存实体的内部类 Entry

    成员变量和方法
    byte[] data 请求返回的数据(Body 实体)
    String etag Http 响应首部中用于缓存新鲜度验证的 ETag
    long serverDate Http 响应首部中的响应产生时间
    long ttl 缓存的过期时间
    long softTtl 缓存的新鲜时间
    Map<String, String> responseHeaders 响应的 Headers
    boolean isExpired() 判断缓存是否过期,过期缓存不能继续使用
    boolean refreshNeeded() 判断缓存是否新鲜,不新鲜的缓存需要发到服务端做新鲜度的检测

    public static class Entry {
            /** The data returned from cache. */
            public byte[] data;
            /** ETag for cache coherency. Http 响应首部中用于缓存新鲜度验证的 ETag*/
            public String etag;
            /** Date of this response as reported by the server. */
            public long serverDate;
            /** The last modified date for the requested object. */
            public long lastModified;
            /** 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();
            }
        }

    6.第一条请求的执行过程及如何处理重复请求

     6.1成员变量:

    维护了一个正在进行中,尚未完成的请求集合。HashSet实现的不可重复,所以这个集合里是正在进行中,尚未完成的请求,并且没有重复!Request 请求结束(finish),移出请求。

    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>(); 
    

    一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。Request 请求结束(finish), 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中mCacheQueue,让缓存请求处理线程CacheDispatcher自动处理。

    HashMap实现key不可重复,queue是linkedList实现的。

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

    mCacheQueue缓存请求队列,CacheDispatcher无限遍历这个队列。

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

    mNetworkQueue网络请求队列,发起网络请求得队列。NetworkDispatcher无限循环这个队列,取出request访问网络。

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

    6.2第一条请求的处理过程

       1)在RequestQueue的add方法中,先把请求加入mCurrentRequests(hashSet实现的,元素不可重复,后续相同的加不了)。

       2)判断请求是否允许缓存,不允许就是加入mNetworkQueue,允许执行第三步。

       3)mWaitingRequests中是否包含此请求,如果包含就加入到相同请求的等待队列中。如果不包含,说明之前没有请求过。就加入到mWaitingRequests和mCacheQueue中mWaitingRequests.put(cacheKey,null),mCacheQueue.add(request);这里主要看mCacheQueue,这时候CacheDispatcher就会把request取出来,判断是否在缓存cache,这是第一个,肯定不在,就加入mNetWorkQueue,NetworkDispatcher就会把它取出来,执行网络请求。

       4)请求完成时,通过ResponseDelivery传送Response给主线程,request调用finish方法结束请求, 首先从正在进行中请求集合mCurrentRequests中移除该请求。
            然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列mCacheQueue中,让缓存请求处         理线程CacheDispatcher自动处理。

    //在NetworkDispatcher中,传送
    mDelivery.postResponse(request, response);   

    6.3有重复请求时,但是该请求正在访问网络

        1)add方法加入,因为mCurrentRequests中已经包含,不能加入到mCurrentRequests。

        2)判断请求是否允许缓存,不允许就是加入mNetworkQueue,允许执行第三步。

        3)检查mWaitingRequests中是否包含此请求,因为之前已经有啦,就加入到相同请求的等待队列中(LinkedList实现的队列)。等待访问网络的请求完成。

        4)然后就是6.2的第四步

     6.4有重复请求,已经在mCache中

        这个最简单,第一个重复的请求加入mCacheQueue ,由CacheDispatcher处理,在Cache取出相应结果,传给主线程。request调用finish方法

       1)在RequestQueue的add方法中,先把请求加入mCurrentRequests(hashSet实现的,元素不可重复,后续相同的加不了)。

       2)判断请求是否允许缓存,不允许就是加入mNetworkQueue,允许执行第三步。

       3)mWaitingRequests中是否包含此请求,如果包含就加入到相同请求的等待队列中。如果不包含,说明之前没有请求过。就加入到mWaitingRequests和mCacheQueue中                 mWaitingRequests.put(cacheKey,null),mCacheQueue.add(request);这里主要看mCacheQueue,这时候CacheDispatcher就会把request取出来,判断是否在缓存cache,

        找到缓存结果​用ExecutorDelivery传递给主线程,request调用finish·方法。

    mDelivery.postResponse(request, response);  //还是这个方法    
    

      

    转发请注明出处:http://www.cnblogs.com/jycboy/p/volley-requestqueue.html

  • 相关阅读:
    基于ArcGIS10.0和Oracle10g的空间数据管理平台十一(C#开发)空间数据字段检查
    IT技术人生路之我的大学网站开发技术团队
    分布式日志收集系统: Facebook Scribe
    基于ArcGIS10.0和Oracle10g的空间数据管理平台十(C#开发)空间数据导入RDBMS上MDB格式
    IT技术人生路之我的大学初入大学及军训
    IT技术人生路之我的大学我技术方向的转变
    基于ArcGIS10.0和Oracle10g的空间数据管理平台十三(C#开发)空间数据导出
    基于ArcGIS10.0和Oracle10g的空间数据管理平台(C#开发)系统需求分析
    web服务
    js数据转换
  • 原文地址:https://www.cnblogs.com/jycboy/p/volley-requestqueue.html
Copyright © 2020-2023  润新知