深入解析开源项目之Volley框架

珍惜作者劳动成果,如需转载,请注明出处。
http://blog.csdn.net/zhengzechuan91/article/details/50433704

Volley是google在2013年io大会上推荐的网络通信框架,它封装了android中的网络请求模块,使开发者处理网络请求更加简单,适合数据量不大但是通信频繁的场景。

前面我们对Retrofit网络通信框架做了详细的介绍,这篇文章我们就来看看Volley框架是怎么通信的?

初始化RequestQueue

首先我们先来看看初始化

/* Volley.java */

    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        //默认缓存在cache目录下,文件名为volley
        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);
            // volley/0/packageName/versionCode
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        //这里处理跟Retrofit一样
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        //request的执行者
        Network network = new BasicNetwork(stack); 

        RequestQueue queue;
        if (maxDiskCacheBytes <= -1)
            //不设定缓存大小,最大网络线程并发数为4
            //并创建ExecutorDelivery将response传递到UI
            //线程
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }
        else
        {
            //设定缓存大小,最大网络线程并发数为4
            //并创建ExecutorDelivery将response传递到UI
            //线程
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }

        //初始化并开启任务队列
        queue.start();

        return queue;
    }

开启RequestQueue任务

然后我们看看RequestQueue#start()中做了什么,这里有点需要声明下,CacheDispatcher和NetworkDispatcher实际上都是线程,命名为Task会更好些:

/* RequestQueue.java */

    public void start() {
        //发送中断请求中断所有任务
        stop();
        //创建缓存任务
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        //开启缓存线程
        mCacheDispatcher.start();

        //创建4个网络任务并开启
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            //数组是在初始化的时候创建的
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

这个方法很简单,就是停掉之前的所有任务,然后开启新的所有任务。

缓存线程

到了这个时候线程都启动了,我们肯定要去看看run()方法中干了什么,先来看缓存的线程:

/* CacheDispatcher.java */

    @Override
    public void run() {
         //线程优先级为后台级别
         Process.setThreadPriority(
         Process.THREAD_PRIORITY_BACKGROUND);

        //对Volley#newRequestQueue()时产生的
        //DiskBasedCache实例初始化,实际上就是扫描根目录,
        //将所有的CacheHeader装入到内存(mEntries)中
        mCache.initialize();

        Request<?> request;
        while (true) {
            //释放前面的request对象,避免mQueue用完后内存
            //泄露

            request = null;
            try {
                //从阻塞队列中取request
                request = mCacheQueue.take();
            } catch (InterruptedException e) {
                //用户发送Thread#interrupt()中断请求退出
                //线程
                if (mQuit) {
                    return;
                }

                //不是用户主动退出继续任务
                continue;
            }
            try {
                // 如果调用Request#cancel()取消任务
                if (request.isCanceled()) {

                    //这时候会将request从当前加载的
                    //request列表中移除,如果添加了
                    //finish后通知的listener,通知
                    //listener此request完成,如果
                    //未完成的request需要记下来,则将等待
                    //中的request队列mWaitingRequests
                    //移动到mCacheQueue中 
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 通过key获取磁盘上保存的实体
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    //缓存中找不到,直接将request添加到
                    //网络队列中
                    mNetworkQueue.put(request);
                    continue;
                }

                // 缓存过期,HttpHeaderParser#
                //parseCacheHeaders()这个方法里定义了
                //ttl,默认为head头的(Expires-Date) 
                if (entry.isExpired()) {
                    //关联request和磁盘上过期的实体
                    request.setCacheEntry(entry);
                    //添加到网络队列中
                    mNetworkQueue.put(request);
                    continue;
                }

                //已经命中了磁盘上的缓存,将磁盘上的内容解析
                //为Repsonse
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));

                //默认soft ttl与ttl一样
                if (!entry.refreshNeeded()) {
                    //磁盘上缓存不需要刷新,直接将
                    //response传递给UI线程
                    mDelivery.postResponse(request, response);
                } else {
                    //关联request和磁盘上的实体
                    request.setCacheEntry(entry);

                    //标注数据会刷新
                    response.intermediate = true;

                    //先返回磁盘的缓存,再去网络加载
                    final Request<?> finalRequest = request;
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(finalRequest);
                            } catch (InterruptedException e) {
                            }
                        }
                    });
                }
            } catch (Exception e) {
        }
    }

让我们总结下缓存任务中做了哪些事情:
1. 如果使用Thread#interrupt()中断任务,则直接退出;
2. 如果Request#cancel()取消掉任务,则会从当前执行的列表和等待的队列中移除掉;
3. 如果缓存中找不到或已过期,直接去网络任务中执行;
4. 如果磁盘上的缓存需要更新,则先加载磁盘上的缓存返回给UI线程,在去网络任务中执行;
5. 如果磁盘上存在并且没有过期,则直接加载磁盘上的缓存,并返回给UI线程。

网络线程

看完了缓存任务的逻辑,我们再来看看网络任务的逻辑:

/* NetworkDispatcher.java */

    @Override
    public void run() {
        //后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            long startTimeMs = 
                SystemClock.elapsedRealtime();
            //释放前面的request对象,避免mQueue用完后内存
            //泄露
            request = null;
            try {
                //从网络队列中去request
                request = mQueue.take();
            } catch (InterruptedException e) {
                // 用户中断,直接退出
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                // 任务被取消,从当前列表和等待队列中移除
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                 //4.0以上在线程中标识数据传输
                addTrafficStatsTag(request);

                // 执行请求任务
                NetworkResponse networkResponse = mNetwork.performRequest(request);

                //将流数据转换为Response
                Response<?> response = request.parseNetworkResponse(networkResponse);

                // 写入磁盘缓存
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                }

                //将request返回UI线程
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                //将错误结果返回UI线程
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                //将错误结果返回UI线程
                mDelivery.postError(request, volleyError);
            }
        }
    }

