Volley网络框架完全解析(使用篇)

时间:2021-05-14 10:47:44

在Android中,网络请求无非就这两种:HttpURLConnection和HttpClient( Apache),我们在使用时一般都会对它们进行一系列的封装,但是这过程不免有些繁琐,所以,Google官方也考虑到了这点,在2013年Google I/O大会上就推出了一个新的网络请求框架——Volley,它将各种网络请求都简单化,并且把AsyncHttpClient和Universal-Image-Loader两大框架的优点集一身,Volley用在数据量不大的网络请求操作时它的性能表现的非常出色,但是Volley如果在进行数据量大的网络操作时(下载文件等),那么Volley将表现的比较糟糕。

关于Volley的架构可以看看这个文章Volley架构

Volley有这么几大功能:

1、普通数据、JSON、图片的异步加载

2、网络请求优先级处理

3、自带硬盘缓存(普通数据、图片、JSON),另外我们在加载图片时候通过ImageLoader还可加入LruCache

4、取消请求

5、与Activity生命周期联动(Activity退出时同时取消所有的请求)

可见,Volley框架是非常强大的,下面我就一一介绍怎么使用Volley框架。

Volley框架的原理:它内部是通过一个请求队列(RequestQueue)来维护所有请求,我们新创建一个请求(request)后通过RequestQueue.add()方法将请求添加置请求队列中,然后调用RequestQueue.start()方法执行请求队列中的方法

Volley中包含这么几种类型的请求:

  1. StringRequest - 返回字符串数据
  2. JsonObjectRequest - 返回JSONArray数据
  3. JsonArrayRequest - 返回JSONObject数据
  4. ImageRequest - 返回Bitmap类型数据

当然使用前我们必须导入Volley.jar包(可以去网上下载),或者通过git下载

git clone https://android.googlesource.com/platform/frameworks/volley

这里给出我上传的jar包下载地址:Volley.jar

创建RequestQueue请求队列

RequestQueue是通过Volley的静态方法newRequestQueue来创建的:

RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());

一般我们会继承自Application在自定义的MyApplication中创建一个全局的请求队列,用来维护app中的网络请求。

StringRequest

这里主要讲最常用的GET和POST请求方式:

这里我用聚合网上查询手机号码归属地的数据为例子,我们创建一个StringRequest请求,然后给该请求设置一个Tag,用来标记这个请求,取消请求时候我们可以通过这个Tag来取消某个或者所有请求,再把该请求加入请求队列,最后执行请求队列中的请求。

StringRequest的构造方法为:

/**
* @method 请求方式(GET、POST等)
* @url 请求url
* @listener 请求成功回调的接口
* @errorListener 请求失败回调的接口
*/
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener)

GET

一个完整的StringRequest的GET请求如下:

        String url = "http://apis.juhe.cn/mobile/get?phone=18270837821&key=9a4329bdf84fa69d193ce601c22b949d";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());//创建一个请求队列
        StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                Toast.makeText(getApplicationContext(),s,Toast.LENGTH_SHORT).show();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        });
        request.setTag("zxy");
        mRequestQueue.add(request);
        mRequestQueue.start();

POST

一个完整的StringRequest的POST请求如下:

String url = "http://apis.juhe.cn/mobile/get";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                Toast.makeText(getApplicationContext(),s,Toast.LENGTH_SHORT).show();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        }){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String,String> map  =new HashMap<>();
                map.put("phone","18270837821");
                map.put("key","9a4329bdf84fa69d193ce601c22b949d");
                return map;
            }
        };
        request.setTag("zxy");
        mRequestQueue.add(request);
        mRequestQueue.start();

其中,因为是以POST方式请求数据的,所以我们必须实现StringRequest的getParams()方法,该方法返回的是Map<String, String>类型的集合,也就是用<key,value>的形式把数据通过POST传入服务器

JsonObjectRequest

JsonObjectRequest构造方法为:

/**
* @method 请求方式(GET、POST等)
* @url 请求url
* @jsonRequest 请求传入的json数据
* @listener 请求成功回调的接口
* @errorListener 请求失败回调的接口
*/
public JsonObjectRequest(int method, String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorListener)

GET

一个完整的JsonObjectRequest的GET请求如下:

因为用的是GET请求方式,参数是在url中传入,所以JSONObject对象传入null

