Volley解析

258 阅读25分钟

一般来讲网络请求可以简化为下图:

客户端发出请求,等待服务端响应数据,整个流程看起来很简单,但实际应用过程中会碰到各种各样的问题,比如请求参数繁琐的配置,网络不稳定需要自动重连,URL变更重定向,缓存处理等等。试想如果每个请求我们都完完整整地处理这些异常流程,那么导致的结果就是代码冗余、难以扩展。所以对网络请求进行封装一直都是刚需,网上可以找到各式各样的网络请求框架,今天我们准备学习的是 Volley 的框架,鉴于其在 Android 圈名头太大就不做过多介绍,只需要知道它是 Google I/O 2013上发布的网络通信库。

Volley和餐饮店

在本文中,我打算按照自己的理解来梳理 Volley 的设计流程。为了加深印象,我们假设有这么一家小饭馆,有多小呢,整个人员配置只有一个厨师范师傅。

厨房点餐与请求封装

某一天,你迫于自己太懒所以决定去这家饭馆吃,由于饭馆人员机构过于扁平,你不得不跑去厨房跟厨师说:"范师傅,来个黄焖鸡米饭,记得多放盐,多放辣、不要葱花"。半个小时后,厨师说搞定,这份黄焖鸡就是你的了。过了一会儿,你还想吃啤酒鸭,没服务员呐,你又得去厨房来这么一遍。你稍微感觉有点不对劲,如果你还要一个点个汤呢,这也太麻烦了吧。

吃完饭你到隔壁麻将馆找饭店老板结账时稍微抱怨了一下,老板刚赢了钱很高兴,表示想听听你的看法。你面色一正,轻咳了两声说道:"首先当然要明确职责,厨师你就让好好炒菜,不要他跟顾客打交道分了心,顾客想吃什么写个订单给他好了"。老板一听,言之有理,说道:"这个是好办法,但我们范师傅老花眼,订单上的字得写大一点,这样吧,一个订单就代表一份菜。另外,我们现在主打东北菜,过段时间可能要尝试粤菜,你帮我想想怎么弄"。

网络请求跟点餐十分类似,服务器首先得了解你需要什么,这是可以订单来表示 ,现在我们设计订单样式。

Request源码

public abstract class Request<T> {

    /** Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}. */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";

    /** Supported request methods. */
    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
        int HEAD = 4;
        int OPTIONS = 5;
        int TRACE = 6;
        int PATCH = 7;
    }
 
    private final int mMethod;

    /** URL of this request. */
    private final String mUrl;
}

可以看到Request包含了网络请求的基本信息 URLMethod。现在厨师知道你想吃什么了,那么你知道厨师会用什么给你盛菜吗,锅?碗?瓢?盆?,所以我们还要规定一种饭盒来盛菜。

HttpResponse源码

public final class HttpResponse {

    private final int mStatusCode;
    private final List<Header> mHeaders;
    private final int mContentLength;
    private final InputStream mContent;
}

HttpResponse 包含了网络请求的响应码、响应报头、响应输入流。现在厨师也知道做好的菜要放在这个饭盒里给你了。最后来设计厨师类,现在厨师能看懂你的订单,也知道了要返回给你装满饭盒的菜,但老板说了以后要换菜系,因为每个菜系的做菜方法不尽相同,所以厨师的操作手法不要写死,要面向接口编程。

HttpStack源码

public interface HttpStack {
    HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError;
}

HttpStack 接口只有一个方法,这个方法接受一张订单,一些备注,最终返回一个饭盒。现在让我们的范厨师来实现这个接口,以后范厨师接到的订单做出来的菜都是东北风味。

HurlStack源码

public class HurlStack extends BaseHttpStack {

    private static final int HTTP_CONTINUE = 100;

    @Override
    public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<>();
        map.putAll(additionalHeaders);
        // Request.getHeaders() takes precedence over the given additional (cache) headers).
        map.putAll(request.getHeaders());
        ...
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        boolean keepConnectionOpen = false;
        try {
            for (String headerName : map.keySet()) {
                connection.setRequestProperty(headerName, map.get(headerName));
            }
            setConnectionParametersForRequest(connection, request);
            // Initialize HttpResponse with data from the HttpURLConnection.
            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.");
            }

            if (!hasResponseBody(request.getMethod(), responseCode)) {
                return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
            }

