Volley使用总结及源码分析(一)

Volley

Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布,特别适合数据量小,通信频繁的网络操作(Android中大多数网络通信都是这种特点)

Volley 的主要特点:

1. 扩展性强。Volley 中大多是基于接口的设计,可配置性强。
 2. 一定程度符合 Http 规范,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的处理,请求头的处理,缓存机制的支持等。并支持重试及优先级定义。
 3. 提供简便的图片加载工具。

Volley基本用法

RequestQueue mQueue = Volley.newRequestQueue(this);
        StringRequest stringRequest = new StringRequest(Request.Method.POST,"https://www.baidu.com", 
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.d("TAG", response);
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {

                    }
                }){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                return super.getParams();
            }
        };
        mQueue.add(stringRequest);

首先,创建一个RequestQueue请求队列对象,不需要为每一次http请求都创建mQueue,其内部设计已经考虑了高并发,重复创建是非常浪费资源的,一般在需要和网络交互的Activity中创建一个RequestQueue对象就可以。
 其次创建一个Request对象,这里使用Volley提供的Request的子类StringRequest创建对象,需要传入4个参数,第一个是请求的方法(StringRequest还有一个三个参数的构造方法,第一个参数默认设为Request.Method.GET),第二个参数是http请求的目标服务器的URL地址,第三个参数是服务器响应成功的回调,第四个参数是服务器响应失败的回调。POST方法中需要传入参数,使用getParams()方法传递。
 最后将创建好的StringRequest对象添加到RequestQueue中,这样就完成了Volley的基本使用,是不是很简单(不要忘记在你的AndroidManifest中添加网络权限)

Volley请求架构

Volley的请求架构

图片来源:https://raw.githubusercontent.com/android-cn/android-open-project-analysis/master/tool-lib/network/volley/image/Volley-run-flow-chart.png
 图中蓝色框运行在主线程中,绿色框运行在缓存调度线程中,黄色框运行在网络调度线程中。
 在上面的基本用法中,将请求添加到请求队列时,首先判断是否应该缓存(默认全部缓存),如果修改默认状态不缓存,则直接加入到网络请求队列中,否则直接加入到缓存请求队列。RequestQueue会维护一个缓存调度线程mCacheDispatcher和多个网络调度线程池networkDispatcher,当一个Request被加到队列中的时候,缓存调度线程会把这个请求进行筛选:如果这个请求的内容可以在缓存中找到,缓存调度线程会亲自解析相应内容,并分发到主线程(UI)。如果缓存中没有,这个request就会被加入到另一个NetworkQueue,所有真正准备进行网络通信的request都在这里,第一个可用的networkDispatcher线程会从NetworkQueue中拿出一个request扔向服务器。当响应数据到的时候,这个networkDispatcher线程会解析原始响应数据,写入缓存,并把解析后的结果返回给主线程。

Volley源码分析

在使用案例时,我们首先调用了RequestQueue mQueue = Volley.newRequestQueue(this);那就让我们先看看Volley.newRequestQueue(this)方法。

public class Volley {
    private static final String DEFAULT_CACHE_DIR = "volley";
    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 {
                // 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 = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }
}

很简单吧,就是调用了一个两个参数的构造函数,其函数中首先确定了userAgent,该字段是http协议的请求头的字段,表示客户急的信息,看到Volley中默认是包名+版本号的方式。然后,判断第二个参数HttpStack stack是否为空,如果为空则判断当前的Android版本,当前版本在Gingerbread 及之后(即 API Level >= 9)时,采用基于 HttpURLConnection 的 HurlStack,如果小于 9,则采用基于 HttpClient 的 HttpClientStack。原因请参考这篇文章:Android访问网络,使用HttpURLConnection还是HttpClient?之后,创建了BasicNetwork对象,根据传入的HttpStack对象来处理网络请求。最后创建RequestQueue对象,调用其start方法。
 我们接着看queue.start();方法:

private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
    /**
     * 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();
        }
    }

start方法中先调用了stop方法,确保当前运行的缓存调度线程mCacheDispatcher和网络调度线程池networkDispatcher是停掉的。然后创建了一个缓存调度线程mCacheDispatcher和四个网络调度线程池networkDispatcher并启动运行在后台。注意创建缓存线程的构造函数中的参数mCacheQueue, mNetworkQueue, mCache, mDelivery以及网络调度线程池的参数mNetworkQueue, mNetwork, mCache, mDelivery

回到我们的案例中,最后调用了RequestQueue的add方法,让我们来看一下:

   private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
    private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
/**
     * 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;
        }

        // 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);
            }
            return request;
        }
    }

RequestQueue 中维护了两个基于优先级的 Request 队列,缓存请求队列mCacheQueue和网络请求队列mNetworkQueue。放在缓存请求队列中的 Request,将通过缓存获取数据;放在网络请求队列中的 Request,将通过网络获取数据;维护了一个正在进行中,尚未完成的请求集合mCurrentRequest;维护了一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列mWaitingRequests
 add方法中,先将传入的request设置自身所在的RequestQueue,然后使用mCurrentRequests保存request,设置request在当前队列中的序列号,之后判断,request是否需要缓存,如果不缓存直接添加到网络请求队列,否则的话,先判断这个request是否曾经进入过队列,request.getCacheKey()默认返回的是请求的URL,如果该请求不是第一次处理,在进入mCacheQueue之前,可能回被加入mWaitingRequests(如果有相同url的请求正在处理)。作用是避免重复的请求多次执行,提高请求速度。当请求处理完之后,会检查mWaitingRequests是否有等待的请求,并全部加入缓存队列。

总结

Volley中最核心的是RequestQueue,首先在其start方法中会默认开启一个缓存线程和四个网络线程,开启线程是最主要的参数是RequestQueue维护的mCacheQueuemNetworkQueue,在add方法中根据一定规则向这两个请求队列添加request请求。以上就是RequestQueue的主要流程,下一次我们将继续分析Volley的网络线程和缓存线程。

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

推荐阅读更多精彩内容