Android面试集锦

Volley 源码解析及对 Volley 的扩展(二)

2017-06-25  本文已影响68人  lijiankun24

Volley 源码解析及对 Volley 的扩展系列的第一篇文章中,介绍了一种通过继承 StringRequestJsonObjectRequest等自定义类,只需要重写其中的一个方法,即可获得网络请求的耗时和网络请求的结果,详见第一篇文章

在这篇文章中将对 Volley 的源码进行解析,只有真正去研究 Volley 的源码之后,才会发现 Volley 设计的真是太精妙了。面向接口编程 在 Volley 中体现的非常彻底。

创建 RequestQueue 请求队列对象

了解 Volley 用法的人都知道,使用 Volley 进行网络请求的第一步就是创建一个请求队列 RequestQueue 对象,创建 RequestQueue 对象的代码如下所示:

RequestQueue mQueue = Volley.newRequestQueue(this);

在 Volley 中是通过静态工厂方法的方式创建 RequestQueue 对象的,Volley 类的源码如下所示:

public class Volley {

    /** 默认的文件缓存路径 */
    private static final String DEFAULT_CACHE_DIR = "volley";

    /**
     * 创建一个默认的请求队列对象,并调用 {@link RequestQueue#start()} 方法启动它。
     *
     * @param context 一个 {@link Context} 对象用于创建缓存文件对象
     * @param stack   一个用于执行网络请求的 {@link HttpStack} 对象,若为 null,则使用默认的网络请求对象
     * @return 一个已经启动的 {@link RequestQueue} 对象
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // 在 Android SDK 9 之前,HttpUrlConnection 有 Bug,不可靠,所以使用 HttpClient
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

    /**
     * 创建一个默认的请求队列对象,并调用 {@link RequestQueue#start()} 方法启动它。
     *
     * @param context 一个 {@link Context} 对象用于创建缓存文件对象
     * @return 一个已经启动的 {@link RequestQueue} 对象
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
}
  1. Volley 类中有两个重载的静态方法
public static RequestQueue newRequestQueue(Context context);

public static RequestQueue newRequestQueue(Context context, HttpStack stack);

第一个方法的实现调用了第二个方法,传入的 HttpStack 对象为 null

  1. 通过包名和版本号创建 userAgent 对象
  2. 如果传入的 HttpStack 对象为 null, 则根据 SDK 版本号创建默认的 HttpStack 对象,若 SDK 版本号大于等于 9,则创建 HurlStack 对象(内部使用 HttpUrlConnection 实现);否则创建 HttpClientStack 对象(内部使用 HttpClient 实现)。
  3. 通过已经创建的 HttpStack 对象创建一个 Network 具体实现类 BasicNetwork 的对象
  4. 通过 BasicNetwork 对象和 DiskBasedCache 磁盘缓存对象创建一个 RequestQueue 对象,并启动。

创建一个 Request 对象

Request 是代表网络请求的一个抽象类,其中有两个抽象方法,子类必须实现这两个方法:

/**
 * 子类必须实现此方法,用于解析网络请求的响应,并返回合适的类型对象。这个方法会在一个
 * 工作线程中被调用(即不会在 UI 线程中调用此方法),如果此方法返回 null,则结果并不会被发送。
 *
 * @param response 来自于网络请求的响应
 * @return 解析的结果,如果发生错误则返回 null
 */
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

/**
 * 子类必须实现这个方法,把解析的结果发送给监听器。其中 T 类型的参数 response 要保证
 * 不可以为 null,如果解析失败,则解析的结果不会通过此方法发送。
 *
 * @param response 通过 {@link #parseNetworkResponse(NetworkResponse)} 方法解析的结果
 */
abstract protected void deliverResponse(T response);
  1. 默认实现 Request 的子类有:StringRequestJsonObjectRequest
    JsonArrayRequestImageRequest
  2. 我们也可以自定义一个实现 Request 的类,实现上面两个方法,将其加入到网络请求队列中进行网络请求。在下一篇博客中将会举两个自定义 Request 的例子
  3. Volley 中包括 8 种 Http 网络请求方式:GETPOSTPUTDELETEHEAD
    OPTIONSTRACEPATCH
  4. Request 类中包含了网络请求的 url,请求方式,请求 Header,请求 Body 和请求的优先级等信息。
  5. 以下三个方法也经常被子类重写
    /**
     * 返回一个 Map 类型的参数,为这个请求添加网络请求头信息 Http Header。
     * 最常用的就是可以把 Cookie 信息通过此方法添加
     */
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

