Volley源码解析 --- Volley组成(1)

简介

volley框架是Google公司开发的一套网络访问开源框架,具有体积小、效率高和适合频繁的网络请求等特点;虽然现在的网络主流都推荐用Retrofit+Okhttp,但是lz认为用什么样的网络框架还是得根据什么样的项目,不是一种功夫就可以打遍天下无敌手的;所有这里分析Volley的目的不一定是去用,而是学习分析Volley的代码设计、网络请求、缓存设计等知识,补充我们的知识,更好的帮助我们开发今后的工作。

知识点简介

本文将以以下几个方面逐点分析

  • Volley由哪些重要的成员组成 ?
  • Volley执行的流程如何以及它的成员运行流程如何 ?
  • Volley一些特性点怎么实现,缓存设计、HTTPS等

Volley重要成员

NetWork.java
网络控制中心,执行真正的网络请求部分,所有的请求最终执行网络访问就是它来完成的

Request.java
Volley提供的网络请求类,封装网络请求参数

RequestQueue.java
这个类很重要,网络请求队列类,所有的Request都是要add到这个类中,而网络线程和缓存线程都是从这个队列里面去取

CacheDispatcher.java
缓存线程,默认只有一个,这个线程用于处理一些具有缓存特性的请求任务,如果有缓存就去缓存中取,没有或者该任务是实时性的,就需要重新把该任务添加到网络线程中去执行

NetworkDispatcher.java
网络线程,所有的网络请求都是由这类型网络线程发出执行的,Volley默认该线程由4个,协同处理所有的请求Request任务

数据结构:

Set<Request<?>> mCurrentRequests: 存储所有的请求Request

PriorityBlockingQueue<Request<?>> mCacheQueue: 缓存队列,存储所有有缓存特性的请求

PriorityBlockingQueue<Request<?>> mNetworkQueue:网络队列,存储需要进行网络访问的请求

Map<String, Queue<Request<?>>> mWaitingRequests:待会再解释,不清楚

Volley源码分享 – 从最简单的使用方法入手

RequestQueue queue = Volley.newRequestQueue(this);
StringRequest request = new StringRequest(Request.Method.GET, "https://www.baidu.com/", new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                tv.setText(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                tv.setText("error"+error.getMessage());
            }
        });
        queue.add(request);

Volley.newRequestQueue(this)

追踪进去发现它调用了另一个方法newRequestQueue(Context context, HttpStack stack)

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        ......
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack(); //内部使用HttpURLConnection
            } else {
              //内部使用HttpClient,但是现在已经无法使用了
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        Network network = new BasicNetwork(stack);

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

        return queue;
    }

以上任务有三:

  1. 创建缓存目录;
  2. 实例化网络控制核心NetWork,api>9使用HurlStack,<9的使用HttpClient,追踪AndroidHttpClient.newInstance,发现它直接抛了一个异常,说明现在<9的是无法使用Volley
  3. 实例化RequestQueue,并传递了缓存类和网络控制类,RequestQueue很重要,继续分析:

RequestQueue构造方法

public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

上面完成了RequestQueue的成员赋值,实例化了DEFAULT_NETWORK_THREAD_POOL_SIZE(4)个网络线程,还加上一个回调到主线程的工具类ExecutorDelivery;ExecutorDelivery就是内部封装了handler.postRunnable方法,具体可以点击查看源码

回到newRequestQueue执行queue.start()

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();
        }
    }

默认情况下,是创建一个CacheDispatcher缓存线程和4个NetworkDispatcher网络线程并运行,它们两个的大致工作就是轮询去mCacheQueue或者mNetworkQueue队列取出Request请求,并执行;
上面代码还有一个很重要的知识点,当前线程如何停止其他线程?解决办法就是该方法的第一条stop()方法,Java默认是无法直接停止其他线程的,只有利用interrupt产生中断异常,由其他线程设置标志位自行完结自己

回到Volley最简单的用法 — RequestQueue.add(Request)

上面示例的StringRequest不是设计主要的就暂不分析了,如有兴趣的可点击这里

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);  //所有的Request都要加入mCurrentRequests队列中去,方便管理
        }

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

以上完成任务:

  1. 将所有Request都加入mCurrentRequests这个Set集合里面来,方便管理;比如有时想取消某个Request就会从set.remove(Request)
  2. 如果Request任务不是一个缓存特性的请求,就直接加入到mNetworkQueue队列去,方法结束
  3. Request是一个缓存特性,就要根据一定的逻辑加入到mCacheQueue队列里面去;分析这个逻辑:
    Map<String, Queue<Request<?>>> mWaitingRequests;它是一个Map集合key为String存储访问的url,value为一个Queue队列存储Request;按照上面的代码实现可以得出,其目的就是防止相同的网络访问url重复访问,先判断Map里面有相同的url的key没?如果有,就把request加到value中去;没有就把value设置成null,为什么没有就设置为null,因为为null的时候这个Request是第一个用这个url访问的,把它加入到缓存队列即可,后续的就不需要添加了;

__ mWaitingRequests除了上面的过滤相同请求功能之后,怎么在网络请求完成后回调的?__
这里大致阐释流程,后续会详细讲解:

  1. mCacheQueue里面的Request请求完成并且回调后,会调用自身的finish方法
  2. finish再次回调用RequestQueue的finish方法,在此方法中
if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> 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. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }

它会将相同的key的value,从mWaitingRequests的Map集合中取出来,在从新假如到mCacheQueue中去,在CacheDispatcher线程中在轮训mCacheQueue队列把上次网络访问的数据赋值给新的请求,就不必再从新进行一次网络请求

总结

一张流程图

Volley.png

至此,RequestQueue重要方法已经分析差不多了;以上是lz一点见解,如有不足还请指教;明天再继续分析CacheDispatcher和NetworkDispatcher线程执行流程

HttpClient浅析

阻塞式请求访问,底层设置有最大连接数和单个路由的最大连接数,来一次请求,就会重连接池里面取出一个connection,在此基础上进行网络访问

OkHttpClient

在此基础上多了一个Dispatcher调度,一个同步双端队列和两个异步双端队列,两个异步队列分别存储正在执行网络请求和准备执行的网络请求,默认最大连接数64个,单个路由最大5个,当来了一个请求判断是同步还是异步请求,同步就加入同步队列,异步就判断连接总数和单个路由有没有超,没超就从连接池取出一个连接,在把请求加入正在执行的队列,拦截器处理,完成请求并移除执行队列;后面来的请求再次走这个过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅气好男人_Jack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值