美文网首页
Android网络编程(七)Volley源码解析

Android网络编程(七)Volley源码解析

作者: zskingking | 来源:发表于2019-02-19 17:33 被阅读143次

前言

Volley是Google在2013I/O大会上发布的一款轻量级Android端网络请求框架,它的特点:适合数据量小,通讯频繁的网络请求操作。在前两篇文章我们分析了OKHttp的源码,通过源码的分析能让我们对HTTP有更加深入的了解,并且每一个优秀的框架内部都涵盖了很多设计模式,这些都是非常值得我们去学习的,所以今天我们继续分析另一款优秀的HTTP网络请求框架Volley。

注:本文只分析关于Volley运行流程方面源码,对HTTP细节、缓存、四大Request源码暂不做分析

1. 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) {
                        Log.i("volley","success:"+response);
                    }
                },
          new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.i("volley","error:"+error.toString());

                    }
          });
 queue.add(request);

打印结果:

 <html><!--STATUS OK--><head><meta charset="utf-8"><title>百度一下,你就知道......

StringRequest中四个参数分别是:请求方法、url、成功回调、失败回调。请求体定义完毕加入queue中即可进行请求。StringRequest只能接收字符串结果,在Volley中为我们提供了四种Request分别如下:

  • StringRequest 响应的主体为字符串
  • JsonArrayRequest 发送和接收JSON数组
  • JsonObjectRequest 发送和接收JSON对象
  • ImageRequest 发送和接收Image

使用方式基本一致,所以就不一一叙述。另外一个Activity创建一个RequestQueue即可,至于为什么分析完源码你就知道了。Volley的使用相比于OKHttp还是简单不少的,下面我们进入源码分析部分。

2. 源码分析

首先从入口Volley开始分析

2.1 Volley

Volley中代码非常简单,里面就做了一件事情:构造RequestQueue,内部只存在四个方法分别是newRequestQueue()的四种重载形式,不过最终调用的是拥有三个参数的newRequestQueue(),源码如下:

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();//SDK大于9使用HttpUrlConnection
            } else {
                //SDK<9使用HttpClient
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //网络请求类
        Network network = new BasicNetwork(stack);
        RequestQueue queue;
        if (maxDiskCacheBytes <= -1) {
            //未指定缓存大小
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        } else {
            // 指定了磁盘缓存大小
        queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }
        //开启
        queue.start();
        return queue;
    }

构造RequestQueue大概分为4步分别如下:

  • 创建磁盘缓存文件
  • 判断当前版本,如果>=9使用HttpUrlConnection否则使用HttpClient
  • 构造网络请求类Network
  • 构造RequestQueue

RequestQueue构造完毕后调用start()方法开启,此时很多同学会有疑问,RequestQueue是什么?调用start()开启的又是什么,别急我们马上分析。

2.2 RequestQueue

RequestQueue可直译为请求队列,就是用来存放请求的队列,下面我们来分析一下它的核心源码:
入队方法add():

 public <T> Request<T> add(Request<T> request) {
        //标记request所属的队列
        request.setRequestQueue(this);
        //添加到正在被执行的集合
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }
        //为request设置序号
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");
        // 如果该请求不能缓存,直接加入到网络请求队列并return
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            //等价于url相同
            if (mWaitingRequests.containsKey(cacheKey)) {
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                //后续相同url的请求都放到该链表中
                stagedRequests.add(request);
                //进入此等待队列
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                     VolleyLog.v("...", cacheKey);
                }
            } else {
                mWaitingRequests.put(cacheKey, null);
                //加入缓存队列
                mCacheQueue.add(request);
            }
            return request;
        }
    }

注释写的很清楚,就不再重复解释,这里着重说一下用到的四个集合:

  • mCurrentRequests:是一个Map集合,用来存储当前正在执行的Request
  • mNetworkQueue:网络请求优先级队列,通过网络获取数据
  • mWaitingRequests:是一个Map集合,如果请求可以被缓存并且挣扎被处理,后续有相同url的请求就会进入此等待集合
  • mCacheQueue:缓存优先级队列,通过缓存获取数据