String url = "http://apis.juhe.cn/mobile/get?phone=18270837821&key=9a4329bdf84fa69d193ce601c22b949d";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject jsonObject) {
                Toast.makeText(getApplicationContext(),jsonObject.toString(),Toast.LENGTH_SHORT).show();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        });

        request.setTag("zxy");
        mRequestQueue.add(request);
        mRequestQueue.start();

POST

一个完整的JsonObjectRequest的POST请求如下:

String url = "http://apis.juhe.cn/mobile/get";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("phone", "18270837821");
            jsonObject.put("key", "9a4329bdf84fa69d193ce601c22b949d");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url, jsonObject, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject jsonObject) {
                Toast.makeText(getApplicationContext(),jsonObject.toString(),Toast.LENGTH_SHORT).show();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        });
        request.setTag("zxy");
        mRequestQueue.add(request);
        mRequestQueue.start();

用以上StringRequest和JSONObjectRequest请求我们都获取到了数据,如图

Volley网络框架完全解析(使用篇)

虽然这两种方式都可以返回我们请求的数据,但是JSONObjectRequest请求在处理json对象的返回结果时候效率更高,所以确定返回结果是json类型时候可以使用JSONObjectRequest

ImageRequest

ImageRequest的构造方法为:

/**
* @url 请求url
* @listener 请求成功回调的接口
* @maxWidth 图片最大的宽度(如果超过则Volley会对图片进行压缩,如果为0则不压缩)
* @maxHeight 图片最大的高度
* @decodeConfig 图片的配置
* @errorListener 请求失败回调的接口
*/
public ImageRequest(String url, Listener<Bitmap> listener, int maxWidth, int maxHeight, Config decodeConfig, ErrorListener errorListener)

普通的加载方式(ImageView显示)

一个标准的普通请求网络图片的方法:

String url="http://img.my.csdn.net/uploads/201507/21/1437459520_6685.jpg";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap bitmap) {
                mImageView.setImageBitmap(bitmap);
            }
        }, 0, 0, Bitmap.Config.ARGB_8888, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        });
        request.setTag("zxy");
        mRequestQueue.add(request);
        mRequestQueue.start();
    }

这种方式只适合在图片数量不多的情况下使用,否则则需要考虑使用下面两种,同样,因为Volley内部本身就有硬盘缓存机制,在没网的情况下则会加载缓存中的图片

带LruCache缓存的加载方式(ImageView+ImageLoader+ImageCache)

我们上一种方法就是使用Volley中的ImageRequest请求的加载网络图片,这种方法没有LruCache和显示效果不好,下面我们来使用Volley中的ImageLoader+ImageCache的加载方式,这种方式可以在网络差的情况下和加载出错的情况下给出一个默认的提示图片,而且使用了LruCache缓存可以避免OOM。

首先我们创建一个ImageLoader对象,ImageLoader构造方法

public ImageLoader(RequestQueue queue, ImageLoader.ImageCache imageCache)

需要传入请求队列对象和ImageCache对象,我们再来看看ImageCache对象

public interface ImageCache{...}

发现它是ImageLoader内部的一个接口,所以我们得实现这个接口然后传入,于是我们创建一个BimapCache实现ImageCache接口:

public class BitmapCache implements ImageLoader.ImageCache {
    private LruCache<String, Bitmap> mBitmapLruCache;

    public BitmapCache() {
        int maxCache = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxCache / 8;
        mBitmapLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };
    }

    @Override
    public Bitmap getBitmap(String key) {
        return mBitmapLruCache.get(key);
    }

    @Override
    public void putBitmap(String key, Bitmap bitmap) {
        mBitmapLruCache.put(key, bitmap);
    }
}

其实这就是创建一个图片内存缓存对象。

之后我们再使用ImageLoader.get()方法加载网络图片,我们来看看get()方法的参数:

/**
* @requestUrl - 请求url
* @listener - ImageLoader.ImageListener的监听对象
* @maxWidth - 图片的最大高度,如果超过则会压缩,为0则不压缩
* @maxHeight
*/
public ImageLoader.ImageContainer get(String requestUrl, ImageLoader.ImageListener listener, int maxWidth, int maxHeight)

第一个参数好办,那么第二个参数我们可以通过ImageLoader.getImageListener()来得到,我们来看看它的参数:

