【安卓网络请求开源框架Volley源码解析系列】初识Volley及其基本用法

时间:2022-01-02 17:11:38

在安卓中当涉及到网络请求时,我们通常使用的是HttpUrlConnection与HttpClient这两个类,网络请求一般是比较耗时,因此我们通常会在一个线程中来使用,但是在线程中使用这两个类时就要考虑到如何将处理结果传出去,通常的解决方法就是采用接口回调技术来解决,代码如下:

public static void doGetRequest(final String uri,final HttpCallbackListener listener) throws IOException
{
new Thread(){
public void run ()
{
URL url;
try {
url = new URL(uri);
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
// connection.setDoInput(true);
// connection.setRequestMethod("GET");
connection.connect();//必须加上该语句
InputStream is=connection.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
StringBuilder buffer=new StringBuilder();
String line;
while((line=reader.readLine())!=null)
{
buffer.append(line); }
if(listener!=null)
{ listener.onComplete(buffer.toString());
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}.start(); }

在上述的doGetRequest()方法中的第二个参数即为一个回调接口,关于接口回调不是本博客的重点,因此不过多赘述。可以看到这样处理起来非常的繁琐,必须自己先定义一个接口,在接口中定义一些方法,然后还得将该接口作为doGetRequest()方法一个参数,在该方法中调用接口中定义的方法,最后在具体使用doGetRequest()时还得自己创建一个实现了该接口的实例类对象来具体执行接口中的方法。

当然如果涉及到一些复杂数据的请求,那就更加繁琐了,如图片,xml或json类型的数据,而事实上在网络通信中这些数据的请求也相当的频繁。因此谷歌在2013年Google I/O大会上推出了一个新的网络通信框架——Volley。也就是本博客的主角。

一初识Volley

Volley 是 Google 推出的轻量级 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。其适用场景是数据量小,通信频繁的网络操作。

主要特点:

(1). 扩展性强。Volley 中大多是基于接口的设计,可配置性强。

(2). 一定程度符合 Http 规范,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的处理,请求头的处理,缓存机制的支持等。并支持重试及优先级定义。

(3). 默认 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现。

    原因如下:

在 Froyo(2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。另外在 Gingerbread(2.3) HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了请求结果缓存。再加上 HttpURLConnection
本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。

(4). 提供简便的图片加载工具。

二Volley的基本用法:

正如我在上述所介绍的那样,网络中的请求一般包括文本请求,json数据请求与图片请求。下面我们一一介绍:

1StringRequest:首先我们来看一下类的定义:

public class StringRequest extends Request<String>

可以看到它继承自Request类,之所以谈到这个是因为后续讲解的JsonRequest也是继承自Requese。这也是为何它们用法非常相似的一个原因。

再来看一下StringRequest的构造函数:

 public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
} public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}

下面是基于第二种构造函数的使用StringRequest的代码:

RequestQueue mQueue = Volley.newRequestQueue(context);

StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);

从上述代码可以看到使用StringRequest来获取文本请求包括三个步骤:

1创建一个RequestQueue对象,调用Volley.newRequestQueue(context);即可得到一个RequestQueue对象。

2创建一个StringRequest对象,StringRequest的构造函数需要传入三个参数,因为网咯通信使用的是HTTP协议,因此第一个参数就是目标服务器的URL地址,第二个参数是服务器响应成功的回调,第三个参数是服务器响应失败的回调。其中,目标服务器地址我们填写的是百度的首页,然后在响应成功的回调里打印出服务器返回的内容,在响应失败的回调里打印出失败的详细信息。

3将StringRequest对象添加到RequestQueue对象中,即mQueue.add(stringRequest).

注意Volley是要访问网络的,因此不要忘记在你的AndroidManifest.xml中添加如下权限:

<uses-permission android:name="android.permission.INTERNET" />

显然上述代码是用来从服务器端获取数据的,我们知道HTTP请求包含两种最基本的请求方式,即POST与GET。显然上述代码表示的是get请求,那么如何发送post请求呢?

这就需要用到Volley的另一个构造函数了:

StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener);

该函数比前面的那个构造函数多了一个参数,即Method.POST用来表示是一个post请求,

可是这只是指定了HTTP请求方式是POST,那么我们要提交给服务器的参数又该怎么设置呢?StringRequest中没有提供设置POST参数的方法,但是当发出POST请求的时候,Volley会尝试调用StringRequest的父类——Request中的getParams()方法来获取POST参数,因此,我们只需要在StringRequest的匿名类中重写getParams()方法,在这里设置POST参数就可以了,代码如下所示:

StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
map.put("baidu", "baidu");
map.put("tencent", "tencent");
return map;
}
};

2JsonRequest:首先我们来看一下类的定义:

public abstract class JsonRequest<T> extends Request<T> 

可以看到与StringRequest一样,它也是继承自Request的,而且它是一个抽象类,也就是我们不能直接实例化它,而应该实例化其子类,JsonRequest包含两个子类:JsonArrayRequest与JsonObjectRequest。这也很好理解,分别对应json中的JsonArray与JsonObject。

再来看一下JsonObjectRequest的构造函数:

public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
errorListener);
} public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
ErrorListener errorListener) {
this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
listener, errorListener);
}