            // Need to keep the connection open until the stream is consumed by the caller. Wrap the
            // stream such that close() will disconnect the connection.
            keepConnectionOpen = true;
            // 返回响应
            return new HttpResponse(
                    responseCode,
                    convertHeaders(connection.getHeaderFields()),
                    connection.getContentLength(),
                    new UrlConnectionInputStream(connection));
        } finally {
            if (!keepConnectionOpen) {
                connection.disconnect();
            }
        }
    }
}

先不用去了解细节,里面封装的是标准的 HttpUrlConnection 请求,它能做到解析 RequestHeaders,返回 HttpResponse。也就是说,给范厨师一张订单,写上一些备注,他能给你做出东北味的菜了,最后还装在饭盒里递给你。万一以后再来一个蔡师傅,实现这个接口后,就能做出淮扬菜了。

至此,设计工作初步完成,饭馆老板很满意,给你的饭钱算了八折。

Volley 为了移除对 Apache HTTP 库的依赖问题和兼容原有设计,将接口方法下沉到抽象类BaseHttpStack中去,所以 HurlStack 实现的是父类的抽象方法,而不是我们定义的接口方法

主厨与重试机制

迫于懒惰,过了几天你又去这家饭馆,这次菜上得很快,但打开饭盒里面居然是空的,你强压怒火问怎么回事。范厨师抽了根烟说:"红烧排骨刚加完水,熄火了,后面还一大堆订单呢,所以你这个就不做了"。你正要跳起来跟范厨师理论的时候,饭馆老板凭空出现,打发范厨师先去做饭并拉着你的手说:"老弟,你帮我想个办法弄弄这事儿,我平时都忙着在隔壁博弈,实在没空管这个",你不为所动,老板试探性地问道:"给个八折?",这时你的脸才舒张开来,说道:"炒菜过程中可能遇到各种各样的问题,不能因为停水停电就撂挑子,你让范师傅多试几次,说不定水电早就好了呢。我们需要一个主厨来决定到底要不要重试",老板深以为然,然后让你展开讲讲。

这次不用老板说你就考虑到以后做大做强的问题了,厨师长这个类肯定不能写死,现在顾客和厨师之间隔着一个主厨,所以订单由主厨转交给厨师,成菜也由主厨转交给你。既然都有主厨了,再用饭盒也不合适,我们来定义一个餐盘类。

NetworkResponse源码

public class NetworkResponse {

    /** The HTTP status code. */
    public final int statusCode;

    /** Raw data from this response. */
    public final byte[] data;

    /**
     * Response headers.
     *
     * <p>This map is case-insensitive. It should not be mutated directly.
     *
     * <p>Note that if the server returns two headers with the same (case-insensitive) name, this
     * map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned
     * by the server.
     */
    public final Map<String, String> headers;

    /** All response headers. Must not be mutated directly. */
    public final List<Header> allHeaders;

    /** True if the server returned a 304 (Not Modified). */
    public final boolean notModified;

    /** Network roundtrip time in milliseconds. */
    public final long networkTimeMs;
}

可以看到,新的响应封装中,已经把数据响应流转换为数组了,这方便我们的后续处理。

那么再定义一个服务员接口如下:

Network源码

public interface Network {
    NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

主厨接收订单,返回餐盘,所以的重试判断都由它的实现类来处理。

public class BasicNetwork implements Network {

    protected static final boolean DEBUG = VolleyLog.DEBUG;

    private static final int SLOW_REQUEST_THRESHOLD_MS = 3000;

    private static final int DEFAULT_POOL_SIZE = 4096;

    /**
     * @deprecated Should never have been exposed in the API. This field may be removed in a future
     *     release of Volley.
     */
    @Deprecated protected final HttpStack mHttpStack;

    private final BaseHttpStack mBaseHttpStack;