开启方法start():

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

RequestQueue内部维护了一个默认长度为4的NetworkDispatcher 数组和一个CacheDispatcher对象,这两个类都直接继承自Thread,分别用来从网络和缓存中获取数据。调用start()后者4个网络请求线程和一个缓存线程都会被开启。

结束方法finish():
当一个请求结束后需要将Request从mCurrentRequests、mWaitingRequests移除,如果允许缓存就再加入到缓存,代码中内容很容易理解所以就不贴出来了。

add()、start()、finish()是RequestQueue的三个核心方法,此外它还提供了取消请求、停止线程等方法,都比较简单,感兴趣的同学可自行了解。接下来我们来分析两个线程的源码

2.3 CacheDispatcher

CacheDispatcher是一个用来处理缓存的线程,缓存策略遵循HTTP协议,我们来看其run()方法源码:

 public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //初始化缓存
        mCache.initialize();
        Request<?> request;
        while (true) {
            //释放上一个request对象
            request = null;
            try {
                //从缓存队列获取请求
                request = mCacheQueue.take();
            } catch (InterruptedException e) {
                // 线程被停止直接return
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
                request.addMarker("cache-queue-take");
                // 该请求已经被取消
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                //根据cacheKey从缓存中得到对应的记录
                Cache.Entry entry = mCache.get(request.getCacheKey());
                //缓存中没有cacheKey对应的记录,
                if (entry == null) {
                    request.addMarker("cache-miss");
                    //将请求加入到网络请求队列
                    mNetworkQueue.put(request);
                    continue;
                }
                //如果缓存中有记录,但是已经过期,则加入到网络中进行获取
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }
                //能执行到这说明命中了一个有效缓存
                request.addMarker("cache-hit");
                //将响应体进行格式转换
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");
                //缓存记录不需要更新,
                if (!entry.refreshNeeded()) {
                    //直接在UI线程中处理响应结果
                    mDelivery.postResponse(request, response);
                } else {//通过网络验证,缓存为软过期,即虽然过期但是可用
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);
                    response.intermediate = true;
                    final Request<?> finalRequest = request;
                    //将response响应给UI线程并更新网络数据
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                //从网络中更新数据
                                mNetworkQueue.put(finalRequest);
                            } catch (InterruptedException e) {}
                        }
                    });
                }
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
            }
        }
    }

整个流程完全遵循HTTP缓存策略,代码较多但核心代码就那么几句,下面我简要描述一下从缓存获取具体流程:

  • 拿着request试图从缓存中火树response
  • 如果缓存不存在对应数据直接将request加入到网络队列,从网络获取数据
  • 如果缓存存在但已经过去也将request加入到网络队列
  • 如果缓存新鲜则直接将response响应给UI线程
  • 如果缓存不新鲜将response响应给UI线程后再去网络做新鲜度验证
2.4 NetworkDispatcher

NetworkDispatcher 是一个进行网络请求的线程,run()方法源码如下:

public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            request = null;
            try {
                //从优先级队列中取请求体,take()方法为阻塞式方法
                request = mQueue.take();
            } catch (InterruptedException e) {
                if (mQuit) {//线程已经停止直接return
                    return;
                }
                continue;
            }
            try {
                request.addMarker("network-queue-take");
                if (request.isCanceled()) {//判断请求是否被取消
                    request.finish("network-discard-cancelled");
                    continue;
                }
                addTrafficStatsTag(request);
                //进行网络请求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
                //服务器返回304,不返回响应body
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }
                //将响应体进行转换String、JSON等等
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");
                // 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();
                //在UI线程中回调
                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);
            }
        }
    }