    /**
     * 返回一个字节数组的对象作为 POST 或 PUT 请求的 Body 内容。
     *
     * 当重写此方法时,也需要考虑重写 {@link #getBodyContentType()} 方法
     */
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    /**
     * 在 {@link #getBody()} 没有被重写的情况下,可以通过此方法返回一个
     * Map 类型的参数,用于构建 POST 或 PUT 请求方式的 Body 内容
     *
     * 注意:也可以通过直接重写 {@link #getBody()} 方法自定义 Body 数据。
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }

RequestQueue 源码分析

RequestQueue 是 Volley 中的核心类,主要用于处理添加进来的网络请求。
在本小节中将会分三部分介绍 RequestQueue 的类,分别是:RequestQueue 中的主要属性、RequestQueue 类的构造方法和 RequestQueue 的主要方法。

RequestQueue 中的主要属性


    /**
     * 维护了一个等待请求的集合,如果有一个请求正在被处理并且可以被缓存,如果有新的相同
     * URL 请求被添加进来以后,则会新的请求则会进入此集合中。此集合主要是为了避免相同的
     * 且不必要的网络请求
     */
    private final Map<String, Queue<Request<?>>> mWaitingRequests =
        new HashMap<String, Queue<Request<?>>>();

    /**
     * 正在被此 RequestQueue 处理的请求的集合,如果一个请求正在等待被处理或者正在被
     * 某个调度线程处理,则它会在此集合中
     */
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

    /** 缓存请求队列,在此队列中的请求,将通过缓存获取数据 */
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    /** 网络请求队列,在此队列中的请求,将通过网络向服务器发送请求获取数据 */
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();

RequestQueue 中有两个 基于优先级 Request 的队列:mCacheQueue 缓存请求队列和 mNetworkQueue 网络请求队列

RequestQueue 的构造方法


    /** 默认的网络请求调度线程数量 4 */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /** Cache 接口,用户获取和缓存响应结果,默认的实现类是 DiskBasedCache */
    private final Cache mCache;

    /** Network 接口,用于执行网络请求,默认的实现类是 BasicNetwork */
    private final Network mNetwork;

    /** 响应分发器,默认的实现类是 ExecutorDelivery */
    private final ResponseDelivery mDelivery;

    /** 网络调度线程数组,NetworkDispatcher是 {@link Thread} 的子类*/
    private NetworkDispatcher[] mDispatchers;

    /** 缓存调度线程,是{@link Thread} 的子类*/
    private CacheDispatcher mCacheDispatcher;

    /**
     * 创建工作线程池,不调用 {@link #start()} 方法,就不会开始开始工作,所以创建完请求队列以后,必须调用{@link #start()}
     *
     * @param cache           向磁盘持久化响应结果的缓存对象
     * @param network         执行 Http 请求的对象
     * @param threadPoolSize  网络请求调度线程的数量
     * @param delivery        一个负责分发响应结果和异常的分发器
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

    /**
     * 调用 {@link #RequestQueue(Cache, Network, int, ResponseDelivery)} 实现
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    /**
     * 调用 {@link #RequestQueue(Cache, Network, int)} 实现
     */
    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }  
  1. 有三个构造方法,最终调用的是 RequestQueue(Cache, Network, int,ResponseDelivery) 这个构造方法
  2. 创建一个 ExecutorDelivery 对象并赋值给 mDelivery,其中在 ExecutorDelivery 构造函数中传入的 Handler 对象中的 Looper 对象是主线程的,这样使用 mDelivery 发送的请求响应结果或者异常就被发送到主线程中了
  3. 创建一个 NetworkDispatcher 类型的数组对象 mDispatchers,默认长度是4
  4. 可以看到所依赖的属性 mCachemNetworkmDelivery 都是接口类型的,而不是具体的实现类,这充分体现了面向接口编程的思想

