Volley 深入解析(上)

Volley是Google为Android平台推出的轻量级网络库,支持多种数据类型的异步请求及缓存机制。本文详细介绍Volley的功能特性、使用方法及内部实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

在开发的时候多数情况下我们都会使用HTTP协议和服务器进行通信。Android SDK 提供了两种方式进行HTTP通信,即HttpClient和HttpURLConnection。不过直接使用HttpClient和HttpURLConnection进行通信需要自己处理通信的细节,包括数据缓存、超时处理等细节。于是一些Android网络通信框架就应运而生,而Volley就是在2013 Google I/O大会上发布的Android平台的网络通信库,能使网络通信更快、更精简、更健壮。Google推荐我们使用Volley库。所以有必要深入学习Volley库的内部实现。

2、介绍

2.1 Volley的功能
Volley提供了网络通讯功能,包括JSON、图片、String类型数据的请求和解析,并且Volley的可扩展性非常强,开发者可根据自己的项目需要进行自定义扩展(具体哪些地方可以扩展后面会做分析)。
简单来说,它提供了如下功能:
* JSON,图片等多种数据类型的异步请求
* 网络请求的排序
* 网络请求的优先级处理
* 数据的缓存

2.2 Volley的特点
优点

* 包体小  Volley库大小才120k
* 适合进行数据量不大,但请求频繁的应用场景
* 面向接口编程,容易扩展
* 缓存机制中数据只缓存到文件中,而没有在内存中,这样可节省运行内存
* 使用标准的HTTP缓存机制,保证缓存的数据是和服务器一致

缺点
* 使用的是HttpURLConnection和HttpClient,但Android6.0不支持HttpClient
* 对数据量大的文件下载,用户体验糟糕(后面会分析原因)
* 只磁盘缓存机制,而没有在内存缓存机制

2.3 Volley的使用
Volley的用法非常简单,只需要几行代码即可完成数据请求:

首先需要获取一个RequestQueue对象:

RequestQueue  mRequestQueue = Volley.newRequestQueue(Context);

接着创建相应的请求对象Request,并添加到RequestQueue队列即可。
下面看一下各种Request的使用
StringRequest
StringRequest是请求内容为字符串的请求对象,StingRequest的创建方式如下:

String url = "http:\\www.baidu.com";
StringRequest request = new StringRequest(Request.Method.GET, url,
      new Response.Listener<String>() {
        @Override
        public void onResponse(String s) {
          // 请求成功回调
        }
      },
      new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError volleyError) {
          // 请求失败回调
        }
      }
);

参数1:发起HTTP请求的方法
参数2:发起请求的服务器地址
参数3:请求成功回调接口
参数4:请求失败回调监听

JsonRequest
JsonRequest请求对象是用于请求Json格式的数据,请求成功后会返回一个JSONObject对象,JsonRequest创建方式和StringRequest类似

String url = "http:\\www.baidu.com";
JsonObjectRequest jsonRequest = new JsonObjectRequest(Request.Method.GET, url, null,
      new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject jsonObj) {

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

        }
      }
);

参数1:发起HTTP请求的方法
参数2:发起HTTP请求的服务器地址
参数3:如果参数1是一个POST方法,那么这个参数就是要提交的JSONObject对象,该参数可为null
参数4:请求成功回调接口
参数5:请求失败回调接口

ImageRequest
ImageRequest请求对象是用于请求Bitmap数据,请求成功后会返回一个Bitmap对象,ImageRequest创建方式和参数说明如下:

String url = "http:\\www.baidu.com";
ImageRequest imageRequest = new ImageRequest(url,
      new Response.Listener<Bitmap>() {
        @Override
        public void onResponse(Bitmap jsonObj) {

        }
      }, 0, 0, Bitmap.Config.ARGB_8888,
      new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError volleyError) {

        }
      }
);

参数1:发起HTTP请求的服务器地址
参数2:请求成功回调接口
参数3:请求的图片的最大长度,该参数是根据你要展示该图片的ImageView的最大长度来设定,可设置为0
参数4:请求的图片的最大高度,该参数是根据你要展示该图片的ImageView的最大高度来设定,可设置为0
参数5:请求的图片的每个像素点的需要占用的字节数,占用的字节越多,图片越清晰
参数6:请求失败回调接口
注意:合理的设置参数3、参数4、参数5可减小返回的Bitmap对象所占用的内存。