    protected final ByteArrayPool mPool;
 
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                ...
                        // 真正执行网络请求
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                ...
                // 返回 Response
                return new NetworkResponse(
                        statusCode,
                        responseContents,
                        /* notModified= */ false,
                        SystemClock.elapsedRealtime() - requestStart,
                        responseHeaders);
            } catch (SocketTimeoutException e) {
            // 1. 纳尼,超时了,吃掉异常,进入下一个 while 循环
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (MalformedURLException e) {
            // 2. 纳尼,url都拼错了,处理不了抛异常
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusCode();
                } else {
                // 抛出连接异常
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    networkResponse =
                            new NetworkResponse(
                                    statusCode,
                                    responseContents,
                                    /* notModified= */ false,
                                    SystemClock.elapsedRealtime() - requestStart,
                                    responseHeaders);
                    if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
                            || statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                            // 3. 纳尼,权限问题,吃掉异常,进入下一个 while 循环
                        attemptRetryOnException(
                                "auth", request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        // Don't retry other client errors.
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        if (request.shouldRetryServerErrors()) {
                        // 重连机制
                            attemptRetryOnException(
                                    "server", request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx? No reason to retry.
                        throw new ServerError(networkResponse);
                    }
                } else {
                // 重连机制
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }

    /**
     * Attempts to prepare the request for a retry. If there are no more attempts remaining in the
     * request's retry policy, a timeout exception is thrown.
     *
     * @param request The request to use.
     */
    private static void attemptRetryOnException(
            String logPrefix, Request<?> request, VolleyError exception) throws VolleyError {
        RetryPolicy retryPolicy = request.getRetryPolicy();
        int oldTimeout = request.getTimeoutMs();

        try {
        // 如果仍然满足重试条件就吃掉这个异常;否则抛出异常,中止本次请求
            retryPolicy.retry(exception);
        } catch (VolleyError e) {
            request.addMarker(
                    String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
            throw e;
        }
        request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
    }
}

可以看到,主厨通过判断请求过程中的异常和响应的异常码来决定要不要重试。当然我们也不能一直重试,不然其他订单就只能永远等着,所以有一个重试策略存在,它由 RetryPolicy 来控制,此处暂且不表。

不同的主厨有不同的处理风格,所以客人可以选择将订单交给他们任意一个。

老板很满意,觉得这小馆子越来越正规了,按照约定给你打了八折。

服务员与请求队列

又有一天,你都下订单半天了还不见上菜,原来主厨处理完上一个订单就躲到一边抽烟去了。你跟老板说,这可不行,得赶紧找个服务员,在有新订单的时候通知主厨,老板说你看着办。

因为服务员要不停地检查是否还有订单需要处理,所以创建一个线程来做循环处理。

public class NetworkDispatcher extends Thread {

    /** The queue of requests to service. */
    private final BlockingQueue<Request<?>> mQueue;
    /** The network interface for processing requests. */
    private final Network mNetwork;
    /** The cache to write to. */
    private final Cache mCache;
    /** For posting responses and errors. */
    private final ResponseDelivery mDelivery;
    /** Used for telling us to die. */
    private volatile boolean mQuit = false;
 
    /**
     * 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() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of NetworkDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }

    // Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
    // This is needed to avoid keeping previous request references alive for an indeterminate amount
    // of time. Update consumer-proguard-rules.pro when modifying this. See also
    // https://github.com/google/volley/issues/114
    private void processRequest() throws InterruptedException {
        // Take a request from the queue.
        Request<?> request = mQueue.take();
        processRequest(request);
    }

    @VisibleForTesting
    void processRequest(Request<?> request) {
        long startTimeMs = SystemClock.elapsedRealtime();
        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");
                request.notifyListenerResponseNotUsable();
                return;
            }

            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()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // 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);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        } 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);
            request.notifyListenerResponseNotUsable();
        }
    }
}

可以看到我们的服务员一旦获取到订单就交给主厨(NetWork)处理,并得到成品,并查询是否还有未处理的订单,循环往复。

冷藏工与缓存处理

又有一天,你去店里点了个王八汤,等了两个小时问服务员怎么还没动静,服务员告诉你今天做十个做王八汤的,还排着队挨个做呢。你想,这不得做到天黑啊,赶紧去找老板。 你们不能一次性多做点冷藏起来吗,只要还是达标的就不用挨个去做了,热热不完了吗?老板义正辞严地说道,我们要搞诚信餐饮,我考虑考虑。第二天找了一个冷藏工,专门负责成品冷藏。

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;

    /** Manage list of waiting requests and de-duplicate requests with same cache key. */
    private final WaitingRequestManager mWaitingRequestManager;

    @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 {
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of CacheDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }

    // Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
    // This is needed to avoid keeping previous request references alive for an indeterminate amount
    // of time. Update consumer-proguard-rules.pro when modifying this. See also
    // https://github.com/google/volley/issues/114
    private void processRequest() throws InterruptedException {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.
        final Request<?> request = mCacheQueue.take();
        processRequest(request);
    }

    @VisibleForTesting
    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");

        // If the request has been canceled, don't bother dispatching it.
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // 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.
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // If it is completely expired, just send it to the network.
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

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

            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                // 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) {
                                    // Restore the interrupted status
                                    Thread.currentThread().interrupt();
                                }
                            }
                        });
            } else {
                // request has been added to list of waiting requests
                // to receive the network response from the first request once it returns.
                mDelivery.postResponse(request, response);
            }
        }
    }
}    