RequestQueue 中的主要方法

还记得在 Volley 类中的 newRequestQueue(Context, HttpStack) 创建完成 RequestQueue 对象 queue 以后,还调用的了 queue.start() 方法,start() 相关方法如下所示:

    /**
     * 启动在此队列中的线程
     */
    public void start() {
        stop();  // 在启动之前,需要确保现在正在运行 mCacheDispatcher 线程和 mDispatchers[] 中的线程被终止
        // 创建缓存调度线程并启动
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 根据 mDispatchers[] 的长度,创建对应数量的网络调度线程添加进 mDispatchers[] 并启动
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /**
     * 停止缓存调度线程 mCacheDispatcher 和网络调度线程 mDispatchers[]
     */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }
  1. NetworkDispatcherCacheDispatcher 都是 Thread 的子类,都是线程,创建完该对象以后都需要进行调用 start() 方法启动该线程。到这块儿的代码,意识到有必要看一下 NetworkDispatcherCacheDispatcher 这两个类的代码了,先不着急,我们先分析完 RequestQueue 的代码。

通过 Volley 进行网络请求时,创建完网络请求之后,需要将网络请求通过 RequestQueue.add(Request) 方法,将网络请求添加进网络请求队列,那么来分析下 add(Request) 方法,这个方法是 RequestQueue 中非常重要的一个方法。


    /** 用于为请求生成一个自动增长的序列号 */
    private AtomicInteger mSequenceGenerator = new AtomicInteger();

    .....

    /**
     * 得到一个序列号
     */
    public int getSequenceNumber() {
        return mSequenceGenerator.incrementAndGet();
    }

    .....

    /**
     * 向请求队列中添加一个请求
     * @param request 向服务器发送的请求
     * @return 已经发送经过处理的请求
     */
    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 设置请求队列,并将其添加进 mCurrentRequests 队列中
        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.
        // 如果该请求 request 不可以缓存的,则跳过缓存队列,直接进入网络请求队列
        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.
        // 首先根据 cacheKey(其实就是url)判断 mWaitingRequests 中是否有相同的请求正在进行,如果有,则将其添加进 mWaitingRequests 队列中
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                // 如果有相同的请求,则进行入队操作
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in flight.
                // 如果在 mWaitingRequests 中没有相同的请求正在进行,则在 mWaitingRequests 插入一个 null 值
                mWaitingRequests.put(cacheKey, null);
                // 将该 request 添加进 mCacheQueue 队列中
                mCacheQueue.add(request);
            }
            return request;
        }
    }
  1. 通过 add(Request) 添加一个网络请求,首先需要将该请求 request 添加到mCurrentRequests 队列中
  2. 如果该请求不走缓存,则直接将该请求 request 添加到网络请求队列 mNetworkQueue 中,结束该方法
  3. 如果该请求 request 可以走缓存,根据 cacheKey (其实就是 url)判断 mWaitingRequests 中是否有相同的请求正在进行,如果有,则将其添加进 mWaitingRequests 队列中,如果没有则在 mWaitingRequests 中添加值为 null 的值,并将其添加进缓存请求队列 mCacheQueue
  4. RequestQueue.add(Request) 方法的流程图(该图出自 Volley 源码解析)如下所示:
RequestQueue-add-flow-chart.png

RequestQueue还有一个常用的方法:RequestQueue.cancelAll(Object)


    /**
     * 一个在 {@link RequestQueue#cancelAll(RequestFilter)} 方法中使用的判断或过滤接口
     */
    public interface RequestFilter {
        public boolean apply(Request<?> request);
    }

    /**
     * 将此队列中符合 filter 条件的所有请求取消
     * @param filter 使用的过滤条件
     */
    public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request<?> request : mCurrentRequests) {
                if (filter.apply(request)) {
                    request.cancel();
                }
            }
        }
    }

    /**
     * 通过给定的 tag 取消在此队列中所有 tag 相同的请求,tag绝对不可以为 bull
     */
    public void cancelAll(final Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("Cannot cancelAll with a null tag");
        }
        cancelAll(new RequestFilter() {
            @Override
            public boolean apply(Request<?> request) {
                return request.getTag() == tag;
            }
        });
    }
  1. 一般我都会重写每个请求对象 ***RequestsetTag() 方法返回一个该请求的 TAG,在合适的地方(比如:Activity.onDestory() 方法中)通过 cancelAll(Object) 取消该 TAG 对应的请求,以防止发生意外(比如:内存泄露)。