可以看到几乎与StringRequest一模一样,都包括两种构造函数,一种用来执行get请求一种用来执行post请求,其实不光构造函数与StringRequest一模一样,其用法也与StringRequest一模一样,只不过在StringRequest的三个步骤中的第二部创建一个JsonObjectRequest对象,代码如下:

RequestQueue mQueue = Volley.newRequestQueue(context);

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d("TAG", response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(jsonObjectRequest);

注意当获取数据成功时会回调第二个参数Listener接口,该接口中的onResponse()方法中携带的参数是一个JSONObject对象,因此只需要从JSONObject对象取出我们想要得到的那部分数据就可以了。

至于JsonObjectRequest与JsonObjectRequest用法几乎完全相同,在此不再赘述。

另外注意在JsonObjectRequest中存在一个重要的方法:protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) 该方法位于其父类Request中,即Request的子类都包含该方法,这个方法可以用来处理中文乱码问题,如在本人的仿腾讯QQ的IM通讯APP中的QQ天气模块开发时就遇到过中文乱码问题,即使用Volley从中国天气网提供接口获取到的数据显示出来是乱码,但是使用HttpUrlConnection则不会出现此问题。此时我们只需要自定义一个类继承自JsonObjectRequest

然后重写其 parseNetworkResponse()方法,在该方法中指定使用"UTF-8"格式即可。代码如下:

public static class CharsetJsonRequest extends JsonObjectRequest {

	    public CharsetJsonRequest(String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener) {
super(url, jsonRequest, listener, errorListener);
} public CharsetJsonRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener) {
super(method, url, jsonRequest, listener, errorListener);
} @Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { try {
String jsonString = new String(response.data, "UTF-8");
return Response.success(new JSONObject(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException je) {
return Response.error(new ParseError(je));
}
} }



3ImageRequest.:
首先我们来看一下类的定义:

public class ImageRequest extends Request<Bitmap> 

可以看到ImageRequest.也是继承自Request,那么你也应该想到它的使用与StringRequest几乎完全相同。

我们来看一下其构造函数:

 public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(
new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}

可以看到,ImageRequest的构造函数接收六个参数,每个参数含义如下:

url URL of the image 第一个参数就是图片的URL地址

listener Listener to receive the decoded bitmap第二个参数是图片请求成功的回调

maxWidth Maximum width to decode this bitmap to, or zero for none,maxHeight Maximum height to decode this bitmap to, or zero for none第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。

decodeConfig Format to decode the bitmap to第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。

errorListener Error listener, or null to ignore errors第六个参数是图片请求失败的回调,通常当请求失败时在ImageView中显示一张默认图片。

它的使用与StringRequest一模一样,代码如下:

RequestQueue mQueue = Volley.newRequestQueue(context);

ImageRequest imageRequest = new ImageRequest(
"http://www.baidu.com/home/home.png",
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
imageView.setImageBitmap(response);
}
}, 0, 0, Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
imageView.setImageResource(R.drawable.default_image);
}
}); mQueue.add(imageRequest);

4ImageLoader

上述三中类型其父类都继承自Request。因此它们的用法非常相似,但是ImageLoader则不然,我们还是先来看一下类的定义:

public class ImageLoader

可以看到ImageLoader没继承任何类,因此它的用法与上述三种不同。

下面我们来看一下ImageLoader中一些重要的属性成员:

 private final ImageCache mCache;

public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}

可以看到在ImageLoader中存在一个 ImageCache成员,它被定义为ImageLoader的一个内部接口,顾名思义,ImageCache是用来缓存图片的,因此可知ImageLoader具备缓存图片的功能,这也是ImageLoader比ImageRequest高效的原因。通常使用缓存的话会用到LruCache这个类。

下面我们来看一下其构造函数:

public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}

其构造器包括两个参数,第一个为RequestQueue对象,第二个为 ImageCache对象。我们来看一下如何使用它:

RequestQueue mQueue = Volley.newRequestQueue(context);

public class BitmapCache implements ImageCache {

	private LruCache<String, Bitmap> mCache;

	public BitmapCache() {
int maxSize = 10 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
} @Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
} @Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
} }
ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache()); ImageListener listener = ImageLoader.getImageListener(imageView,
R.drawable.default_image, R.drawable.failed_image);
imageLoader.get("http://www.baidu.com/index/logo.jpeg", listener);

可以看到总共包含大致四步(第二步创建一个缓存可以不用):

1. 创建一个RequestQueue对象。

2. 创建一个ImageLoader对象。(在这一步中如果要用到缓存,还需要创建一个缓存的类的实例)

3. 获取一个ImageListener对象。

4. 调用ImageLoader的get()方法加载网络上的图片。

在第三步中调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对象,getImageListener()方法接收三个参数,第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片。当第四部使用ImageLoader的get()方法加载网络上的图片后,ImageListener会自动将结果显示在ImageView控件上。

在第四步中get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:

imageLoader.get("http://www.baidu.com/index/logo.jpeg",
listener, 200, 200);

可以看到在上述几种方式中首先都要创建一个RequestQueue,那是不是我们每次使用都得创建一个呢?其实不需要,我们全局保有一个即可。这时候自然想到使用Application了。我们可以在Application里面创建RequestQueue,然后提供一个getRequestQueue()对外暴露该RequestQueue。代码很简单,相信大家都会写。

以上就是关于Volley的基本用法,感谢大家能看到最后!