前台与队列封装

现在服务员和冷藏工都可能出菜,我们还得找个前台来统筹安排。 如果顾客赶时间,订单上注明了只要食物还比较新鲜,不介意使用库存,那么前台就将订单交给冷藏工,否则交给服务员。如果被冷藏工告知食物过期了,那么就将订单转交给服务员。

设计一个前台

public class RequestQueue {
 
    /** The cache triage queue. */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>();

    /** The queue of requests that are actually going out to the network. */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>();

    /** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /** Cache interface for retrieving and storing responses. */
    private final Cache mCache;

    /** Network interface for performing requests. */
    private final Network mNetwork;

    /** Response delivery mechanism. */
    private final ResponseDelivery mDelivery;

    /** The network dispatchers. */
    private final NetworkDispatcher[] mDispatchers;

    /** The cache dispatcher. */
    private CacheDispatcher mCacheDispatcher;
 
    /** Starts the dispatchers in this queue. */
    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();
        }
    }

    /**
     * Adds a Request to the dispatch queue.
     *
     * @param request The request to service
     * @return The passed-in request
     */
    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);
        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;
        }
        mCacheQueue.add(request);
        return request;
    }

}

可以看到前台管理了 N个 服务员(NetworkDispatcher),一个冷藏工(CacheDispatcher),调用 start 方法两个线程就开始做无限循环。顾客订单通过 add 方法添加进来,如果请求注明了禁止缓存则直接交给服务员处理;否则交给冷藏工。

现在我们再梳理一下整个流程

  1. 顾客将订单交给前台;
  2. 前台根据订单备注来决定把订单交给服务员还是冷藏工;
  3. 如果交给冷藏工,冷藏工去仓库查询是否有可用库存。如果没有,交给服务员,进入下一步;如果有,交给顾客,终止。
  4. 如果交给服务员,服务员再转交给主厨,主厨再交给范师傅。主厨判定中途发生问题时是否重试,服务员负责将新的订单源源不断地传给主厨。
  5. 服务员拿到成品后,根据订单要求决定是否往冷库存储。交给顾客,终止。

细节分析

前文简单地将Volley的架子搭建好,下面继续看看里面的细节。

前台(RequestQueue)

RequestQueue#start方法

    public void start() {
    // 1. 先重置
        stop(); // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        // CacheDispatcher就代表冷藏工,它持有库存请求单、网络请求单、冷库、回调。
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        // start开始后,开始无限循环
        mCacheDispatcher.start();

        // NetworkDispatcher代表服务员,它持有网络请求单、请求封装类(主厨)、冷库、回调。
        // start开始后,开始无限循环查询订单
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher =
                    new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

RequestQueue#stop方法

    /** Stops the cache and network dispatchers. */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (final NetworkDispatcher mDispatcher : mDispatchers) {
            if (mDispatcher != null) {
                mDispatcher.quit();
            }
        }
    }

    // Dispatcher#quit方法,先将中止标志位置为 true,调用 interrupt 方法会抛出 InterruptedException ,在异常捕获中跳出 while 循环。
    public void quit() {
        mQuit = true;
        interrupt();
    }

RequestQueue#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 标记属于当前队列,在特定时候需要通过当前对象移除 Request
        request.setRequestQueue(this);
        // 加锁添加
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

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

        // 如果请求不可缓存,请跳过缓存队列并直接进入网络。向队列添加 Request 完成后,服务员或冷藏工可以通过 while 循环得到它。
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        mCacheQueue.add(request);
        return request;
    }

RequestQueue#cancelAll方法

    public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
            // 当满足指定条件时,将 Request 标记为 cancel,后续处理过程中,会对这个状态进行判断。
                if (filter.apply(request)) {
                    request.cancel();
                }
            }
        }
    }

冷藏工(CacheDispatcher)

