Volley使用及其原理解析

前言

在现在的Android开发之中,已经比较少人使用volley进行网络请求了,之所以现在还写这篇关于Volley的文章,是因为volley是一个优秀的框架,其设计严格遵循了面向对象的设计原则,学习volley的设计原则,对自己的项目开发有比较好的提示作用。

使用方式

  1. 导入
    在AndroidStudio里面,只需要在Projrct structure里面添加依赖,在搜索框里输入“volley”,直接搜索v,然后点击添加即可。


    添加volley
  2. 使用
    使用相对来说比较简单,首先需要创建一个RequestQueue,然后添加Request即可
        RequestQueue queue = Volley.newRequestQueue(this);
        queue.add(new StringRequest(Request.Method.POST, "URL", new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

            }
        }));

对于添加的Request,官方默认实现的有以下几种请求方式,当然可以自定制Request,后续会讲到。


Request的实现类

原理解析

在详细解释每个步骤的原理之前,先看一下volley的整个UML图

Volley整体UML图

如图,红框部分是整个Volley主要部分,可以看到,最中间的RequestQueue是把所有功能组合起来的类,而整个Volley设计是遵守了依赖倒转原则,即针对接口编程,而不是针对实现编程,由此对于功能的拓展将很容易实现。接下来讲述整个Volley的运作过程。

  1. 创建
    在详细解释之前,先看一下创建的整体流程,当熟悉整个流程之后,对源码的理解会容易很多。
    Volley.newRequestQueue流程

    对于Volley而言,创建是由Volley.newRequestQueue()开始的,返回一个RequestQueue实例,该静态方法有两个重载,如下
RequestQueue newRequestQueue(Context context);
RequestQueue newRequestQueue(Context context, HttpStack stack);

当使用第一个重载方法时,其实也是调用到第二个方法。

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

第二个参数HttpStack,是用来进行网路请求的,由Volley的整体框架图,可以看出其有两个实现子类,选择哪个子类是有SDK的版本决定的,源码如下:

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//放置缓存的地方
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        String userAgent = "volley/0";
        if (stack == null) {
               //如果版本号大于9(V2.3)
            if (Build.VERSION.SDK_INT >= 9) {
                //创建基于HttpURLConnection的HttpStack
                stack = new HurlStack();
            } else {
                //创建基于HttpClient的HttpStack
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //创建网络请求和queue
        Network network = new BasicNetwork(stack);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

由代码可以看出,完全可以自定义实现HttpStack,这样就可以和其它的网络请求框架结合起来或者是自定义的网络请求结合起来了。
对于RequestQueue的构造方法,最终都会调用到一下方法

    /**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     * @param delivery A ResponseDelivery interface for posting responses and errors
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

可以看第三个参数,网络请求的线程数,默认是4,当然也可以自己完全定制一个自己想要的线程数。
RequestQueue#start方法,主要是创建缓存分发线程和网络访问分发线程,一个queue只有一条缓存线程,有threadPoolSize数量的网络访问线程,默认是4,因此不适用于数据量大、通讯频繁的网络操作,因为会占用网络请求的访问线程。

    public void start() {
        stop();  // 确定当前线程已经停下来了
        // 只创建一条缓存分发线程并且启动,注入mNetWorkQueue,用于缓存获取失          
        // 败时进行网络请求
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        //创建多条网络请求线程并启动,在创建时注入mCache,用于缓存
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }
  1. 添加Request
    对于请求的添加,整体流程图如下所示:


    添加请求

    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.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

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

        // 判断是否可缓存,如果不可缓存,直接添加到网络请求队列
        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();
        //如果已经存在当前Request,那么只需要在等待队列里面插入当前请求即可,防止多次网络访问
            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);
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }
  1. 对Request进行处理
    在上部分代码中,可以看到add()方法只是简单的把请求插入到了网络请求对列或者缓存请求对列,按照插入请求之后就会进行网络请求,可以猜测这两个线程都是在不断的进行着轮询,先来看一下CacheDispatcher的处理流程
    流程图


    CacheDispatcher处理流程

    CacheDispatcher的run()源码如下

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            try {
                // 阻塞获取一个Request,queue原型为BlockingQueue
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                // 如果请求已经取消,则跳过该请求
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                // 尝试从缓存里面获取数据
                Cache.Entry entry = mCache.get(request.getCacheKey());
                //如果为空,表示没有缓存,添加请求到网络队列
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }
                // 如果缓存过期了,添加到网络请求对列
                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()) {
                    // 不需要刷新缓存,直接进行结果传递
                    mDelivery.postResponse(request, response);
                } else {
                    // 需要刷新的缓存,在把缓存结果传递时,同时应该进行缓存的刷新
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);
                    response.intermediate = true;
                    // 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) {
                                // 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;
            }
        }
    }

可以看出run()是一直在循环中的,并且阻塞获取Request,当获取到Request后分情况处理
对于NetWorkDIspatcher,主要是进行网络请求以及对请求结果的缓存,处理流程图如下所示


网络请求流程

run()的源代码如下

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

                // 此处进行网络请求,由mNetWork处理
                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");
                    continue;
                }

                // 对response解析,由Request解析
                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);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

可以看出请求是通过创建RequestQuesu传入的Network进行处理的, 然后对请求返回的结果,是通过我们的Request.parseNetworkResponse(NetworkResponse response)处理的,也就是说,如果是StringRequest,那么这个方法就是把请求结果转换为String,所以我们自定义Request的时候,需要实现这个方法。

总结

这篇文章就写到这里,虽然不一定会使用Volley来进行网络请求了(效率比较低),但是了解一下这个优秀的框架,个人觉得还是很有必要的。现在进行网络请求,推荐使用RxJava + Retrofit的方式,其中Retrofit的网络请求是通过okHttp实现的,在这里就不细说了。

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

推荐阅读更多精彩内容