2. 参考博文
http://arnab.ch/blog/2013/08/asynchronous-http-requests-in-android-using-volley/
http://blog.csdn.net/guolin_blog/article/details/17482095
Volley是Google提供的网络通信库,通过它可以使得从网络获取数据更加的方便。它对网络的请求做了很多封装优化,让开发者更加便捷的开发。在新的Android文档中,已经加入了Volley方面的部分,在Training部分。网上有很多对它的参考,当然,Android文档就是一份很好的参考。大牛都批评只会使用而不去研究它源码的码农,但,我还是先浅尝辄止,不去学习的它的源码。不过,他们都推荐它的源码写得非常好,那当然要适时的找时间学习它的源码。下面记录下它的使用方法,涉及到的有异步请求文本,json,图片等请求,Volley是对返回的字节流做了封装。当然,我们也可以根据需要继承Request,定制自己的Request。另外,需要记录的是内存级别和硬盘(sd卡)的缓存处理。
我们首先要获得一个RequestQueue对象,文档推荐使用单例模式。推荐的方法是实现一个单模式类,提供一个获取对象的单例方法。如下:
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
// getApplicationContext() is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
其中, Volley#newRequestQueue(Context context) 这个静态方法的参数很讲究。不推荐使用Activity的context,而是使用Application的context,正如上面的代码所示。这样,即使一个Activity销毁了,RequestQueue对象也不会跟随它销毁,而是和这个APP的生命周期相关联,避免了重复的生成。
接着,我们来看请求文本数据,如下:
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
StringRequest有多个重载的构造方法,可以使用GET方式和POST方式,相应的有请求成功和请求失败的回调监听函数。那么对于POST方式,如何传递参数呢?在JsonObjectRequest的构造方法中,有个参数选项可以传进一个Map结构的键值对参数。而StringRequest 中确没有,参考博文的方法是,重写StringRequest的getParams()方法,如下:
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("params1", "value1");
map.put("params2", "value2");
return map;
}
};
有了请求对象后,就把该对象加入的请求队列RequestQueue中即可。
// Add the request to the RequestQueue.
queue.add(stringRequest);
如何请求JSON数据呢?就是构造一个JsonObjectRequest或者JsonArrayRequest请求对象,形式和上面大同小异。然后加入到请求队列中即可。
那如何,请求图片资源呢?记录这两种好用的方式。一种是构造一个ImageRequest对象,然后在请求成功的回调函数中ImageView#setImageBitmap 即可。构造方法中可以指定图片的大小。
ImageRequest imageRequest = new ImageRequest(imgUrl1, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
mImageView1.setImageBitmap(response);
}
}, 200, 200, Config.ARGB_8888, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "imageRequest-->"+error.getMessage());
}
});
mQueue.add(imageRequest);
和上面的类似。另一种功能强大一些,它可以设定加载中的图片,加载失败的图片,和内存缓存的策略。该方法使用的是ImageLoader类实现,首先生成一个该对象:
mImageLoader = new ImageLoader(mQueue, new LruBitmapCache(this));
第一个参数是请求队列,第二个参数是缓存策略。文档中提供一个例子。当然,我们可以自己实现或者实现为不使用缓存。最后,我们调用一下#get方法就可以了,
mImageLoader.get(imgUrl2, ImageLoader.getImageListener(mImageView2, android.R.drawable.alert_dark_frame, android.R.drawable.edit_text));
内存缓存中,有个奇怪的事,我在文档提供的 LruBitmapCache类的 #getBitmap 方法中,加入log观察,发现多次请求同一个url图片时,它的 #get 方法返回的对象返回为null,根本就没有缓存到内存中。
基本的网络请求,就如上。有时,我们需要设置一下HTTP头,该如何做?覆盖请求对象的 #getHeaders 方法即可,
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("CUSTOM_HEADER", "Yahoo");
headers.put("ANOTHER_CUSTOM_HEADER", "Google");
return headers;
}
有时,当我们退出Activity时,希望取消正在进行的网络操作,我们可以调用RequestQueue的一系列的 #cancel* 方法,比如,
public static final String TAG = "MyTag";
StringRequest stringRequest; // Assume this exists.
RequestQueue mRequestQueue; // Assume this exists.
// Set the tag on the request.
stringRequest.setTag(TAG);
// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
有时,我们需要根据自己的需求封装请求返回的格式,那么我们可以继承Request来实现一个,文档中给出一个Gson的例子,是很好的参考,
public class GsonRequest<T> extends Request<T> {
private final Gson gson = new Gson();
private final Class<T> clazz;
private final Map<String, String> headers;
private final Listener<T> listener;
/**
* Make a GET request and return a parsed object from JSON.
*
* @param url URL of the request to make
* @param clazz Relevant class object, for Gson's reflection
* @param headers Map of request headers
*/
public GsonRequest(String url, Class<T> clazz, Map<String, String> headers,
Listener<T> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.clazz = clazz;
this.headers = headers;
this.listener = listener;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers != null ? headers : super.getHeaders();
}
@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}
现在,还有一个较重要的问题要记录,就是缓存问题。Volley默认是对每一个url 都缓存到sd卡中(实验得出)。默认大小是5M。那么我们如何取消缓存或者修改缓存大小呢?修改缓存大小,可以如下(参考文档),
RequestQueue mRequestQueue;
// Instantiate the cache
Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap
// Set up the network to use HttpURLConnection as the HTTP client.
Network network = new BasicNetwork(new HurlStack());
// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(cache, network);
// Start the queue
mRequestQueue.start();
String url ="http://www.myurl.com";
// Formulate the request and handle the response.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Do something with the response
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Handle error
}
});
// Add the request to the RequestQueue.
mRequestQueue.add(stringRequest);
...
那么,如何设置不进行缓存呢,暂时没有发现直接的方法,但阅读源码,可以很快的实现,方法如下,
/**
* 没有硬盘缓存
* @param context
* @param stack
* @return
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new NoCache(), network);
queue.start();
return queue;
}
和原来代码中不同的是,删除了一些缓存初始化的代码,以及构造RequestQueue请求队列时,传入的是 NoCache对象,这样即可。我们也可以通过RequestQueue#getCache方法获得Cache对象,然后进行移除指定key的缓存文件,或者clear 清空全部。这样,功能就达到了 组件Android-Universal-Image-Loader所能实现的。