Volley框架辨析( 二)从开始到结束

Volley框架剖析( 二)从开始到结束

上一篇中,我们分析了Volley的一个总体组成。今天我们继续分析Volley的一个数据流走向,即从初始化到发起请求,再到请求结束的一个流程。

先看初始化。
Volley的初始化,实际上就是返回一个RequestQueue的队列。在Volley中调用。一个最简单的创建方式即有一个Context即可。

/**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

我们的分析当然不是点到即止,所以,我们重点关注参数最全的构造函数。


    /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     * You may set a maximum size of the disk cache in bytes.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @param stack An {@link HttpStack} to use for the network, or null for default.
     * @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
   File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

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

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);

        RequestQueue queue;
        if (maxDiskCacheBytes <= -1)
        {
            // No maximum size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }
        else
        {
            // Disk cache size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }

        queue.start();

        return queue;
    }

这个方法有三个参数Context,HttpStack和maxDiskCacheBytes.
前面说了HttpStack是一个接口,用于Http请求的具体实现,maxDiskCacheBytes用于创建磁盘缓存时来设置最大的缓存容量。

初始化中有意思的是自定义了一个USERAGENT,这个USERAGENT用在HTTP头部中,表示当前的请求是由当前软件发起,方便日后查找问题和统计。这里也建议自己在创建网络请求的时候,用自己的UA代替。按照经验,UA通常由软件(框架)名称+版本+其他附加信息构成。

创建HTTP请求这里也区别开了SDK VERSION 在9以上和9以下。
对于9以上的版本,直接使用了HttpURLConnection,对于9以下的版本使用了AndroidHttpClient,什么原因呢,可以参考:http://android-developers.blogspot.com/2011/09/androids-http-clients.html
由于这文章需要*,我这里把要点罗列如下:

1.HttpURLConnection是一个轻量级的Http连接方案,适用于大多数应用,它支持流压缩与网络缓存,能够减少网络流量与节省电池,
因此推荐Gingerbread 及以上的使用这个API。
2. 在Gingerbread以下,HttpURLConnection当在一个还没有读完的流上调用close方法时,这个流上没有读完的部分会缓存下来,然后会加到下一个请求的数据区,导致下一个请求的数据错误。
3. AndroidHttpClient复杂扩展性好且稳定,但AndroidTeam很难在保证兼容性的同时改进一些特性,因此,通常不推荐使用。

当然如果你是自己扩展之后,传的一个实现了HttpStack的对象,就不会走到上面的逻辑中。

在根据版本获取了HttpStack对象后,将其作为参数传给Network的默认实现BasicNetwork对象。即BasicNetwork中实际上是对HttpStack的一个包装(当然,如果自己实现了其他的Network,就不一定需要这个HttpStack)。

之后,根据maxDiskCacheBytes创建了一个磁盘缓存。

最后,启动这个RequestQueue。
启动时做了两件事,创建CacheDispatcher和NetworkDispatcher。这两个都是线程类的子类,CacheDispatcher 的线程主要在作用是本地的IO操作,加载缓存用。NetworkDispatcher的线程主要执行网络操作,默认创建了1个缓存线程和4个网络线程。

在初始化完成之后,就可以使用RequestQueue来发起网络请求。

发起请求从RequestQueue的add方法开始。

Volley框架辨析( 二)从开始到结束
/img/2015/04/26/1634063.png.png

流程如上图,是比如简单的。这里说几个有趣细节。
在add的第一行代码,我们看到 request把RequestQueue传到了对象中,并作为一个成员变量保存。那为什么?因为request的生命周期是由request的一些方法来维护,此后就与这个queue无关了,但是在一个request又需要在结束时,把自己从queue中移除掉,它需要 这个引用来做移除操作。可以参见request.finish的代码。

 if (mRequestQueue != null) {
            mRequestQueue.finish(this);
 }

每个请求,有一个唯一的序列号,这个是用AtomicInteger来实现的。

private AtomicInteger mSequenceGenerator = new AtomicInteger();

  /**
     * Gets a sequence number.
     */
 public int getSequenceNumber() {
        return mSequenceGenerator.incrementAndGet();
 }

在没有主动设置优先级的情况,这个是做为request的优先级来处理的。即数字越小,优先级越高。

还有个有意思的点是request的日志系统,所有的状态改变,都会有事件记录,以便跟踪问题。
Volley框架辨析( 二)从开始到结束

从上图可以看出,比如缓存命中情况,网络请求情况,重试情况等,都有一一记录。

volley避免重复请求节约网络资源的逻辑写得很有技巧。这里巧妙的利用了一个空的value来处理,可以少生成一个链表对象。

  // Insert request into stage if there's already a request with the same cache key in flight.
        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.put(cacheKey, null);
                mCacheQueue.add(request);
            }

如果是第一次请求,直接放入一个空value,第二次如果是相同的请求,才会增加一个链表来缓存相同的request。当第一个请求网络完成后,它才会把这个链表中的请求通通加到缓存队列中。这样,即可以保证即时很短时间内的并发相同请求,实际只有一个才会使用网络资源,又能保证相同的请求不会被丢弃。

volley的很多细节还是很值得我们学习。下一次我们对一些重要的类进行分析。