NetworkDispatcher 源码分析

由于 NetworkDispatcher 源码也并不算长,只有100+行,直接上源码,里面的注释也很详细了,后面会配有相应的说明和流程图。

public class NetworkDispatcher extends Thread {
    /** 请求服务器的网络请求队列 */
    private final BlockingQueue<Request<?>> mQueue;
    /** 处理网络请求的实现 Network 接口类的对象 */
    private final Network mNetwork;
    /** 写缓存的对象 */
    private final Cache mCache;
    /** 用于发送响应和异常的分发器 */
    private final ResponseDelivery mDelivery;
    /** 用于标志此线程是否中断 Used for telling us to die. */
    private volatile boolean mQuit = false;

    /**
     * 创建一个网络调度线程,必须调用 {@link #start()} 启动此线程
     *
     * @param queue     网络请求队列
     * @param network   执行网络请求的 Network 接口实现类
     * @param cache     将响应写进缓存的 Cache 接口实现类
     * @param delivery  用于分发请求结果的分发器
     */
    public NetworkDispatcher(BlockingQueue<Request<?>> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 强制此调度线程立即停止。如果在队列中仍然有请求,它们不能保证一定会被处理
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    ......

    @Override
    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.
                // 如果服务器返回的 304,并且该请求之前已经得到过并发送过响应结果,则响应结果可以复用,没必要进行新的网络请求,结束本次循环
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    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.
                // 根据 request 的 {@link #shouldCache()} 方法判断此请求是否需要进行缓存,如果需要进行缓存处理,则将其放到 mCache 中
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back
                // 将响应通过 mDelivery 发送到 UI 线程中
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                // 如果抛出 VolleyError 异常,则将网络请求耗时通过{@link volleyError#setNetworkTimeMs(long)}
                // 放进 volleyError 中,并通过 mDelivery 将异常发送出去
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            }
            catch (Exception e) {
                // 若发生其他异常,则生成 VolleyError 对象,并将网络请求耗时放进 volleyError 对象中,
                // 并通过 mDelivery 将异常发送出去
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }
}
  1. run() 方法中有一个 while(true){ ...... } 代码段,可见启动此线程以后,它就进入一直在循环的状态,不断的从网络请求队列 mQueue 中取出网络请求并执行,除非通过 quit() 方法改变 mQuit 成为 true,该方法才会停止
  2. NetworkDispatcher 类中所依赖的属性,mNetworkmCachemDelivery 都是接口类型的,而不是具体的实现类,这也充分体现了面向接口编程的思想
  3. 在上面的代码片段中,注释已经很清楚了。下面是一张 NetworkDispatcher 进行网络请求的流程图,出自 Volley 源码解析
NetworkDispatcher-run-flow-chart.png

CacheDispatcher 源码分析

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;

    /**
     * 创建一个 缓存调度线程,必须调用 {@link #start()} 方法,此线程才会开始工作
     *
     * @param cacheQueue   缓存请求队列
     * @param networkQueue 网络请求队列
     * @param cache        处理缓存的对象
     * @param delivery     分发响应的结果
     */
    public CacheDispatcher(
            BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 强制此线程立即停止,如果在队列中有请求,则不能保证请求一定会被处理
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        // 初始化缓存
        mCache.initialize();

        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                // 从缓存队列中取一个请求,如果没有可用的则阻塞
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                // 如果缓存请求已经被取消,则不用处理它
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                // 通过 request.getCacheKey() 从缓存中取出对应的缓存记录
                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 将结果发送到 UI 线程中
                    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.
                    // 将其传回主线程的同时,将请求放到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;
            }
        }
    }
}
  1. CacheDispatcher 类和 NetworkDispatcher 类的代码很相似,都是 Thread 的子类,创建该类的对象以后都需要通过 start() 方法启动它
  2. CacheDispatcher 所依赖的对象 mCachemDelivery 都是接口类型的,而不是具体的实现类,这也是面向接口编程思想的体现
  3. 下面是一张 CacheDispatcher 进行缓存请求的流程图,同样出自 Volley 源码解析