很多代码跟CacheDispatcher类似,注释写的很清楚,下面我简要描述一下流程:

  • 从网络队列中取出一个请求体,判断请求是否取消,如果是直接continue
  • 通过Network进行网络请求
  • 服务器返回304代表更新缓存,所以不存在body直接结束即可
  • 将response转换为响应的格式
  • 判断是否需要加入缓存
  • 响应给UI线程

代码虽然看去来眼花缭乱但逻辑也很简单。两个线程拿到数据后都是通过ResponseDelivery类型的mDelivery响应给UI线程的,下面我们来分析ResponseDelivery的实现类ExecutorDelivery的源码

2.5 ExecutorDelivery

首先来看ExecutorDelivery的一个内部类ResponseDeliveryRunnable:

    public void run() {
            // 如果请求已经取消直接return
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }
            // 请求成功回调成功方法
            if (mResponse.isSuccess()) {
                //响应请求的回调接口,对应我们请求时写的成功回调方法
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }
            ...
            ...
    }

ResponseDeliveryRunnable实现Runnable接口,run()方法中将Response响应至UI线程。
ExecutorDelivery中也维护了一个Executor,主要是用于跟UI线程通讯,来看Executor实现源码:

       mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                //将任务切换到UI线程
                handler.post(command);
            }
        };

通过Handler实现与UI线程的通讯,command即ResponseDeliveryRunnable类型对象。
NetworkDispatcher和CacheDispatcher获取到response时都会调用ExecutorDelivery的postResponse(request, response)方法,来看一下这个方法的源码:

  @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

很简单,只是将ResponseDeliveryRunnable任务传递给了Executor,最后会将response响应给UI线程。代码没啥难度,就不再进行赘述。

Volley逻辑上的代码大概就是这些。

总结

Volley的使用非常简单,如果碰到频繁做数据量小,通讯频繁的网络请求我还是推荐使用Volley。内部源码也比较简单,主要逻辑都封装在了消息队列和两个线程中。由于Volley的定位是一款轻量级框架所以复杂程度远远比不上OKHttp,但里面的一些设计思想是非常值得我们去借鉴的。本篇文章差不多就这些内容了,今天是元宵节,祝大家节日快乐。

相关文章

  • 从源码解析Volley(一)

    转载刘望舒的博客Android网络编程(四)从源码解析Volley 1.Volley结构图 从上图可以看到Voll...

  • Android网络编程(七)Volley源码解析

    前言 Volley是Google在2013I/O大会上发布的一款轻量级Android端网络请求框架,它的特点:适合...

  • 笔记 android网络框架源码解析及对比(待续)

    自己备忘,随便写 android网络框架源码解析及对比 android常用网络框架对比 Volley: 特点 基于...

  • Volley

    Android Volley完全解析(四),带你从源码的角度理解Volley

  • 网络请求相关

    1,Android网络请求心路历程,总结的非常好 Android网络请求心路历程 2,Volley的源码解析和详细...

  • Volley用法全解析

    转载刘望舒的博客Android网络编程(三)Volley用法全解析 前言 Volley想必很多人都用过,为了建立网...

  • 【源码解析】 Volly

    【源码解析】 Volley的用法及源码解析 前言 Volley是谷歌在2013年I/O大会上推出的新的网络请求框架...

  • Android网络编程(四)从源码解析Volley

    1.Volley结构图 从上图可以看到Volley分为三个线程,分别是主线程、缓存调度线程、和网络调度线程,首先请...

  • Volley源码解析

    原博客地址参考资料:Android Volley完全解析(四),带你从源码的角度理解VolleyVolley 源码...

  • Android 网络框架 Volley 源码解析

    Volley 是 Google 官方推出的一套 Android 网络请求库,特别适用于通信频繁、数据量较小的网络请...

网友评论

      本文标题:Android网络编程(七)Volley源码解析

      本文链接:https://www.haomeiwen.com/subject/dlztyqtx.html