CacheDispatcher#processRequest方法

    void processRequest(final Request<?> request) th  rows InterruptedException {
    // 1. 标记 Request 来源
        request.addMarker("cache-queue-take");

        // 2. 如果 Request 已被标记移除,结束
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // 3. 尝试从冷库中检索出缓存,key 为字符串 [Request Method + URL]
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
        // 4. 标记缓存不存在
            request.addMarker("cache-miss");
            // 5. 关于 mWaitingRequestManager 我们后面再看 TODO
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
            // 6. 将该请求添加到网络请求队列中去,终止。
                mNetworkQueue.put(request);
            }
            return;
        }
 
        if (entry.isExpired()) {
        // 7. 已拿到缓存,但是过期了,标记
            request.addMarker("cache-hit-expired");
            // 8. Request 持有这个缓存
            request.setCacheEntry(entry);
           
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
            // 9. 将该请求添加到网络请求队列中去,终止。
                mNetworkQueue.put(request);
            }
            return;
        }

        // 10. 拿到未过期的缓存,标记
        request.addMarker("cache-hit");
        // 11. 构造 Response,Response 的 Json转换放在这一步
        Response<?> response =
                request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
        request.addMarker("cache-hit-parsed");

        if (!entry.refreshNeeded()) {
            // 12. 无需刷新数据,回调,终止。
            mDelivery.postResponse(request, response);
        } else {
            // 13. 软过期缓存命中,我们可以直接响应缓存 Response 。但我们也需要将其扔进网络请求队列中刷新数据,标记
            request.addMarker("cache-hit-refresh-needed");
            request.setCacheEntry(entry);
            // 14. 标记为软过期响应
            response.intermediate = true;

            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                // 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) {
                                    // Restore the interrupted status
                                    Thread.currentThread().interrupt();
                                }
                            }
                        });
            } else {
                // request has been added to list of waiting requests
                // to receive the network response from the first request once it returns.
                mDelivery.postResponse(request, response);
            }
        }
    }

CacheDispatcher#WaitingRequestManager类


    private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener {

        /**
         * Staging area for requests that already have a duplicate request in flight.
         *
         * <ul>
         *   <li>containsKey(cacheKey) indicates that there is a request in flight for the given
         *       cache key.
         *   <li>get(cacheKey) returns waiting requests for the given cache key. The in flight
         *       request is <em>not</em> contained in that list. Is null if no requests are staged.
         * </ul>
         */
        private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>();

        private final CacheDispatcher mCacheDispatcher;

        WaitingRequestManager(CacheDispatcher cacheDispatcher) {
            mCacheDispatcher = cacheDispatcher;
        }

        /** Request received a valid response that can be used by other waiting requests. */
        @Override
        public void onResponseReceived(Request<?> request, Response<?> response) {
            if (response.cacheEntry == null || response.cacheEntry.isExpired()) {
                onNoUsableResponseReceived(request);
                return;
            }
            String cacheKey = request.getCacheKey();
            List<Request<?>> waitingRequests;
            synchronized (this) {
                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.
                for (Request<?> waiting : waitingRequests) {
                    mCacheDispatcher.mDelivery.postResponse(waiting, response);
                }
            }
        }

        /** No valid response received from network, release waiting requests. */
        @Override
        public synchronized void onNoUsableResponseReceived(Request<?> request) {
            String cacheKey = request.getCacheKey();
            List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
            if (waitingRequests != null && !waitingRequests.isEmpty()) {
                if (VolleyLog.DEBUG) {
                    VolleyLog.v(
                            "%d waiting requests for cacheKey=%s; resend to network",
                            waitingRequests.size(), cacheKey);
                }
                Request<?> nextInLine = waitingRequests.remove(0);
                mWaitingRequests.put(cacheKey, waitingRequests);
                nextInLine.setNetworkRequestCompleteListener(this);
                try {
                    mCacheDispatcher.mNetworkQueue.put(nextInLine);
                } catch (InterruptedException iex) {
                    VolleyLog.e("Couldn't add request to queue. %s", iex.toString());
                    // Restore the interrupted status of the calling thread (i.e. NetworkDispatcher)
                    Thread.currentThread().interrupt();
                    // Quit the current CacheDispatcher thread.
                    mCacheDispatcher.quit();
                }
            }
        }

        /** 
         * 如果相同的 Request 还是处理中,加入队列中等待之前的 Request 处理完成。
         * @return 如果返回 false,我们应该继续处理请求;true,我们应该暂停处理,等待之前的 Request 处理完毕。
         */
        private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
            String cacheKey = request.getCacheKey();
            // Insert request into stage if there's already a request with the same cache key
            // in flight.
            if (mWaitingRequests.containsKey(cacheKey)) {
                // 1. 如果存在相同的 Request。
                List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new ArrayList<>();
                }
                // 2. 标记
                request.addMarker("waiting-for-response");
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
                return true;
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                // 3. 没有待处理的相同的 Request
                mWaitingRequests.put(cacheKey, null);
                request.setNetworkRequestCompleteListener(this);
                if (VolleyLog.DEBUG) {
                    VolleyLog.d("new request, sending to network %s", cacheKey);
                }
                return false;
            }
        }
    }