CacheDispatcher-run-flow-chart.png

BasicNetwork 源码分析

BasicNetworkNetwork 接口的实现类,Network 接口是执行网络请求的接口,其中只有一个方法 performRequest(Request),该方法由于执行网络请求并返回 NetworkResponse 类型的请求结果,那来看一下在 BasicNetwork 中该方法是怎么实现的

public class BasicNetwork implements Network {

    protected final HttpStack mHttpStack;

    ......

    /**
     * 通过 {@link HttpStack#performRequest(Request, Map)} 方法执行网络请求,将得到的网络请求响应包装成 NetworkResponse 类型的对象并将其返回
     */
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        // 记录请求开始的时间
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            // 请求响应的对象引用
            HttpResponse httpResponse = null;
            // 请求响应中的 body
            byte[] responseContents = null;
            // 请求响应中的 header
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                // 请求头
                Map<String, String> headers = new HashMap<String, String>();
                // 将请求中添加的 CacheEntry 添加到请求头中,详见 {@link addCacheHeaders(Map<String, String>, Cache.Entry)} 方法
                addCacheHeaders(headers, request.getCacheEntry());
                // 通过 mHttpStack.performRequest(Request, Map<String, String>) 方法执行具体的网络请求,并得到请求的响应
                httpResponse = mHttpStack.performRequest(request, headers);
                // 请求响应中的状态行信息对象
                StatusLine statusLine = httpResponse.getStatusLine();
                // 请求响应中的 状态码
                int statusCode = statusLine.getStatusCode();
                // 响应中的响应头 Header[] 转换成 Map<String, String> 的形式
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());

                // Handle cache validation.
                // 如果响应中状态码是 304,则表示请求的内容在服务器端没有更改,使用本地的缓存即可
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    // 取出缓存 entry 对象
                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        // 如果 entry 对象为 null,则返回一个 entry 为 null 的 NetworkResponse 对象
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                    // A HTTP 304 response does not have all header fields. We
                    // have to use the header fields from the cache entry plus
                    // the new ones from the response.
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                    // 即使返回 304 的响应,但是真正的响应中的响应头包括两部分信息:当前返回的响应头和缓存中已经缓存的响应头
                    entry.responseHeaders.putAll(responseHeaders);
                    // 使用缓存对象 entry 生成一个 NetworkResponse 对象并返回
                    // 将请求耗时放入 NetworkResponse 对象中
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // Some responses such as 204s do not have content.  We must check.
                // 有一些响应(比如:204)并没有 body 内容,所以必须进行检查
                if (httpResponse.getEntity() != null) {
                  // 将响应中的 HttpEntity 对象转换成 byte[] 类型的 responseContents 对象,详见 {@link entityToBytes(HttpEntity)} 方法
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  // 如果响应中的 HttpEntity 为空,也要给 responseContents 对象赋值
                  responseContents = new byte[0];
                }

                // if the request is slow, log it.
                // 如果请求的耗时太久则打印请求相关的信息,详见 {@link logSlowRequests(long, Request ,byte[], StatusLine)} 方法
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                // 如果响应状态码超出 200-299 的范围,则抛出 IOException 异常
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                // 如果响应状态码在 200-299 之内,则生成 NetworkResponse 对象并返回
                // 将请求耗时放入 NetworkResponse 对象中
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (SocketTimeoutException e) {
                // 处理 SocketTimeout,重复请求
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                // 处理 ConnectTimeout,重复请求
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                // 处理 MalformedURLException,重复请求
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                // 处理 IOException,重复请求
                int statusCode;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().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,
                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        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());
                }
            }
        }
    }

    ......
}
  1. BasicNetwork 类中最重要的一个属性就是 mHttpStackHttpStack 类型的,HttpStack 也是接口类型的,它是具体的执行网络请求的接口,在 Volley 中有两个实现了 HttpStack 接口的类:HurlStackHttpClientStackHurlStack 内部是使用 HttpUrlConnection 实现的,而 HttpClientStack 内部是使用 HttpClient 实现的
  2. BasicNetwork 中,对响应状态码为 304204 等特殊情况做了一定的处理,如果状态码在 200-299 之外则抛出 IOException,在 200-299 之内则生成 NetworkResponse 对象并返回,并对各种异常 SocketTimeoutExceptionConnectTimeoutException 等异常做了特殊的处理
  3. performRequest(Request) 中起始的位置记录请求开始的时间,在生成 NetworkResponse 对象或者抛出 VolleyError 异常中都代码网络请求的时间,这是第一篇博客中请求耗时的原始值

