volley
[TOC]
volley是一个简单的异步http库,特点就是封装了请求,加载,缓存,线程,同步等过程和提供回调方法,使用者只需创建特定类型的Request和传入相应的url,便可在回调中获取到相应类型的对象。除此之外,volley还为图片的加载提供了多种方式,毕竟图片的大量加载在平时开发时需要用到。
优点是比较适合小而频繁的http请求,还结合了ImageLoader的优点。
缺点是不支持同步,不能post大数据,所以不适合用于上传大文件。
1 volley使用方法
学习一个框架最好的一个办法就是先从它的使用入手,知道了它如何使用了,然后再跟着它的使用步骤去看它的源码是怎样实现的。
这里分为三个部分介绍,一个是volley的基本使用方法,一个是将volley加载网络图片的方法单独提取出来介绍,一个是自定义Request的使用。
1.1 volley的基本使用
使用volley获取相应数据是需要3步:
- 创建RequestQueue对象
- 创建Request对象并在Listener的回调中处理回调对象
一般会在回调方法中更新UI - 将Request添加至RequeueQueue
注:HTTP的请求类型通常有两种,GET和POST,默认使用的是GET方法,之后在分析源码时可以看到,如果要发出POST请求的话,还应该在创建Request对象时传入Method参数并重写getParams(),在内部返回一个带有数据的Map对象
1.1.1 StringRequest
按照上面的步骤我们可以写出下面的StringRequest使用的步骤(其中访问的数据是在自己搭建的服务器):
//1. 创建RequestQueue对象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
//2. 创建Request对象并在Listener中处理回调对象,在回调方法中更新UI
StringRequest stringRequest = new StringRequest(
"http://10.132.25.104/JsonData/data1.php",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// TODO Auto-generated method stub
textView.setText(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG,error.getMessage());
}
});
//3. 将Request添加至RequestQueue
queue.add(stringRequest);
上面的Request的请求方式是GET,那如何将请求方式转为POST然后添加参数呢?答案就在于创建StringRequest的时候,传入Method.POST将请求方式转为POST,然后重写getParams()将请求参数添加进Map里然后返回,代码如下。
StringRequest stringRequest = new StringRequest(Request.Method.POST, url, listener, errorListener)
{
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> map = new HashMap<String,String>();
map.put("name", "Shane");
map.put("age","23");
return map;
}
};
1.1.2 JsonRequest
JsonRequest也是继承自Request类,因此使用方法跟StringRequest一样,不过JsonRequest是一个抽象类,JsonRequest有两个直接的子类JsonObjectRequest和JsonArrayRequest,这两个类的区别就是一个的回调方法是JSONObject,另一个是JSONArray。它们的格式分别是:
JSONObject:"{name:lee, age:24}"
JSONArray:"[{name:one, age:1},{name:two, age:2},{name:three, age:3}]"
JsonObjectRequest用法(数据格式为"{name:lee, age:24}")
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d("TAG", response.toString());
textView.setText("name:"+response.getString("name")
", age:"+response.getInt("age"));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
queue.add(jsonObjectRequest);
JsonArrayRequest的用法(数据格式为"[{name:one, age:1},{name:two, age:2},{name:three, age:3}]")
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
url, new Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
tv.setText("");
for(int i = 0;i<response.length();i++)
{
try {
JSONObject object = response.getJSONObject(i);
tv.append("name:"+object.getString("name"));
tv.append(", age:"+object.getInt("age")+"\n");
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}, null);
queue.add(stringRequest);
1.2 volley加载网络图片
volley之中为加载图片提供了多种方法,能够实现与UIL一样出色的加载网络图片的功能,下面就来学习一下使用volley加载网络图片的方法。
1.2.1 ImageRequest
一看到Request就想到之前所学的StringRequest和JsonRequest,同样的,ImageRequest也是volley提供的异步加载图片的请求,使用方法也是和之前的一样。
- 创建RequestQueue对象
- 创建Request对象
- 将Request添加至RequeueQueue
不过与之前不同的是,ImageRequest的构造方法所需的参数比较特殊,由下面的代码可以看出,ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
ImageRequest imageRequest = new ImageRequest(
"http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg",
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
// TODO Auto-generated method stub
ivPic1.setImageBitmap(response);
}
},
0,
0,
Config.RGB_565,
new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO Auto-generated method stub
ivPic1.setImageResource(R.drawable.ic_launcher);
}
});
queue.add(imageRequest);
1.2.2 ImageLoader
使用ImageRequest已经十分方便了,因为用法和其他的Request的使用方法一样,因此十分容易记忆。但是ImageRequest就相当于每次调用都去访问网址请求数据,这对于大量加载网络图片来说这是不可取的。回忆一下UIL加载图片的流程,其中使用了缓存来提高加载图片的效率。
同样的,volley也提供了ImageLoader,它不仅可以对图片进行缓存,还可以过滤掉重复的链接来避免重复发送请求。
使用volley的ImageLoader加载图片分为以下几步:
- 创建RequestQueue对象
- 创建BitmapCache类实现ImageCache接口
需要自己实现缓存(一般使用LruCache) - 创建ImageLoader对象
- 通过ImageLoader获取ImageListener对象
其中设置显示控件ImageView以及默认显示图片 - 通过ImageLoader.get()加载显示图片
其中可设置图片大小限制
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.defaultImage, R.drawable.errorImage);
imageLoader.get("http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg",
listener,200,200);
/**
* 自定义缓存类实现ImageCache接口
*/
class BitmapCache implements ImageCache
{
private LruCache<String, Bitmap> lruCache;
public BitmapCache()
{
lruCache = new LruCache<String, Bitmap>(10 * 1024 * 1024)
{
@Override
protected int sizeOf(String key, Bitmap value) {
// TODO Auto-generated method stub
return value.getRowBytes() * value.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
// TODO Auto-generated method stub
return lruCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
// TODO Auto-generated method stub
lruCache.put(url, bitmap);
}
}
1.2.3 NetworkImageView
volley还提供了第三种加载网路图片的方法,自定义了继承于ImageView的NetworkImageView,通过setImageUrl可以直接从url中加载图片,当然这里面也需要RequestQueue和ImageLoader。
使用NetworkImageView需要以下几步(前面几步跟上面一节的一样,只是后面不需要Listener了):
- 创建RequestQueue对象
- 创建BitmapCache类实现ImageCache接口
需要自己实现缓存(一般使用LruCache) - 创建ImageLoader对象
- 在xml布局文件创建NetworkImageView控件
- 在代码中使用NetworkImageView的setImageUrl加载图片
注:在之前的加载网络图片的方法中,都可以自定义最终图片的大小从而压缩图片,NetworkImageView的压缩则是直接利用xml中的控件的宽高,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽高,再决定是否需要对图片进行压缩。在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。因此NetworkImageView的压缩过程是在内部自动化的,并不需要我们关心
前3步与前一节一样,所以省去了,只看NetworkImageView是怎么设置工作的。
在xml布局文件中:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"
/>
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/network_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
/>
</LinearLayout>
在java代码中:
NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
networkImageView.setImageUrl("http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg", imageLoader);
1.3 自定义Request
上面介绍了volley中的一些基本网络请求使用方法,可以发现,volley主要是通过建立Request对象,然后将其添加进工作的RequestQueue之中。但是在volley之中仅仅提供了StringRequest、JsonRequest及其子类、还有针对图片的ImageRequest。而开发中还有许多其他类型的返回数据,比如说xml数据类型,此时就是展现volley的强大之处的时候了。volley是一个开源框架,因此我们可以仿照这StringRequest的实现来自定义XMLRequest,GsonRequest等Request子类,这样我们只需将该Request添加至RequestQueue当中便可以在回调中获取到相应的数据类型,而不用每次都进行封装。
1.3.1 StringRequest源码
要自定义Request,首先先查看volley自带的StringRequest是怎么实现的,然后我们便可以仿照着自定义Request类。以下是StringRequest的源码。
//StringRequest.java
public class StringRequest extends Request<String> {
private final Listener<String> mListener;
/**
* Creates a new request with the given method.
*
* @param method the request {@link Method} to use
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* Creates a new GET request.
*
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
}
从上面的代码中可以看出实现一个Request的子类只需要关注3个地方,构造方法、deliverResponse和parseNetworkResponse。
- 其中构造方法中自定义了一个Response.Listener,用于主线程中的回调;
- deliverResponse是返回解析的结果,该方法运行在主线程当中,一般该方法内部利用Listener将解析数据回调出去;
- parseNetworkResponse是将网络返回的字节数组型的数据(response.data)解析成想要的类型,比如StringRequest就解析成String,JsonObjectRequest就解析成JSONObject,最后通过调用Response.success()来将解析结果重新包装成Response对象返回。
1.3.2 JsonObjectRequest源码
JsonObjectRequest跟StringRequest类似,不过在JsonObjectRequest的上面多了一层JsonRequest,原因是JsonArrayRequest和JsonObjectRequest有些相同的地方,比如deliverResponse(),因此JsonObjectRequest中就不用实现deleverResponse了。
public class JsonObjectRequest extends JsonRequest<JSONObject> {
/**
* Creates a new request.
* @param method the HTTP method to use
* @param url URL to fetch the JSON from
* @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
* indicates no parameters will be posted along with request.
* @param listener Listener to receive the JSON response
* @param errorListener Error listener, or null to ignore errors.
*/
public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
errorListener);
}
/**
* Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
* <code>null</code>, <code>POST</code> otherwise.
*
* @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener)
*/
public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
ErrorListener errorListener) {
this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
listener, errorListener);
}
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString =
new String(response.data, HttpHeaderParser.parseCharset(response.headers));
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));
}
}
}
可以看出JsonObjectRequest的parseNetworkResponse与StringRequest不同的地方就是,其将String数据里面转换成了JSONObject数据。由此可以知道,自定义的Request子类其实就是在parseNetworkResponse里面将String数据转换成所需的数据格式,比如XML所需的数据格式为XmlPullParser,则只需将String数据转换成XMLPullParser即可。
1.3.3 XMLRequest
有前面的StringRequest和JsonObjectRequest可以知道,自定义一个Request只需要3步,最主要就是在parseNetworkResponse中则将String转换成XmlPullParser,这里就不多说了,直接看代码。
public class XMLRequest extends Request<XmlPullParser>{
private final Listener<XmlPullParser> mListener;
public XMLRequest(int method, String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
}
public XMLRequest(String url,Listener<XmlPullParser> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
@Override
protected Response<XmlPullParser> parseNetworkResponse(
NetworkResponse response) {
try {
String xmlString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlString));
return Response.success(xmlPullParser,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
return Response.error(new ParseError(e));
}catch (XmlPullParserException e) {
// TODO Auto-generated catch block
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(XmlPullParser response) {
mListener.onResponse(response);
}
}
1.3.4 GsonRequest
再实现一个Request加深印象,这里采用GsonRequest。因为JsonObjectRequest和JsonArrayRequest还是有点麻烦,因此自行封装一个GsonRequest。
public class GsonRequest<T> extends Request<T> {
private final Listener<T> mListener;
private Class<T> mClazz;
private Gson mGson;
public GsonRequest(int method, String url,Class<T> clazz, Listener<T> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.mListener = listener;
this.mClazz = clazz;
mGson = new Gson();
}
public GsonRequest(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) {
this(Method.GET, url, clazz, listener, errorListener);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String parsed = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
T result = mGson.fromJson(parsed, mClazz);
return Response.success(result,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
}
下面展示实际的用法:
首先看一下信息类User
public class User {
private UserInfo userInfo;
public UserInfo getUserInfo() {
return userInfo;
}
public void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}
}
public class UserInfo
{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在代码中使用GsonRequest,可以发现,GsonRequest中只传入了User而并没有传入UserInfo,这里Gson已经把User里面的类做了封装了,因此可以直接获取到UserInfo类。
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
GsonRequest<User> gsonRequest = new GsonRequest<User>(url,
User.class,
new Response.Listener()
{
@Override
public void onResponse(User response)
{
UserInfo userInfo = response.getUserInfo();
textView.setText("name:"+userInfo.getName()
+ ", age:"+userInfo.getAge());
}
},new Response.ErrorListener()
{
@Override
public void onErrorResponse(VolleyError error)
{
Log.e("TAG", error.getMessage(), error);
}
});
queue.add(gsonRequest);
从url获取到的数据格式如下:
{
"userInfo":
{
"name":"lee",
"age":23
}
}
输出格式如下:
name:23, age:23
1.3.5 JacksonRequest
由于Gson解析Json数据的性能不如Jackson,因此再来自定义一个JacksonRequest,这里需要注意的是,Jackson在解析Json数据的时候,对于字符串类型一定要有双引号引起来,不然会报错,比如{"name":"lee","age":19}
public class JacksonRequest<T> extends Request<T> {
private final Listener<T> listener;
private Class<T> mClazz;
private ObjectMapper mapper;
public JacksonRequest(int method, String url,Class<T> clazz, Listener<T> l, ErrorListener listener) {
super(method, url, listener);
this.listener = l;
this.mClazz = clazz;
mapper = new ObjectMapper();
}
public JacksonRequest(String url,Class<T> clazz, Listener<T> l, ErrorListener listener) {
this(Method.GET,url,clazz, l, listener);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String parsed = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
T result = mapper.readValue(parsed, mClazz);
return Response.success(result,
HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
// TODO Auto-generated method stub
listener.onResponse(response);
}
}
最后再总结一下自定义Request的用法,主要分为3步:
- 构造带有Response.Listener的构造方法
- 重写deliverResponse()方法,一般在该方法里面调用mListener.onResponse(result)将解析结果回调出去
- 重写parseNetworkResponse()方法,该方法是实现不同Request子类的关键,因为在该方法中,需要将获取到的String类型的数据转换成特定的类型,比如JsonObjectRequest需要JSONObject类型数据,XMLRequest需要XmlPullParser类型数据等等。