也就是说每个 Request 我们都会去判断,此时是不是还有一个相同的 Request 尚在处理中。

最后再看看 mDelivery,它是一个名为 ResponseDelivery 的接口,主要作用是网络请求结果的回调以及线程切换

ResponseDelivery源码

public interface ResponseDelivery {
    /** Parses a response from the network or cache and delivers it. */
    void postResponse(Request<?> request, Response<?> response);

    /**
     * Parses a response from the network or cache and delivers it. The provided Runnable will be
     * executed after delivery.
     */
    void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /** Posts an error for the given request. */
    void postError(Request<?> request, VolleyError error);
}

`ExecutorDelivery源码`

简单的三个方法,分别用于处理响应成功和响应失败的情况。再看一看它的实现类

public class ExecutorDelivery implements ResponseDelivery {
    /** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     *
     * @param handler {@link Handler} to post responses on
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster =
                new Executor() {
                    @Override
                    public void execute(Runnable command) {
                        handler.post(command);
                    }
                };
    }

    /**
     * Creates a new response delivery interface, mockable version for testing.
     *
     * @param executor For running delivery tasks
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse  Request<?> request, Response<?> response, Runnable runnable) {
    // 1. 标记
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /** A Runnable used for delivering network responses to a listener on the main thread. */
    @SuppressWarnings("rawtypes")
    private static class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = runnable;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // NOTE: If cancel() is called off the thread that we're currently running in (by
            // default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
            // won't be called, since it may be canceled after we check isCanceled() but before we
            // deliver the response. Apps concerned about this guarantee must either call cancel()
            // from the same thread or implement their own guarantee about not invoking their
            // listener after cancel() has been called.

            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
            // 1. Request 被取消,标记
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) {
            // 2. 成功,回调
                mRequest.deliverResponse(mResponse.result);
            } else {
            // 3. 报错,回调
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            // 4. 是否是软过期,标记
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }
            // 5. 处理其他逻辑
            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
        }
    }
}

运行在哪个线程取决于 Handler,默认主线程。

服务员(NetworkDispatcher)

NetworkDispatcher源码

    void processRequest(Request<?> request) {
        long startTimeMs = SystemClock.elapsedRealtime();
        try {
        // 1. 标记 Request 来源
            request.addMarker("network-queue-take");

            // If the request was cancelled already, do not perform the
            // network request.
            if (request.isCanceled()) {
            // 2. 已取消
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }
            // 3. ???统计流量??
            addTrafficStatsTag(request);

            // 4. 让主厨完成请求
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");
 
            // 5. 当服务器返回304表明已有未过期的缓存,并且已经回调过。终止
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // 6. 数据转换,如gson解析等
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // 7. 如果可用,写入缓存。备注,当返回304的时候只更新元数据。
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // 8. 标记数据已回传,并通过 Delivery 回传数据
            request.markDelivered();
            mDelivery.postResponse(request, response);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
        // 9. 异常处理
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        } 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);
            request.notifyListenerResponseNotUsable();
        }
    }

主厨(Network)