上面介绍Volley库内置 的三种请求类型StringRequest、JSONObjectRequest、ImageRequest的创建。前面说了,Volley库是一个面向接口编程的网络库,所以可很容易进行扩展
所以我们可自定义自己的请求对象,只需继承Request对象,并实现数据的解析方法。

自定义Request
这里自定义一个文件请求类型,用于从服务器下载一个文件数据。

public class FileRequest extends Request<File> {
    private final Response.Listener<File> mListener;

    public FileRequest(String url, String savePath, Response.Listener<File> listener, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        mListener = listener;
    }

    @Override
    public Priority getPriority() {
        return Priority.LOW;
    }

    @Override
    protected Response<File> parseNetworkResponse(NetworkResponse networkResponse) {
        // 对原始数据networkResponse.data进行处理
    }

    @Override
    protected void deliverResponse(File file) {
        this.mListener.onResponse(file);
    }
}

看一下请求对象都支持哪些操作:

* setShouldCache(boolean shouldCache)     设置该请求对象如果缓存中数据,是否使用缓存的数据,以及从服务器请求回来的最新数据是否需要缓存到本地
* cancel( )    取消该请求

3、源码解析

现在已知道Volley的基本用法,接下来我们从源码层面来解析Volley的内部实现机制。在使用Volley时需要先创建一个RequestQueue队列
那我们先从RequestQueue的创建开始。

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

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) {
    }

    // 这里需要注意,当参数stack为null时,如果系统版本为9及以上,创建HurlStack对象,9以下则创建HttpClientStack
    // 下面会解释HurlStack和HttpClientstack的作用
    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    // 使用上面的stack创建一个Network,后面会分析Network
    Network network = new BasicNetwork(stack);

    //创建一个请求队列,并启动。创建请求队列时需要传递两个对象:磁盘缓存对象和Network对象
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

    return queue;
}

先来介绍RequestQueue这个类
RequestQueue的创建

public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    // 存放网络分发线程的数组
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

参数1:磁盘缓存,用于缓存HTTP请求返回的数据
参数2:用于发起HTTP请求
参数3:HTTP请求线程数量,如果用户不设定该参数,默认开启4个线程,进行HTTP请求
参数4:因为HTTP请求是在子线程中操作的,得到的结果也是在子线中,我们需要通过ResponseDelivery把结果从子线程传递到主线程。

RequestQueue内部重要的数据集合:

/**
*
* 如果由多个Request请求同一张相同参数的图片,那么Volley只处理一个请求,其它的请求存储在这个集合中等待处理结果。
*/
private final Map<String, Queue<Request<?>>> mWaitingRequests =
        new HashMap<String, Queue<Request<?>>>();

/**
* 存储全部的请求
*/
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

/** 缓存队列,缓存线程就是从这个队列中取数据 */
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
    new PriorityBlockingQueue<Request<?>>();

/** 网络请求队列,网络分发线程从这个队列中取数据 */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
    new PriorityBlockingQueue<Request<?>>();

RequestQueue提供两个比较重要的接口RequestQueue.start()和RequestQueue.add(request)
RequestQueue.start()启动网络分发线程NetworkDispatcher和缓存线程CacheDispatcher,这两种类型的线程从RequestQueue不断的取出请求对象进行处理 。

public void start() {
    // 首先停掉全部线程
    stop();  

    // 接着创建一个缓存分发线程,并启动线程,该线程用于处理缓存操作
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 创建网络分发线程用于HTTP请求,注意这里把请求队列传递到线程。
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

注意:上面启动一个缓存线程、网络请求线程的数量默认是4个。

RequestQueue.add(request)接口用于向RequestQueue添加请求

public <T> Request<T> add(Request<T> request) {
    request.setRequestQueue(this);

    // 1:每个请求都添加到这个集合中
    synchronized (mCurrentRequests) {
        mCurrentRequests.add(request);
    }

    ...

    //2:如果对于这个请求,不允许从缓存中拿数据,则把该请求添加到网络请求队列中,并返回
    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();
        // 3:判断是否有相同的请求
        if (mWaitingRequests.containsKey(cacheKey)) {
            // 4:有,则添加到等待集合
            Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            stagedRequests.add(request);
            mWaitingRequests.put(cacheKey, stagedRequests);

        } else {
            // 5:没有,则添加到缓存处理队列,并在等待集合中记录
            mWaitingRequests.put(cacheKey, null);
            mCacheQueue.add(request);
        }
        return request;
    }
}

下面流程图是RequestQueue.add(request)的执行流程。
这里写图片描述

下面分析NetworkDispatcher这个类,这个类的实现很简单。
NetworkDispatcher继承Thread实现一个子线程