我们再来总结下网络任务中的逻辑:
1. 如果使用Thread#interrupt()中断任务,则直接退出;
2. 如果Request#cancel()取消掉任务,则会从当前执行的列表和等待的队列中移除掉;
3. 执行网络请求,将NetworkResponse中的流数据包装成Response返回;
4. 写入磁盘缓存;
5. 将返回结果通过ResponseDelivery返回到UI线程。

添加任务

接下来我们看看任务被添加的代码:

/* RequestQueue.java */

    public <T> Request<T> add(Request<T> request) {
        //关联request和requestQueue
        request.setRequestQueue(this);
        //添加到当前请求列表中
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        //对request编号
        request.setSequence(getSequenceNumber());

        //如果不需要缓存,直接添加到网络队列
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            // 如果等待队列中已经有此request的key,说明之
            //前已经添加过
            if (mWaitingRequests.containsKey(cacheKey)) {

                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                //这次才会添加到等待队列中
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);

            } else {
                //第一次无需将request添加到等待队列中
                mWaitingRequests.put(cacheKey, null);
                //第一次直接添加到缓存队列中
                mCacheQueue.add(request);
            }
            return request;
        }
    }

从上面我们看到,RequestQueue中共有四个地方来保存request,mWaitingRequests保存了缓存时每个key对应的request队列,mCurrentRequests保存了当前正在执行的request集合,mCacheQueue是带有优先级的缓存队列,mNetworkQueue是带有优先级的网络队列。

如果不用缓存,那么直接添加到网络队列中就直接返回了。开启的4个执行网络任务的线程都是从mNetworkQueue中取request的。

看完上面的代码,我们一定会有一个疑惑,有任务我们直接添加到缓存队列和网络队列中就行了,为什么还要有当前执行的集合和等待队列呢?

首先我们先看当前执行的request集合mCurrentRequests是做什么用的,mCurrentRequests是在每次add()的时候不管是缓存request还是网络request都会添加进去的,而在finish()时移除的,所以这个集合中保存的是当前正在执行的request,而当我们根据tag使用cancelAll()方法移除request的时候,实际上移除的就是这个集合中正在执行的request,而ExecutorDelivery在返回UI线程时也会先判断这个request是否cancel了。

等待队列mWaitingRequests跟mCurrentRequests的功能相似,不过只有在执行缓存request才会用到。由于缓存任务只有一个线程,同一个request我们可能会多次添加到缓存任务中,如果直接添加到mCacheQueue中,势必会影响我们的效率,这时候我们可以先将其缓存在mWaitingRequests中,当这个request执行完finish()时在将mWaitingRequests中的request队列再全部放到mCacheQueue中执行。

自定义Request

分析完了框架运行的流程,我们看看自定义Request:

系统已经帮我们实现了几个常用的Request,例如StringRequest、JsonRequest等,但是我们可能需要根据自己的需求去自定义自己的Request,Request是一个抽象类,要自定义Request必须实现抽象方法parseNetworkResponse()和deliverResponse()两个方法,前者将NetworkResponse解析为Response,而后者则是将泛型T返回到UI线程。

除了上面提到的,Request里我们还会添加Header,如果是POST/PUT还要添加Body,那这个框架又是怎么实现的呢?

先来看看默认实现:

/* Request.java */

    //默认header为空
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

    //默认POST/PUT请求参数为空,如不需要修改contentType则覆写此方法
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }

    //默认POST/PUT请求的body为encode getParams()中的参数,如需修改
    //contentType则覆写此方法
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

一般情况下我们会覆写getHeaders()来向header中添加参数,而POST/PUT方法覆写getParams()来向body中写入参数。

网络加载

上面设置的这些header和body会在构建http连接实例时用到,让我们来看看这部分的逻辑。

/* HttpStack.java */

public interface HttpStack {
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
        throws IOException, AuthFailureError;

}

封装http连接的接口是HttpStack,返回的Response为HttpResponse,实现类HurlStack封装的是HttpURLConnection的逻辑,将返回值包装成了HttpResponse,而HttpClientStack封装的是HttpClient的逻辑,直接返回的就是HttpResponse。

/* Network.java */

public interface Network {
    public NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

Network则是Volley框架请求的接口,实现类BasicNetwork里也只是对HttpStack的包装而已,而且将HttpStack返回的HttpResponse包装成了NetworkResponse。

Volley框架执行流程

好了,平常用到的就这么多东西,我们来总结下Volley这个框架:
1. 使用Volley#newRequestQueue()初始化RequestQueue并启动RequestQueue;
2. 开启1个缓存线程CacheDispatcher和4个网络线程CacheDispatcher;
3. 缓存线程从缓存的阻塞队列中取request,根据key从磁盘上读取缓存,读取不到再去添加到网络的阻塞队列;
4. 网络线程从网络的阻塞队列中取request,使用Network发起http网络请求,返回的结果会封装在NetworkResponse中;
5. 根据我们重写的Request的parseNetworkResponse()方法将
NetworkResponse解析为Response;
6. 成功返回时会通过ResponseDelivery#postResponse()使用Handler通知到UI线程,实际上就是在UI线程执行ResponseDeliveryRunnable,在我们重写的Request#deliverResponse()方法中通知我们实例化Request时传入的Listener#onResponse()回掉;
7. 失败时会通过ResponseDelivery#postError()使用Handler通知到UI线程,实际上就是在UI线程执行ResponseDeliveryRunnable,通过Request#deliverError()方法将VolleyError通过我们实例化Request时传入的Response.ErrorListener#onErrorResponse()回调。

你可能感兴趣的:(github,android,Google,Volley,网络通信框架)