BasicNetwork#performRequest

    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                // 跟缓存有关的表头
                Map<String, String> additionalRequestHeaders =
                        getCacheHeaders(request.getCacheEntry());
                        // 真正执行网络请求
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                // 获取响应码
                int statusCode = httpResponse.getStatusCode();
                // 响应报头
                responseHeaders = httpResponse.getHeaders();
                // Handle cache validation.

                // 处理 body
                InputStream inputStream = httpResponse.getContent();
                if (inputStream != null) {
                    responseContents =
                            inputStreamToBytes(inputStream, httpResponse.getContentLength());
                } else {
                    // Add 0 byte response as a way of honestly representing a
                    // no-content request.
                    responseContents = new byte[0];
                }
 
                if (statusCode < 200 || statusCode > 299) {
                // 请求不成功,抛出异常
                    throw new IOException();
                }
                // 返回 Response
                return new NetworkResponse(
                        statusCode,
                        responseContents,
                        /* notModified= */ false,
                        SystemClock.elapsedRealtime() - requestStart,
                        responseHeaders);
            } catch (SocketTimeoutException e) {
            // 1. 纳尼,超时了,吃掉异常,进入下一个 while 循环
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (MalformedURLException e) {
            // 2. 纳尼,url都拼错了,处理不了抛异常
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusCode();
                } else {
                // 抛出连接异常
                    throw new NoConnectionError(e);
                }
                VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                NetworkResponse networkResponse;
                if (responseContents != null) {
                    networkResponse =
                            new NetworkResponse(
                                    statusCode,
                                    responseContents,
                                    /* notModified= */ false,
                                    SystemClock.elapsedRealtime() - requestStart,
                                    responseHeaders);
                    if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
                            || statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                            // 3. 纳尼,权限问题,吃掉异常,进入下一个 while 循环
                        attemptRetryOnException(
                                "auth", request, new AuthFailureError(networkResponse));
                    } else if (statusCode >= 400 && statusCode <= 499) {
                        // Don't retry other client errors.
                        throw new ClientError(networkResponse);
                    } else if (statusCode >= 500 && statusCode <= 599) {
                        if (request.shouldRetryServerErrors()) {
                        // 重连机制
                            attemptRetryOnException(
                                    "server", request, new ServerError(networkResponse));
                        } else {
                            throw new ServerError(networkResponse);
                        }
                    } else {
                        // 3xx? No reason to retry.
                        throw new ServerError(networkResponse);
                    }
                } else {
                // 重连机制
                    attemptRetryOnException("network", request, new NetworkError());
                }
            }
        }
    }

BasicNetwork#inputStreamToBytes方法

    /** Reads the contents of an InputStream into a byte[]. */
    private byte[] inputStreamToBytes(InputStream in, int contentLength)
            throws IOException, ServerError {
            // mPool作用是对象复用
        PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, contentLength);
        byte[] buffer = null;
        try {
            if (in == null) {
                throw new ServerError();
            }
            buffer = mPool.getBuf(1024);
            int count;
            while ((count = in.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } finally {
            try {
                // Close the InputStream and release the resources by "consuming the content".
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                // This can happen if there was an exception above that left the stream in
                // an invalid state.
                VolleyLog.v("Error occurred when closing InputStream");
            }
            mPool.returnBuf(buffer);
            bytes.close();
        }
    }

ByteArrayPool源码

这个类主要是主要是为了复用 byte[],I/O操作时经常创建大量临时 byte[] 用于复制数据

public class ByteArrayPool {
    /** The buffer pool, arranged both by last use and by buffer size */
    private final List<byte[]> mBuffersByLastUse = new ArrayList<>();

    private final List<byte[]> mBuffersBySize = new ArrayList<>(64);

    /** The total size of the buffers in the pool */
    private int mCurrentSize = 0;

    /** 
     * 数据池大小的上限,超过这个值将移除 old buffers
     */
    private final int mSizeLimit;

    /** Compares buffers by size */
    protected static final Comparator<byte[]> BUF_COMPARATOR =
            new Comparator<byte[]>() {
                @Override
                public int compare(byte[] lhs, byte[] rhs) {
                    return lhs.length - rhs.length;
                }
            };

    /** @param sizeLimit the maximum size of the pool, in bytes */
    public ByteArrayPool(int sizeLimit) {
        mSizeLimit = sizeLimit;
    }

    /**
     * 从数据池中找一个超过指定大小的 byte[]返回(不存在就创建一个),从数据池移除这个byte[] 对象
     */
    public synchronized byte[] getBuf(int len) {
        for (int i = 0; i < mBuffersBySize.size(); i++) {
            byte[] buf = mBuffersBySize.get(i);
            if (buf.length >= len) {
                mCurrentSize -= buf.length;
                mBuffersBySize.remove(i);
                mBuffersByLastUse.remove(buf);
                return buf;
            }
        }
        return new byte[len];
    }

    /**
     * return byte[]到 pool中,相当于添加。
     */
    public synchronized void returnBuf(byte[] buf) {
        if (buf == null || buf.length > mSizeLimit) {
        // 如果直接超过 pool MaxSize,结束
            return;
        }
        mBuffersByLastUse.add(buf);
        int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
        if (pos < 0) {
            pos = -pos - 1;
        }
        // 由大到小排序,插入
        mBuffersBySize.add(pos, buf);
        mCurrentSize += buf.length;
        trim();
    }

    /**  保证 pool 满足 maxSize. */
    private synchronized void trim() {
        while (mCurrentSize > mSizeLimit) {
            byte[] buf = mBuffersByLastUse.remove(0);
            mBuffersBySize.remove(buf);
            mCurrentSize -= buf.length;
        }
    }
}

图片加载 (ImageLoader)

官方介绍说Volley适合进行数据量不大但是通信频繁的网络操作,所以它及其适合图片加载。

图片加载实现了一个新的的 Request,它主要作用是将 InputStream 解析为 Bitmap.

ImageRequest#doParse方法

    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
        // 1. 未指定宽高,直接解码图片
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // 2. 如果需要缩放图片,先获取图片宽高。
            decodeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
            int actualWidth = decodeOptions.outWidth;
            int actualHeight = decodeOptions.outHeight;

            // Then compute the dimensions we would ideally like to decode to.
            int desiredWidth =
                    getResizedDimension(
                            mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType);
            int desiredHeight =
                    getResizedDimension(
                            mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType);

            // Decode to the nearest power of two scaling factor.
            decodeOptions.inJustDecodeBounds = false;
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize =
                    findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // 3. 缩放
            if (tempBitmap != null
                    && (tempBitmap.getWidth() > desiredWidth
                            || tempBitmap.getHeight() > desiredHeight)) {
                bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }

        if (bitmap == null) {
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }

ImageLoader#get方法

    public ImageContainer get(
            String requestUrl,
            ImageListener imageListener,
            int maxWidth,
            int maxHeight,
            ScaleType scaleType) {

        // only fulfill requests that were initiated from the main thread.
        Threads.throwIfNotOnMainThread();
// 1. 获取请求的唯一标识
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);

        // 2. 先查询缓存
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container =
                    new ImageContainer(
                            cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
                            // 3. 直接返回结果
            imageListener.onResponse(container, true);
            return container;
        }

        // 4. 未命中缓存,构建 Container 对象。Container和imageListener关联起来,最终会回调 Container#imageListener接口
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // 5. 通知调用者,此时可显示默认图片
        imageListener.onResponse(imageContainer, true);

        // 6. 检查请求是否已存在。有一个记录图片批量请求的类,将该请求放在里面,并返回
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // 7. 代表这是一个全新的请求
        Request<Bitmap> newRequest =
                makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
        // 8. 添加到 Volley 的请求队列中。成功响应后,会回调 Request#Listener接口,这个接口的方法调用了ImageLoader#onGetImageSuccess方法。
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }

