Volley阅读笔记

简述

Volley是一个请求封装框架,之所以说是框架,是因为其主要是起到一个分工拆分的作用,然后需要根据自身的使用情况来进行各个功能组件的实现,从而完成一套完整的网络请求流程
接下来主要分析几个主要的组件
基于Volley1.0.19版本

NetworkDispatcher

网络调度者,顾名思义,实际上就是请求调度的作用,这个在Volley中是默认实现,接下来看一下Volley的请求调度流程

public class NetworkDispatcher extends Thread {
    /** 当前请求任务的队列 */
    private final BlockingQueue<Request<?>> mQueue;
    /** 用于实际发起网络请求的对象 */
    private final Network mNetwork;
    /** 用于写入请求结果的缓存 */
    private final Cache mCache;
    /** 用于进行请求结果的分发 */
    private final ResponseDelivery mDelivery;
    /** 当前调度线程是否退出,一旦退出,当前线程就会中断 */
    private volatile boolean mQuit = false;
    //...

    @Override
    public void run() {
        //设置线程优先级,在Android中这个优先级相当于中等,一般为后台线程使用
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {//一个一直运行的线程
            long startTimeMs = SystemClock.elapsedRealtime();
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // 这里通过使用阻塞队列,可以做到空闲的时候阻塞线程
                // volley默认使用的是PriorityBlockingQueue,在take的时候会把线程进行await
                // 那么下一次有数据进入队列的时候会notify再进行唤醒
                // 这样可以很有效的避免线程的轮询,也可以节省CPU的开销
                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");

                // 在请求正式开始前首先要看一下当前请求是否取消
                // 如果取消的话没有必要浪费网络链接的流量
                // 注意这里取消后是不会进行Listener的回调的
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                //通过预设的实际网络请求执行者进行请求
                //这里通过接口设计,具有更好的扩展性
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
                //到这里结束后,实际上一次发往服务器的请求和接收服务器响应的过程已经完成

                //一个请求完成了,这时候可能有两种情况
                //1.当前请求在发送之前标记了cache-control,然后去请求服务端,然后返回响应码304,表示客户端本地的缓存数据就是最新的
                //那么这样没有必要重复从网络上拉数据,这个的具体可以去看http自身的缓存机制
                //2.当前请求已经将响应结果进行发送回调,那么没有必要重复进行回调
                //在volley中,request是唯一的
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // 当前请求已经获得响应,那么这里进行返回报文的正体解析
                Response<?> response = request.parseNetworkResponse(networkResponse);
                // 本次请求的响应解析已经完成
                request.addMarker("network-parse-complete");

                // 1.当前请求可以进行缓存
                // 2.当前缓存的为response中的cacheEntry,这个需要注意
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // 准备进行结果的分发,那么这里标记已经分发,为了避免出现重复分发的情况
                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);
            }
        }
    }
    //...

1.NetworkDispatcher实际上就是一个线程,在线程中进行无限循环,不断地从队列中获取请求进行处理
2.为了避免真正意义上的无限轮询,应该结合线程的挂起和唤醒机制进行处理,这样可以灵活做到没有任务的时候挂起,有任务到来的时候唤醒并且执行,这里通过BlockingQueue作为任务队列的基础数据结构来实现这个效果
3.其实从NetworkDispatcher中可以看到几个任务的分工,比方说缓存、请求发起者、结果分发者,这些在后面会细说。
这里主要是能够看出整个Volley的基础任务流程
1.从队列中获取请求
2.检查当前请求是否取消,如果请求已经被取消,不进行回调并且善后处理。如果请求没有取消,继续步骤3
3.通过request发起网络请求
4.如果当前请求的响应为本地缓存已经是最新,那么此时可以直接使用本地缓存中的数据,那么也就不需要进行
5.解析服务器返回的报文中的body部分
6.当前请求是否可以缓存,如果可以缓存,将可以缓存的部分存入缓存中
7.通过回调分发者进行分发,最后进行回调(上述步骤中的任何异常都会进行异常回调)

Network

实际进行网络请求操作的对象,通过接口的模式扩展,可以根据自身的情况实现不同的请求,比方说可以用OkHttp、HttpURLConnection来做底层的网络请求实现
在Volley中有一个默认的实现

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

