网络编程之Volley解析。在2013年Google I/O大会上推出了一个新的网络通信框架Volley。Volley既可以访问网络取得数据,也可以加载图片,并且在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。
Volley请求网络都是基于请求队列的,开发者只要把请求放在请求队列中就可以了,请求队列会依次进行请求,一般情况下,一个应用程序如果网络请求没有特别频繁则完全可以只有一个请求队列(对应Application),如果非常多或其他情况,则可以是一个Activity对应一个网络请求队列,这就要看具体情况了,首先创建队列:
RequestQueue mQueue = Volley.newRequestQueue(this);
使用StringRequest返回的数据是String类型的(请求网页返回的是html文件),StringRequest的源码:
public class StringRequest extends Request{ private final Listener mListener; public StringRequest(int method, String url, Listener listener, ErrorListener errorListener) { super(method, url, errorListener); this.mListener = listener; } public StringRequest(String url, Listener listener, ErrorListener errorListener) { this(0, url, listener, errorListener); } ... }
有两个构造函数,其中第一个比第二个多了一个请求的方法,如果采用第二个则默认是GET请求。
使用步骤:
1.(如果不存在RequestQueue对象)新建RequestQueue请求对象
RequestQueue mQueue = Volley.newRequestQueue(this);
2.实例StringRequest对象
new StringRequest(请求方法,请求的地址,监听事件,错误监听事件);
3.将请求添加在请求队列中
requestQueue.add(StringRequest对象);
实例:
private void UseStringRequest() {
//创建请求队列
StringRequest mStringRequest = new StringRequest(Request.Method.GET, BAIDU_URL,
new Response.Listener() {
@Override
public void onResponse(String response) {
Log.i(TAG, response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, error.getMessage(), error);
}
});
//将请求添加在请求队列中
mQueue.add(mStringRequest);
}
3.获取JSON字符串
使用JsonObjectRequest返回的数据是Json类型的数据(可以使用Gson解析,使用bean类来实现json数据存储)
使用步骤:
1.(如果不存在RequestQueue对象)新建RequestQueue请求对象
RequestQueue mQueue = Volley.newRequestQueue(this);
2.实例化JsonObjectRequest对象
new JsonObjectRequest(请求方法,请求的地址,监听事件,错误监听事件);
3.将请求添加在请求队列中
requestQueue.add(JsonObjectRequest对象)
实例:
private void UseJsonRequest() {
JsonObjectRequest mJsonObjectRequest = new JsonObjectRequest(
JSON_URL, null,
new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
Goods mGoods = new Gson().fromJson(response.toString(), Goods.class);
String id = mGoods.getId();
tv_response.setText("id= " + id);
Log.d(TAG + "**JSON**", "response== " + response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, error.getMessage(), error);
}
});
mQueue.add(mJsonObjectRequest);
}
4.图片的加载
异步加载图片
1.(如果不存在RequestQueue对象)新建RequestQueue请求对象
RequestQueue mQueue = Volley.newRequestQueue(this);
2.实例化LruCache对象
final LruCache lurcache=new LurCache(20);//20为图片大小
3.新建ImageCache对象
putBitmap(){
lurcache.put(key,value)
}
getBitmap(){
return lurcache.get(key)
}
4.创建ImageLoader对象
new ImageLoader(RequestQueue对象,ImageCache对象);
5.获取ImageListener对象
imageLoader.getimageListener(控件对象,默认图片资源,错误图片资源);
6.开始请求
imageLoader.get(图片请求地址,ImageListener对象);
实例:
public void loadImageVolley(){
RequestQueue mQueue = Volley.newRequestQueue(this);
final LruCache lurcache = new LruCache(20);
ImageCache imageCache = new ImageCache(){
@Override
public void putBitmap(String key,Bitmap value){
lurcache.put(key,value);
}
@Override
public Bitmap getBitmap(String key){
return lurcache.get(key);
}
};
ImageLoader imageLoader = new ImageLoader(mQueue,imageCache);
ImageListener listener = imageLoader.getImageLoader(iv,R.mipmap.ic_launcher,R.mipmap.error_img);
imageLoader.get(imageUrl,listener);
}
使用ImageRequest加载图片(已过时)
private void UseImageRequest() {
ImageRequest imageRequest = new ImageRequest(IMAGE_URL,
new Response.Listener() {
@Override
public void onResponse(Bitmap response) {
iv_image1.setImageBitmap(response);
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) { iv_image1.setImageResource(R.drawable.ico_default);
}
});
mQueue.add(imageRequest);
}
查看ImageRequest的源码发现它可以设置你想要的图片的最大宽度和高度,在加载图片时如果图片超过期望的最大宽度和高度则会进行压缩:
public ImageRequest(String url, Listener listener, int maxWidth, int maxHeight, ScaleType scaleType, Config decodeConfig, ErrorListener errorListener) {
super(0, url, errorListener);
this.setRetryPolicy(new DefaultRetryPolicy(1000, 2, 2.0F));
this.mListener = listener;
this.mDecodeConfig = decodeConfig;
this.mMaxWidth = maxWidth;
this.mMaxHeight = maxHeight;
this.mScaleType = scaleType;
}
使用ImageLoader异步加载图片
ImageLoader的内部使用ImageRequest来实现,它的构造器可以传入一个ImageCache缓存形参,实现了图片缓存的功能,同时还可以过滤重复链接,避免重复发送请求。
private void UseImageLoader() {
ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());
ImageLoader.ImageListener listener = ImageLoader.getImageListener(iv_image2, R.drawable.ico_default, R.drawable.ico_default);
imageLoader.get(IMAGE_URL, listener);
}
与ImageRequest实现效果不同的是,ImageLoader加载图片会先显示默认的图片,等待图片加载完成才会显示在ImageView上。
当然ImageLoader也提供了设置最大宽度和高度的方法:
public ImageLoader.ImageContainer get(String requestUrl, ImageLoader.ImageListener imageListener, int maxWidth, int maxHeight) {
return this.get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
使用NewWorkImageView
NetworkImageView是一个自定义控件,继承自ImageView,封装了请求网络加载图片的功能。使用NetworkImageView的好处是在有缓存机制的情况下NetworkImageView不会加载默认图片,而ImageView会加载默认图片,很明显的会看到ImageView会出现图片闪动,而NetworkImageView不会。
使用步骤:
1.异步加载图片的1-4步
2.NewWorkImageView.setTag(“tag”)
NewWorkImageView.setDefaultImageResId()
NewWorkImageView.setErrorImageResId()
NewWorkImageView.setImageUrl(图片请求地址,ImageLoader对象)
实例:
先在布局中引用:
代码中调用,和ImageLoader用法类似:
ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache()); nv_image.setDefaultImageResId(R.drawable.ico_default); nv_image.setErrorImageResId(R.drawable.ico_default);
nv_image.setImageUrl(IMAGE_URL,imageLoader);
NetworkImageView并没有提供设置最大宽度和高度的方法,根据我们设置控件的宽和高结合网络图片的宽和高内部会自动去实现压缩,如果我们不想要压缩可以设置NetworkImageView控件的宽和高都为wrap_content。
5.图片的缓存
//BitmapCache继承ImageCache接口
public class BitmapCache implements ImageLoader.ImageCache {
private LruCache mCache;
public BitmapCache() {
int maxSize = 8 * 1024 * 1024;
mCache = new LruCache(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);
}
}
6.自定义Listener方法及VolleyErrorHelper的使用
自定义Listener方法完成Json的解析
class JsonListener implements Response.Listener{
@Override
public void onResponse(JSONObject response) {
Goods mGoods = new Gson().fromJson(response.toString(), Goods.class);
String id = mGoods.getId();
tv_response.setText("id= " + id);
Log.d(TAG + "**JSON**", "response== " + response);
}
}
VolleyErrorHelper的调用(自定义Volley的错误监听方法,给用户Toast提示)
private class StrErrListener implements Response.ErrorListener {
@Override
public void onErrorResponse(VolleyError arg0) {
Toast.makeText(mContext,VolleyErrorHelper.getMessage(arg0, mContext),Toast.LENGTH_LONG).show();
}
}
VolleyErrorHelper
//正如前面代码看到的,在创建一个请求时,需要添加一个错误监听onErrorResponse。如果请求发生异常,会返回一个VolleyError实例。
//以下是Volley的异常列表:
//AuthFailureError:如果在做一个HTTP的身份验证,可能会发生这个错误。
//NetworkError:Socket关闭,服务器宕机,DNS错误都会产生这个错误。
//NoConnectionError:和NetworkError类似,这个是客户端没有网络连接。
//ParseError:在使用JsonObjectRequest或JsonArrayRequest时,如果接收到的JSON是畸形,会产生异常。
//SERVERERROR:服务器的响应的一个错误,最有可能的4xx或5xx HTTP状态代码。
//TimeoutError:Socket超时,服务器太忙或网络延迟会产生这个异常。默认情况下,Volley的超时时间为2.5秒。如果得到这个错误可以使用RetryPolicy。
public class VolleyErrorHelper {
/**
* Returns appropriate message which is to be displayed to the user against
* the specified error object.
*
* @param error
* @param context
* @return
*/
public static String getMessage(Object error, Context context) {
if (error instanceof TimeoutError) {
return context.getResources().getString(
R.string.generic_server_down);
} else if (isServerProblem(error)) {
return handleServerError(error, context);
} else if (isNetworkProblem(error)) {
return context.getResources().getString(R.string.no_internet);
}
return context.getResources().getString(R.string.generic_error);
}
/**
* Determines whether the error is related to network
*
* @param error
* @return
*/
private static boolean isNetworkProblem(Object error) {
return (error instanceof NetworkError)
|| (error instanceof NoConnectionError);
}
/**
* Determines whether the error is related to server
*
* @param error
* @return
*/
private static boolean isServerProblem(Object error) {
return (error instanceof ServerError)
|| (error instanceof AuthFailureError);
}
/**
* Handles the server error, tries to determine whether to show a stock
* message or to show a message retrieved from the server.
*
* @param err
* @param context
* @return
*/
private static String handleServerError(Object err, Context context) {
VolleyError error = (VolleyError) err;
NetworkResponse response = error.networkResponse;
if (response != null) {
switch (response.statusCode) {
case 404:
case 422:
case 401:
try {
// server might return error like this { "error":
// "Some error occured" }
// Use "Gson" to parse the result
HashMap result = new Gson().fromJson(
new String(response.data),
new TypeToken<>>() {
}.getType());
if (result != null && result.containsKey("error")) {
return result.get("error");
}
} catch (Exception e) {
e.printStackTrace();
}
// invalid request
return error.getMessage();
default:
return context.getResources().getString(
R.string.generic_server_down);
}
}
return context.getResources().getString(R.string.generic_error);
}
}