zoukankan      html  css  js  c++  java
  • 读懂Volley,必须要理解的几个问题

    Volley是一个应用广泛的网络请求开源框架,由Google于2013年推出,它可扩展性性强,适合于数据量小,请求频繁的网络请求,用来加载网络图片也很方便,GitHub地址:https://github.com/google/volley

    关于Volley的使用介绍和源码解析,网络资料很多,这里就不再写了,可参考:
    想看框架原理:Volley 源码解析
    更详细的从使用到源码解析:郭霖的《Volley全解析》

    本篇文章主要是记录一下我在使用及读Volley源码时的一些问题的思考,包括以下问题:

    一、volley的并发请求是怎么实现的
    二、线程里的while无限循环不会影响性能吗?
    三、Request的优先级是怎么处理的?
    四、为什么没有用线程池来处理请求?
    五、有些Request会被放到一个等待的Map里(RequestQueue里的mWaitingRequests这个Map),它的作用是什么?
    六、对响应的处理是怎么切换回主线程的?
    七、Volley对HTTP的304响应是怎么处理的?
    八、为什么Volley不适合数据量大的场景?
    九、可缓存的网络请求结果,是以什么为key进行保存的?

    一、volley的并发请求是怎么实现的

    Volley维护了一个缓存调度线程CacheDispatcher和 n 个网络调度线程NetworkDispatcher,这里 n 默认为 4。
    Volley会根据Request是否可缓存,确定该Request是发起网络请求来处理,还是从缓存里直接得到结果(是否可缓存,可以在构造每个Request的时候自行定义)。

    缓存调度线程的run()方法是一个while(true)无限循环,不断从缓存请求队列中取出 Request去处理(尝试从缓存中拿结果,如果拿不到结果,或者结果数据已经过期,则把Request放到网络请求队列里)。
    网络调度线程的run()方法也是while(true)无限循环,不断从网络请求队列取出Request去处理。这样就实现了并发请求。

    二、线程里的while无限循环不会影响性能吗?

    这里就要介绍下,Volley使用的缓存请求队列和网络请求队列都是无界有序的阻塞队列(PriorityBlockingQueue),它的特点就是从队列里取元素的时候,如果队列为空,则调用此方法的线程会挂起,直至队列有元素可取,线程才会继续运行。同样放入元素的时候,如果队列满了也会挂起,直至队列有空间可放(但是PriorityBlockingQueue是无最大限制的,所以不会满)。

    所以如果队列里的请求都处理完了,线程就都会处于挂起状态,而不会继续循环运行。

    另外,PriorityBlockingQueue是线程安全的,所以不必担心n个线程都会从网络请求队列里取Request的同步问题。

    三、Request的优先级是怎么处理的?

    同样用到了PriorityBlockingQueue。
    Request类实现了Comparable接口并实现了这个接口的compareTo(Request other)方法,用以比较各个Request的优先级。
    在把每个Request加入PriorityBlockingQueue的时候,就会自动根据这个Request的优先级加入队列合适的位置,这也是PriorityBlockingQueue的特点之一。

    而从队列取出Request的时候,都是从队列头部取出的,所以取出的就是优先级最高的。

    Volley默认对Request划分了四种优先级。

        public enum Priority {
            LOW,
            NORMAL,
            HIGH,
            IMMEDIATE
        }
    

    Request比较优先级的时候先比较Priority属性,如果相同再比较它的mSequence属性。
    默认每个Request的优先级都是Priority.NORMAL,可以自行设定。

    四、为什么没有用线程池来处理请求?

    一个说法是,Volley默认用到了四个线程来同时处理网络请求,其实就是线程池的作用了。这样的好处是避免了创建线程和回收线程的开销,毕竟网络请求的开销基本上要大于消息队列的处理,这样可以提高性能。

    但是我觉得用线程池的好处就是,线程池会根据请求数量,动态增加或者减少线程数,而用Volley的做法,如果短时间来了很多请求的话,也只能处理几个,其他的都得排队等待,短时间无法得到响应。网上有很多人也实现了用线程池的Volley版本,例如下面。

    ThreadPoolExecutor threadPoolExecutor = ThreadPoolExecutor)Executors.newFixedThreadPool(mDispatchers.length);
    // 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;
         threadPoolExecutor.submit(networkDispatcher);
         //networkDispatcher.start();
    }
    

    五、有些Request会被放到一个等待的Map里(RequestQueue里的mWaitingRequests这个Map),它的作用是什么?

    先回答,这个Map的作用是,避免同样的Request重复进行网络请求。

    详细来说,如果放入缓存请求队列里的好几个Request都是同样的请求,但是缓存里还拿不到数据,按优先级,先拿出来的那个Request就会先被放到网络请求队列里去执行,因为网络请求的结果可能得一会才能返回并存入缓存,在这期间,缓存请求队列的其他几个相同的Request可能都会被到网络请求队列里去执行了,这就产生了多个不必要的网络请求,浪费了资源。

    而用了这个等待Map,如果有重复的网络请求,之前一个正在处理中,后面来的,就会被暂时放入这个Map里。在这个Map里,key就是Request的URL,相同URL的Request都处于同一个队列元素里。(这个Map类型是HashMap<String, Queue<Request<?>>>)。最先的Request被处理完成之后,后面重复的Request会直接从缓存里拿数据。

    更详细的源码分析,如下,可以略过。
    ————————————————

    每来一个新的Request,是这样处理的:

    先判断是否可缓存。如果不可缓存,就放到网络请求队列里去执行。
    如果可缓存,再判断这个Map里是否含有以这个Request的URL为key的键值对。
    第一次肯定是没有这个key的,那Map就会添加这个键值对,key就是这个Request的url,对应的value为null。同时把这个Request放到缓存队列里去执行。
    这时如果有同样的Request再来时(请求的URL是一样的),这时这个Map里已经有了这个key,那么就会创建新的Queue作为key对应的value,并在Queue里放入这个Request。
    再有同样的Request来,直接会放到这个Queue里。
    上面的3~5步,看代码更清楚,在RequestQueue的add()方法:

                if (mWaitingRequests.containsKey(cacheKey)) {
                    // There is already a request in flight. Queue up.
                    Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                    if (stagedRequests == null) {
                    	// 上面的第4步
                        stagedRequests = new LinkedList<Request<?>>();
                    }
                    // 第4,第5步都会到这里
                    stagedRequests.add(request);
                    mWaitingRequests.put(cacheKey, stagedRequests);
                } else {
                    // 上面的第3步
                    mWaitingRequests.put(cacheKey, null);
                    mCacheQueue.add(request);
                }
    

    那么这个mWaitingRequests什么时候会把添加的元素移除呢?
    每个Request完成以后,等待Map会根据判断它是否含有这个Request的url对应的key,如果有的话,就把key对应的value(也就是处于等待状态的请求队列Queue)从Map移除,并添加到缓存请求队列里去处理。此流程见下RequestQueue的finish()方法:

            if (request.shouldCache()) {
                synchronized (mWaitingRequests) {
                    String cacheKey = request.getCacheKey();
                    Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                    if (waitingRequests != null) {
                        // 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);
                    }
                }
            }
    

    六、对响应的处理是怎么切换回主线程的?

    我们已知Volley的网络请求和缓存处理都是在子线程,那么处理完成后,得到的结果会交给一个结果传递器来处理,这个传递器是在一开始构造RequestQueue的时候,传入构造方法的:

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

    从new Handler(Looper.getMainLooper())可以知道,这个handler是主线程的handler。在每个Request完成之后,结果会包装成Runnable对象,传入这个handler的post方法里进行处理。

    七、Volley对HTTP的304响应是怎么处理的?

    http的304状态码的含义是:

    如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not
    Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而
    保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。

    在Volley的流程里,如果网络请求返回304,就直接使用缓存的数据作为结果,然后结束这个请求。
    NetworkDispatcher的run()方法:

    public void run() {
        ...
        NetworkResponse networkResponse = mNetwork.performRequest(request);
        request.addMarker("network-http-complete");
        // 如果是304并且已经将缓存分发出去里,就直接结束这个请求
        if (networkResponse.notModified && request.hasHadResponseDelivered()) {
            request.finish("not-modified");
            continue;
        }
        ...
        }
    }
    

    下面是BasicNetwork.performRequest()里的相关处理:

            if (statusCode == HttpStatus.SC_NOT_MODIFIED) { //SC_NOT_MODIFIED即304
                return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                        request.getCacheEntry().data, responseHeaders, true);
            }
    

    八、为什么Volley不适合数据量大的场景?

    http传输的数据,不管是发起请求的数据,还是得到的数据,都是会读取到内存中。那么如果几个线程同时访问数据量大的请求,就容易OOM了。

    九、可缓存的网络请求结果,是以什么为key进行保存的?

    从上面的分析我们已经知道,如果我们设置一个请求是可以从缓存里拿结果的,那就会优先从缓存里拿结果。网络请求的方式有GET, POST等多种,如果是POST,那么提交的参数可能是不一样的,那请求结果是怎么保存的呢?

    我们来看下Request类里的getCacheKey(),即获取保存的数据的Key:

        /** Returns the cache key for this request. By default, this is the URL. */
        public String getCacheKey() {
            String url = getUrl();
            // If this is a GET request, just use the URL as the key.
            // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
            // legacy behavior where all methods had the same cache key. We can't determine which method
            // will be used because doing so requires calling getPostBody() which is expensive and may
            // throw AuthFailureError.
            // TODO(#190): Remove support for non-GET methods.
            int method = getMethod();
            if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
                return url;
            }
            return Integer.toString(method) + '-' + url;
        }
    

    注释也说的比较清楚了,如果是GET方式,那么key就是请求的url。如果是其他方式,那么key是Integer.toString(method) + ‘-’ + url。但是将来会移除对非GET方式的支持。
    也就是说如果我们想使用缓存结果的话,最好还是用GET方式来请求数据。

    本文转自:https://blog.csdn.net/fenggering/article/details/88563418

  • 相关阅读:
    软件测试的方法
    常用的adb
    正则表达式
    Python学习笔记(九)————进程和线程
    CodeForces
    华为FusionSphere openstack安装
    华为FusionCompute单节点安装教程--VRM主机的安装
    华为FusionCompute单节点安装教程--CNA主机的安装
    华为FusionSphere--存储管理
    华为FusionSphere--架构介绍
  • 原文地址:https://www.cnblogs.com/sishuiliuyun/p/14778093.html
Copyright © 2011-2022 走看看