注意到这里还分了两层,其中一层就是Network,但是看到默认实现为BasicNetwork
,内部主要是做一些默认操作,比方说处理响应码等操作

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                Map<String, String> headers = new HashMap<String, String>();
                //这里是在请求之前设置缓存相关的头部
                //比方说If-Modified-Since,这样的意义是让服务器判断当前本地缓存是否过期
                addCacheHeaders(headers, request.getCacheEntry());
                //这里可以看到,实际的请求交给了HttpStack处理
                httpResponse = mHttpStack.performRequest(request, headers);
                //成功获得响应,这里是获得请求的响应码
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                //获得响应的头部报文
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                //当前响应码为304,说明当前客户端本地缓存已经是最新数据
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    //因为服务端返回的响应码为304,这意味着客户端缓存就是最新数据,并且当前服务端不会返回数据
                    //那么此时需要手动构建响应体,从而做到统一的请求响应完成
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                    entry.responseHeaders.putAll(responseHeaders);
                    //这里的entry.data其实就是本地缓存中的数据
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }
                
                // 请求返回重定向的响应码
                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                    //重定向的链接默认规定是在响应的头部报文的Location字段中,这个是Http协议规定的
                    String newUrl = responseHeaders.get("Location");
                    request.setRedirectUrl(newUrl);
                }

                // 一些响应码比方说204虽然也是成功,但是响应报文中是没有body的
                if (httpResponse.getEntity() != null) {
                    //网络连接实际上都是一个socket的连接
                    //然后客户端和服务端分别打开输入和输出流进行数据交换
                    //这里就是将服务端向客户端输入的输入流转换为字节数组
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // 这里只是默认非null而已
                  responseContents = new byte[0];
                }

                // 这里的时间为请求发出和客户端收到响应的时间
                // 一般会包括请求发出的延时、网络连接的延时(DNS解析之类的)、服务端处理延时等
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                if (statusCode < 200 || statusCode > 299) {
                    //这里实际上已经处理完了304响应码,304已经返回了自己拼装的数据
                    //这里抛出异常有两种情况,一个
                    throw new IOException();
                }
                //这里是响应成功的正常数据
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {//绝大多数出现异常的时候会在这里抛出异常,从而进行异常处理回调
                    throw new NoConnectionError(e);
                }
                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                        statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                    VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
                } else {
                    VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                }
                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 == HttpStatus.SC_MOVED_PERMANENTLY || 
                                statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                        //重定向处理,但是现在DefaultRetryPolicy中是没有处理的
                        attemptRetryOnException("redirect",
                                request, new RedirectError(networkResponse));
                    } else {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(e);
                }
            }
        }
    }