ImageLoader#onGetImageSucces方法

    protected void onGetImageSuccess(String cacheKey, Bitmap response) {
        // 1. 先缓存一份
        mCache.putBitmap(cacheKey, response);

        // 2. 从 in-flight 列表移除
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        if (request != null) {
            // Update the response bitmap.
            request.mResponseBitmap = response;

            // 3. 在主线程中处理结果
            batchResponse(cacheKey, request);
        }
    }

ImageLoader#batchResponse方法

    private void batchResponse(String cacheKey, BatchedImageRequest request) {
        mBatchedResponses.put(cacheKey, request);
        // If we don't already have a batch delivery runnable in flight, make a new one.
        // Note that this will be used to deliver responses to all callers in mBatchedResponses.
        if (mRunnable == null) {
            mRunnable =
                    new Runnable() {
                        @Override
                        public void run() {
                        // 1. mBatchedResponses对应多个请求 2. BatchedImageRequest表示某个请求对应多个观察者
                            for (BatchedImageRequest bir : mBatchedResponses.values()) {
                                for (ImageContainer container : bir.mContainers) {
                                    // If one of the callers in the batched request canceled the
                                    // request
                                    // after the response was received but before it was delivered,
                                    // skip them.
                                    if (container.mListener == null) {
                                        continue;
                                    }
                                    if (bir.getError() == null) {
                                        container.mBitmap = bir.mResponseBitmap;
                                        container.mListener.onResponse(container, false);
                                    } else {
                                        container.mListener.onErrorResponse(bir.getError());
                                    }
                                }
                            }
                            mBatchedResponses.clear();
                            mRunnable = null;
                        }
                    };
            // Post the runnable.
            mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
        }
    }

总结

粗糙地过了一遍 Volley,总的感觉是分层明显,可以作为面向接口编程的Demo。当然还有细节也值得探究,例如本地缓存、多线程处理等等,这个下次单独总结。