/**
* @view - 表示将图片设置到哪个控件对象上
* @defaultImageResId- 默认时显示的图片的资源id
* @errorImageResId- 加载出错时显示的图片的资源id
*/
public static ImageLoader.ImageListener getImageListener(final ImageView view, final int defaultImageResId, final int errorImageResId)

好了一切都好办了,一个带LruCache缓存的加载图片的标准请求:

String url="http://img.my.csdn.net/uploads/201507/21/1437459520_6685.jpg";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        ImageLoader mImageLoader = new ImageLoader(mRequestQueue,new BitmapCache());
        ImageLoader.ImageListener imageListener = ImageLoader.getImageListener(mImageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
        mImageLoader.get(url,imageListener,0,0);

效果图:

Volley网络框架完全解析(使用篇)

【注意】:mImageLoader.get(url,imageListener)加载图片的原理是什么呢?其实从源码中很容易看出,它首先会得到url对应的key,然后判断硬盘缓存中是否含有该key的图片,如果有则取出,没有则通过网络请求重新加载图片,所以使用这种双缓存的方式加载网络图片,可以有效的防止OOM

使用NetworkImageView+ImageLoader+ImageCache

Volley框架中对图片的请求做的特别好,其中还为我们提供了一个专门用于显示图片的控件:NetworkImageView,该控件继承自ImageView,除了拥有ImageView控件的功能之外,还多了三个方法:

public void setImageUrl(String url, ImageLoader imageLoader) {
        this.mUrl = url;
        this.mImageLoader = imageLoader;
        this.loadImageIfNecessary(false);
    }

public void setDefaultImageResId(int defaultImage) {
        this.mDefaultImageId = defaultImage;
    }

public void setErrorImageResId(int errorImage) {
        this.mErrorImageId = errorImage;
    }
  1. setDefaultImageResId - 设置该控件默认时显示的图片
  2. setErrorImageResId - 设置加载网络图片失败时显示的图片
  3. setImageUrl - 从网络上加载图片

使用NetworkImageView需要在layout中替换掉ImageView:

<com.android.volley.toolbox.NetworkImageView
        android:id="@+id/net_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

同样这里也需要使用ImageLoader和ImageCache,一个标准使用NetworkImageView的例子为:

String url="http://img.my.csdn.net/uploads/201507/21/1437459520_6685.jpg";
        RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        ImageLoader mImageLoader = new ImageLoader(mRequestQueue,new BitmapCache());
        mNetworkImageView.setDefaultImageResId(R.mipmap.ic_launcher);//设置默认显示的图片
        mNetworkImageView.setErrorImageResId(R.mipmap.ic_launcher);//设置加载出错时显示的图片
        mNetworkImageView.setImageUrl(url,mImageLoader);

效果为:

Volley网络框架完全解析(使用篇)

可以看到用NetworkImageView和第二种方式效果是一样的,它同样会先检查硬盘缓存中有没有该图片,如果没有,再通过网络加载得到该图片,如果有则直接设置。既然和第二种一样,那为什么还推出NetworkImageView这个控件呢?答案就是NetworkImageView这个控件在你的Activity退出时候会自动取消网络请求,即完全不需要我们担心网络请求生命周期的问题。

【注意】:在上述两种使用了LruCache缓存加载图片的方法,当图片量较大时推荐使用,否则当你只有几张图片则使用第一种比较好,因为你使用LruCache时需要为它分配一定的内存空间,而图片量不大时候也使用LruCache缓存,那这块空间则一直是作为缓存图片用的,占着这块内存空间,相反反而会得不偿失。

Volley与Activity生命周期联动与取消请求

其实就是在Activity退出时候或销毁时候,取消对应的网络请求,避免网络请求在后台浪费资源,所以,我们一般在onStop()方法中通过之前设置的Tag取消网络请求:

@Override
protected void onStop() {
        super.onStop();
        mRequestQueue.cancelAll("zxy");
    }

RequestQueue.cancelAll()方法会过滤出Tag为“zxy”的所有请求都一并取消。

或者通过Request.cancel()取消请求,把当前Activity的请求放入一个List集合中,关闭Activity时分别取消。

好了,这篇Volley框架的使用篇已经讲完了,下一篇讲Volley的缓存!!!