稍微总结一下,在BasicNetwork中主要是封装了缓存的头部和一些默认的请求完成之后的处理,从很大程度上可以降低他人实现的成本,那么在外部主要是实现HttpStack即可
这里以Volley默认实现的HurlStack为例

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        //在这里之前,BasicNetwork封装了缓存的一些头部报文
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());//设置请求预设的头部报文
        map.putAll(additionalHeaders);//为了避免覆盖,所以BasicNetwork的头部报文后设置
        if (mUrlRewriter != null) {
            //这里主要是提供一个机会修改请求的实际url
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        //这里就是创建一个连接
        //1.设置连接和读取超时
        //2.如果当前是Https请求,允许设置SSL认证工厂
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {//将之前设置的请求头部按照http协议的格式设置
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        //根据请求方式,常用的比方说POST
        //在请求报文中设置请求方式为POST
        //然后通过socket连接建立的通路中的OutputStream将请求参数从客户端输出到服务器中
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode();//获得服务器的响应码
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            //这里进行响应体的数据绑定
            //包括返回数据的流、返回数据格式和大小等等
            response.setEntity(entityFromConnection(connection));
        }
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {//添加响应头部
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        //实际上在HttpStack中主要做了这样的几件事情
        //1.实际发出请求
        //2.根据相应结果进行response的包装
        return response;
    }

这个只是Volley的一个默认实现,在实际使用中需要根据自己的需要来实现。

ResponseDelivery

当一次请求获得响应之后,那么接下来就要进行结果的发送和回调,这个工作就是通过实现ResponseDelivery来完成
这个主要看接口设计就可以明白了

public interface ResponseDelivery {
    /**
     * 默认的使用是这个方法,用于响应网络或者缓存获得的响应
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * 后面提供了这个方法,在postResponse(Request<?> request, Response<?> response)的基础上
     * 添加了一个runnable用于在响应回调之后进行一个默认处理
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * 用于发送异常回调
     */
    public void postError(Request<?> request, VolleyError error);
}

实际上就是用于发送成功响应的响应体或者发送请求失败的异常,接下来看一下默认的实现ExecutorDelivery

    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {//实际上就是把任务扔进handler中执行,所以说重点在handler的执行线程
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    @Override
    public void run() {
        // 在发送之前先检查当前任务是否取消
        if (mRequest.isCanceled()) {
            //任务取消则不会进行回调发送
            mRequest.finish("canceled-at-delivery");
            return;
        }

        //实际上就是在这里进行回调处理
        //在默认的场景下,发出的每一个请求都需要定义一个回调
        //那么就是通过实现Request中的对应方法实现
        if (mResponse.isSuccess()) {
            mRequest.deliverResponse(mResponse.result);
        } else {
            mRequest.deliverError(mResponse.error);
        }

        //此时回调已经完成

        if (mResponse.intermediate) {
            //这个标志意味着当前请求没有完成,此时有点尴尬
            //默认的情况中在NetworkDispatcher中的任务队列中已经移除
            //但是此时在RequestQueue中的当前请求队列中没有移除
            //这个目前的应用在CacheDispatcher中,成功从缓存中获取数据,然后先进行回调
            //之后再通过runnable将当前请求放到任务队列中,用于缓存处理后再次发送请求
            mRequest.addMarker("intermediate-response");
        } else {
            //正常的一次任务完成之后进行收尾处理
            mRequest.finish("done");
        }

        // 目前的使用场景是CacheDispatcher,用于在缓存处理之后再次发送请求
        if (mRunnable != null) {
            mRunnable.run();
        }
    }

在默认的设计上,设计者是希望开发者直接通过不同线程的Handler,默认是主线程的Handler来进行回调处理。
这个个人认为还是要按照实际的场景来处理,因为有的时候一个请求回来了可能会直接接入一个耗时操作,可能此时直接在其它线程池中执行相对合理。

Cache

说到缓存,首先就是要从Volley默认提供的CacheDispatcher来时说起,同NetworkDispatcher差不多的意思,只是职责是处理缓存相关事宜。

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

        Request<?> request;
        while (true) {
            request = null;
            try {
                // 从缓存请求队列中获取请求,这个同样会阻塞
                request = mCacheQueue.take();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
                request.addMarker("cache-queue-take");

                if (request.isCanceled()) {//当前请求已经被取消,不需要继续从缓存中获取数据等一系列操作
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 根据缓存的键名从缓存中获取数据
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {//未能命中缓存
                    request.addMarker("cache-miss");
                    // 将当前任务添加当网络请求队列中,那么后续就会进入NetworkDispatcher中执行网络请求
                    mNetworkQueue.put(request);
                    continue;
                }

                //在http缓存机制中缓存有两种过期
                //首先一个响应一定有一个截止时间
                //但是有个响应可能允许额外的延长过期时间
                //这个具体可以看cache-control
                //在Volley中分别被封装成了entry中的
                //softTtl和ttl

                if (entry.isExpired()) {//当前时间已经大于ttl,那么缓存一定是过期的,这个时间可能包括额外的延长时间
                    //虽然成功击中缓存,但是当前缓存已经过期
                    //此时还是去发起网络请求
                    request.addMarker("cache-hit-expired");
                    //也许后续服务器会返回304标记数据没有变化,此时可以直接使用之前的entry
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }
                //当前击中缓存,并且缓存数据没有完全过期
                request.addMarker("cache-hit");
                //通过缓存的结果构建response,主要是用于后续的统一回调处理
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                //当前没有完全过期
                if (!entry.refreshNeeded()) {//并且当前还没有到缓存过期的时间
                    //直接使用缓存结果进行回调处理即可
                    mDelivery.postResponse(request, response);
                } else {//当前已经超过缓存过期的时间,不过却在额外延长的时间内
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    response.intermediate = true;

                    final Request<?> finalRequest = request;
                    //此时还是认为缓存有效并且进行回调
                    //但是回调之后还会发出网络请求,用于刷新缓存数据
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                //回调处理缓存之后,再次发起网络请求
                                mNetworkQueue.put(finalRequest);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
            }
        }
    }

1.从缓存队列中获取请求,这些添加到队列中的请求肯定是允许使用缓存的
2.从缓存中获取数据
3.如果没有击中缓存或者缓存完全过期,将当前任务放入网络请求队列中等待执行网络请求,否则继续步骤4
4.当前缓存中的数据还没有到过期时间,直接进行回调,完成本次请求。如果已经超过过期时间,但是还在延长时间内,此时先进行缓存数据的回调,然后再将任务放入网络请求队列中等待执行网络请求来进行缓存的刷新和回调操作
看到这里,基本上的组件已经介绍完毕,最后从入口开始看一下基础流程

Volley

作为整个框架的入口,实际上就是一个工厂,里面会提供一些默认选项,这里就看默认的提供

    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        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 {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //对请求执行者再包装一层
        Network network = new BasicNetwork(stack);
        
        RequestQueue queue;
        //创建请求队列管理对象
        if (maxDiskCacheBytes <= -1) {
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        } else {//具有自定义的最大硬盘缓存大小
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }
        //开始请求队列的运作
        queue.start();

        return queue;
    }

可以看到,实际上RequestQueue才是执行者,在Volley中主要是初始化硬盘缓存的目录,确定请求的执行者,初始化请求队列管理,并且最终开启队列的运作,这意味着Volley开始运作。

RequestQueue

这个其实才是Volley对外的操作对象,默认提供一个addRequest操作,用于添加一个请求,从之前的讲述可以看到内部至少有CacheDispatcher和NetworkDispatcher在工作,看一下处理逻辑:

    public void start() {
        stop();
        //实际上在volley的架构中
        //操作都是通过Dispatcher进行的
        //不同的Dispatcher的职责不同
        //默认使用的只有缓存相关和请求相关
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        //实际上NetworkDispatcher本身就是一个线程Thread
        //默认使用的线程固定为4个
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    public <T> Request<T> add(Request<T> request) {

        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {//将请求添加到当前请求队列,这个主要是用于停止请求的
            mCurrentRequests.add(request);
        }

        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // 当前请求不使用缓存
        if (!request.shouldCache()) {
            //将当前请求放入请求队列中,等待NetworkDispatcher获取后执行网络请求
            mNetworkQueue.add(request);
            return request;
        }
        // 当前请求需要使用缓存

        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();//获取缓存的键名
            if (mWaitingRequests.containsKey(cacheKey)) {
                //当前已经有同样的缓存请求在进行中
                //将本次请求添加到等待队列中
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                //这个会等待最早的一个请求成功之后再处理,具体逻辑在finish中
                //简单说就是最早的一个请求成功了,然后再将之前等待的任务全部放入缓存请求队列中
                //最好当然是全部击中缓存
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // 用于标记当前请求进行中
                mWaitingRequests.put(cacheKey, null);
                // 将任务放入缓存请求队列中,等待CacheDispatcher获取后执行
                mCacheQueue.add(request);
            }
            return request;
        }
    }
    
    <T> void finish(Request<T> request) {
        
        synchronized (mCurrentRequests) {//当前请求已经完成,从当前请求中队列移除
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {//进行请求完成回调
            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);
                    }
                    //将之前等待的任务全部放入缓存请求队列中,后续最好是全部击中缓存
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

简单看一下添加请求的逻辑
1.判断请求是否使用http缓存,如果不使用,添加到网络请求队列中等待NetworkDispatcher获取后执行,否则继续步骤2
2.当前使用缓存,那么判断当前请求是否有同样的缓存请求已经在发出中,如果没有,将当前请求加到缓存请求队列中等待CacheDispatcher获取后执行,否则继续步骤3
3.添加到等待队列中,等待最早的一个相同的缓存请求完成之后再添加到缓存请求队列中,此时有很大概率可击中缓存

结语

实际上Volley还有下载图片等功能,不过可能比起正牌的图片框架Glide之类的还是要弱上不少,基于篇幅限制,还有更多默认的功能没有讲。
主要还是希望能够理解Volley对于请求分发框架的设计,无论是对于直接使用还是写自己的请求框架都是有很大的益处的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容