public class NetworkDispatcher extends Thread {
    /** 该集合就是网络请求队列 */
    private final BlockingQueue<Request<?>> mQueue;
    ...
}

重点看一下NetworkDispatcher.run()

public class NetworkDispatcher extends Thread {
    ...
    @Override
    public void run() {
        // 注意这里是死循环
        while (true) {
            Request<?> request;
            try {
                // 1:从网络请求队列中取出请求信息.
                request = mQueue.take();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                //2:判断请求是否被取消
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                // 3:通过Network对象去执行一个请求,并得到请求结果(Network后面再分析).
                NetworkResponse networkResponse = mNetwork.performRequest(request);  

                // 4:对请求到的原始数据解析为我们想要的数据
                Response<?> response = request.parseNetworkResponse(networkResponse);

                //5:如果允许,则把原始数据保存到缓存
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                }

                // 6:最后把请求结果返回到主线程
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {           
                mDelivery.postError(request, volleyError);
            }
        }
    }
    ...
}

下面分析CacheDispatcher这个类
该类也其实和NetworkDispatcher都是实现数据的请求,区别是NetworkDispatcher是请求网络数据,而CacheDispatcher是请求缓存数据
我们直接来看CacheDispatcher的实现,代码中都有注释

public class CacheDispatcher extends Thread {
    ...
    @Override
    public void run() {
        // 注意这里是个死循环
        while (true) {
            try {
                // 从缓存队列中取出缓存请求
                final Request<?> request = mCacheQueue.take();

                // 判断请求是否取消
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 取出缓存,如果不存在缓存,那么就添加到网络请求队列中
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    mNetworkQueue.put(request);
                    continue;
                }

                // 如果有缓存,但缓存已过期,则添加到网络请求队列中
                if (entry.isExpired()) {
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // 把原始数据解析为先要的数据类型
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));

                // 判断数据是否需要更新
                if (!entry.refreshNeeded()) {
                    // 若不需要,直接把数据返回给用户
                    mDelivery.postResponse(request, response);
                } else {

                    // 若需要,则把数据返回给用户后,把发送一个网络请求更新数据
                    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) {
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
}

上面分析的NetworkDispatcher内部是使用Network.performRequest( )进行网络请求,接下来我们介绍Network
Network是一个接口,提供了一个请求数据的接口。唯一的实现类是BasicStack,类的关系图如下:
这里写图片描述

BasicNetwork实现了performRequest( ),BasicNetwork有一个HttpStack的引用,这个引用可指向HttpClientStack,也可指向HurlStack,这两个类后面再分析。我们先来看一下BasicNetwork.performRequest( )的实现。

@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
    long requestStart = SystemClock.elapsedRealtime();
    while (true) {
        HttpResponse httpResponse = null;
        byte[] responseContents = null;
        Map<String, String> responseHeaders = Collections.emptyMap();
        try {
            // 1:设置请求头
            Map<String, String> headers = new HashMap<String, String>();
            addCacheHeaders(headers, request.getCacheEntry());

            // 2:通过mHttpStack请求数据
            httpResponse = mHttpStack.performRequest(request, headers);
            StatusLine statusLine = httpResponse.getStatusLine();

            // 3:根据请求结果状态码进行后续处理,如:请求超时处理、 重定向处理、
            int statusCode = statusLine.getStatusCode();
            ...
            return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                    SystemClock.elapsedRealtime() - requestStart);
        } catch (Exception e) {
           // 错误处理
           ...
        }
    }
}

注意第二步会通过mHttpStack去进行一个请求,HttpStack有两个实现类,这两个实现类分别使用了不同的网络请求方式。
HttpClientStack使用Apache提供的HttpClient实现网络请求
HurlStack使用Java提供的HttpURLConnection实现网络请求
注意:移除了对支持Apache HTTP的支持,可改用 HttpURLConnection 类,该类效率更高。 要继续使用 Apache HTTP API,必须自行添加依赖

如果没指定Http的请求方式,那Volley内部会根据系统的版本号创建HttpStack。
这里就不分析HttpStack的两个实现了网络请求细节。

返回到NetworkDispatcher.run()的第四步,这一步是调用Request.parseNetworkResponse()对请求到的二进制数据进行解析,上面已经讲解Request的几个子类,每个子类对parseNetworkResponse( )的实现都是不一样的,即对二进制的解析方式是不一样的。

NetworkDispatcher.run()的第6步把解析的结果返回到主线程中。

4、总结

下面给出一个Volley框架的类图
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值