ExecutorDelivery 源码分析

ExecutorDeliveryResponseDelivery 接口的实现类,ResponseDelivery 接口主要有三个方法:

public interface ResponseDelivery {
    /**
     * 解析一个来自网络或者缓存的响应结果并发送
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * 解析一个来自网络或者缓存的响应结果并发送,提供的 Runnable 对象会在发送完结果之后被执行
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * 向该 request 对象发送一个异常
     */
    public void postError(Request<?> request, VolleyError error);
}

接着看一下 ExecutorDelivery 实现类的代码

public class ExecutorDelivery implements ResponseDelivery {
    /** 向主线程中发送结果的线程池对象 */
    private final Executor mResponsePoster;

    /**
     * ExecutorDelivery 的构造方法
     * @param handler {@link Handler} 对象决定了是向哪个线程发送结果
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        // 使用提供的 handler 对象实现一个 Executor 对象
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    /**
     * ExecutorDelivery 的构造方法
     * @param executor 用于发送结果的线程池
     */
    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) {
        request.markDelivered();
        request.addMarker("post-response");
        // 根据传进来的 request 、response 和 runnable 对象,生成一个 ResponseDeliveryRunnable 对象,并使用 mResponsePoster 线程池运行
        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));
    }

    /**
     * 一个用于将网络请求的响应结果发送到主线程的线程对象
     */
    @SuppressWarnings("rawtypes")
    private 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() {
            // If this request has canceled, finish it and don't deliver.
            // 如果请求已经被取消,则结束它并不会发送该请求的结果
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            // 视情况通过不同的方法发送正确的响应或异常
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            // 添加标志
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            // 如果 mRunnable 对象不为 null, 则执行它
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
    }
}
  1. ExecutorDelivery 的构造方法中,需要传入一个 Handler 对象,这个 Handler 对象是非常重要的。都知道每个 Handler 对象都会持有一个 Looper 对象,该对象决定了 Handler 发送的消息或者任务在那个线程中处理。在分析 RequestQueue 源码时,是这样 new ExecutorDelivery(new Handler(Looper.getMainLooper())) 生成的 ExecutorDelivery 默认的对象,所以默认情况下,通过 ExecutorDelivery 对象发送的消息都是在主线程中处理的。这也符合我们的习惯,具体的网络请求结果都是在 UI 线程中直接处理,这样更方便一些
  2. ExecutorDelivery 中的内部类 ResponseDeliveryRunnable ,是非常重要的,在它的 run() 方法中,如果成功则调用 requestdeliverResponse(T) 方法,否则调用 deliverError(VolleyError)
    方法。这里的 deliverResponse(T) 方法内部最终会回调我们在构建 Request 时设置的 Response.Listener 对象 onResponse(T) 方法的。

至此,关于Volley 源码解析及对 Volley 的扩展系列的第二篇文章就结束了,从这边文章中也可以知道为什么都说 Volley 具有很强的扩展性,因为很多地方依赖的属性都是接口,而不是具体的实现类。接下来在第三篇文章中就会对 Volley 做一些扩展。如果有什么问题欢迎指出。我的工作邮箱:jiankunli24@gmail.com


参考资料:

Volley 源码解析 -- grumoon

Volley学习笔记之简单使用及部分源码详解 -- Yongyu

Volley源码分析【面向接口编程的典范】 -- 王世晖

Volley源码解析<四> RequestQueue请求队列 -- fenggit

Android Volley完全解析(四),带你从源码的角度理解Volley -- 郭霖

上一篇下一篇

猜你喜欢

热点阅读