volley是一个开源的http库,它能帮助app方便快速的执行网络操作。它能够使开发者更专注地集中于功能逻辑上。Volley不适合用来下载大的数据文件。因为Volley会在解析的过程中保留持有所有的响应数据在内存中。对于下载大量的数据操作,请考虑使用DownloadManager。volley可以很方便地从google代码仓库得到:https://android.googlesource.com/platform/frameworks/volley。
下面从几个方面说明volley库的使用
一、发送简单的网络请求
使用volley的方法是,我们建立一个RequestQueue并传递request对象给它。RequestQueue是用来管理执行网络操作的工作线程,它负责从cache读取数据,解析并相应http内容等。volley库提供了一个方便的方法volley.newRequestQueue来为自己建立一个新的requestqueue。
首先我们要在manifest文件中声明网络权限:android.permission.INTERNET
下面代码演示如何使用volley提供的方法来建立一个requestqueue:
final TextView mTextView = (TextView) findViewById(R.id.text);
...
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
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!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
Volley总是将解析后的数据返回至主线程中。
那么我们在volley中是怎样发送自己的请求的呢,实际上只需要构造一个请求并通过add()
方法添加到RequestQueue
中。一旦你添加了这个请求,它会通过队列,得到处理,然后得到原始的响应数据并返回。当你执行add()
方法时,Volley触发执行一个缓存处理线程以及一系列网络处理线程。当你添加一个请求到队列中,它将被缓存线程所捕获并触发:如果这个请求可以被缓存处理,那么会在缓存线程中执行响应数据的解析并返回到主线程。如果请求不能被缓存所处理,它会被放到网络队列中。网络线程池中的第一个可用的网络线程会从队列中获取到这个请求并执行HTTP操作,解析工作线程的响应数据,把数据写到缓存中并把解析之后的数据返回到主线程。
请注意那些比较耗时的操作,例如I/O与解析parsing/decoding都是执行在工作线程。你可以在任何线程中添加一个请求,但是响应结果都是返回到主线程的。
当然可以调用cancel()
方法取消一个请求,一旦取消,Volley会确保你的响应Handler不会被执行。这意味着在实际操作中你可以在activity的onStop()
方法中取消所有pending在队列中的请求。你不需要通过检测getActivity() == null
来丢弃你的响应handler,其他类似onSaveInstanceState()
等保护性的方法里面也都不需要检测。
为了利用这种优势,你应该跟踪所有已经发送的请求,以便在需要的时候可以取消他们。有一个简便的方法:你可以为每一个请求对象都绑定一个tag对象。然后你可以使用这个tag来提供取消的范围。例如,你可以为你的所有请求都绑定到执行的Activity上,然后你可以在onStop()
方法执行requestQueue.cancelAll(this)
。同样的,你可以为ViewPager中的所有请求缩略图Request对象分别打上对应Tab的tag。并在滑动时取消这些请求,用来确保新生成的tab不会被前面tab的请求任务所卡到。
一个演示为:
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);
然后再onstop()进行:
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
二、建立自己的请求队列,并利用单例模式使得requestqueue持续整个app生命周期
一个RequestQueue需要两部分来支持它的工作:一部分是网络操作,用来传输请求,另外一个是用来处理缓存操作的Cache。在Volley的工具箱中包含了标准的实现方式:DiskBasedCache
提供了每个文件与对应响应数据一一映射的缓存实现。 BasicNetwork
提供了一个网络传输的实现,连接方式可以是AndroidHttpClient 或者是 HttpURLConnection.
下面的代码片段演示了如何一步步建立一个RequestQueue:
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);
...
一个关键的概念是RequestQueue必须和Application context所关联的。而不是Activity的context。这确保了RequestQueue在你的app生命周期中一直存活,而不会因为activity的重新创建而重新创建RequestQueue。
下面是一个单例类,提供了RequestQueue与ImageLoader的功能:
private static MySingleton mInstance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;
private MySingleton(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mImageLoader = new ImageLoader(mRequestQueue,
new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap>
cache = new LruCache<String, Bitmap>(20);
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
}
public static synchronized MySingleton getInstance(Context context) {
if (mInstance == null) {
mInstance = new MySingleton(context);
}
return mInstance;
}
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;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
}
下面演示了利用单例类来执行RequestQueue的操作:
// Get a RequestQueue
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
getRequestQueue();
...
// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest);
三、创建标准的网络请求
使用Volley支持的常用请求类型:
StringRequest
。指定一个URL并在相应回调中接受一个原始的raw string数据。请参考前一课的示例。
ImageRequest
。指定一个URL并在相应回调中接受一个image。
JsonObjectRequest
与
JsonArrayRequest
(均为
JsonRequest
的子类)。指定一个URL并在相应回调中获取到一个JSON对象或者JSON数组。
如果你需要的是上面演示的请求类型,那么你应该不需要自己实现一个自定义的请求。
请求图片:Volley为请求图片提供了如下的类。这些类依次有着依赖关系,用来支持在不同的层级进行图片处理:
ImageRequest
- 一个封装好的,用来处理URL请求图片并且返回一张decode好的bitmap的类。它同样提供了一些简便的接口方法,例如指定一个大小进行重新裁剪。它的主要好处是Volley会确保类似decode,resize等耗时的操作执行在工作线程中。
ImageLoader
- 一个用来处理加载与缓存从网络上获取到的图片的帮助类。ImageLoader是管理协调大量的ImageRequest的类。例如,在ListView中需要显示大量缩略图的时候。ImageLoader为通常的Volley cache提供了更加前瞻的内存缓存,这个缓存对于防止图片抖动非常有用。。这还使得能够在避免阻挡或者延迟主线程的前提下在缓存中能够被Hit到。ImageLoader还能够实现响应联合Coalescing,每一个响应回调里面都可以设置bitmap到view上面。联合Coalescing使得能够同时提交多个响应,这提升了性能。
NetworkImageView
- 在ImageLoader的基础上建立,替换ImageView进行使用。对于需要对ImageView设置网络图片的情况下使用很有效。NetworkImageView同样可以在view被detached的时候取消pending的请求。
ImageView mImageView;
String url = "http://i.imgur.com/7spzG.png";
mImageView = (ImageView) findViewById(R.id.myImage);
...
// Retrieves an image specified by the URL, displays it in the UI.
ImageRequest request = new ImageRequest(url,
new Response.Listener() {
@Override
public void onResponse(Bitmap bitmap) {
mImageView.setImageBitmap(bitmap);
}
}, 0, 0, null,
new Response.ErrorListener() {
public void onErrorResponse(VolleyError error) {
mImageView.setImageResource(R.drawable.image_load_error);
}
});
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(request);
你可以使用ImageLoader与NetworkImageView用来处理类似ListView等大量显示图片的情况。在你的layout XML文件中,你可以使用NetworkImageView来替代通常的ImageView, 例如:
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/networkImageView"
android:layout_width="150dp"
android:layout_height="170dp"
android:layout_centerHorizontal="true" />
你可以使用ImageLoader来显示一张图片,例如:
ImageLoader mImageLoader;
ImageView mImageView;
// The URL for the image that is being loaded.
private static final String IMAGE_URL =
"http://developer.android.com/images/training/system-ui.png";
...
mImageView = (ImageView) findViewById(R.id.regularImageView);
// Get the ImageLoader through your singleton class.
mImageLoader = MySingleton.getInstance(this).getImageLoader();
mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView,
R.drawable.def_image, R.drawable.err_image));
然而,如果你要做得是为ImageView进行图片设置,你可以使用NetworkImageView来实现,例如:
ImageLoader mImageLoader;
NetworkImageView mNetworkImageView;
private static final String IMAGE_URL =
"http://developer.android.com/images/training/system-ui.png";
...
// Get the NetworkImageView that will display the image.
mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);
// Get the ImageLoader through your singleton class.
mImageLoader = MySingleton.getInstance(this).getImageLoader();
// Set the URL of the image that should be loaded into this view, and
// specify the ImageLoader that will be used to make the request.
mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);
对于ImageLoader(一个用来处理加载与缓存图片的帮助类)来说,单例模式可以避免旋转所带来的抖动。使用单例模式可以使得bitmap的缓存与activity的生命周期无关。如果你在activity中创建ImageLoader,这个ImageLoader有可能会在手机进行旋转的时候被重新创建。这可能会导致抖动。
Volley工具箱中提供了通过DiskBasedCache实现的一种标准缓存。这个类能够缓存文件到磁盘的制定目录。但是为了使用ImageLoader,你应该提供一个自定义的内存LRC缓存,这个缓存需要实现ImageLoader.ImageCache
的接口。你可能想把你的缓存设置成一个单例。
下面是一个内存LRU Cache的实例。它继承自LruCache并实现了ImageLoader.ImageCache的接口:
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import com.android.volley.toolbox.ImageLoader.ImageCache;
public class LruBitmapCache extends LruCache<String, Bitmap>
implements ImageCache {
public LruBitmapCache(int maxSize) {
super(maxSize);
}
public LruBitmapCache(Context ctx) {
this(getCacheSize(ctx));
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
public Bitmap getBitmap(String url) {
return get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
// Returns a cache size equal to approximately three screens worth of images.
public static int getCacheSize(Context ctx) {
final DisplayMetrics displayMetrics = ctx.getResources().
getDisplayMetrics();
final int screenWidth = displayMetrics.widthPixels;
final int screenHeight = displayMetrics.heightPixels;
// 4 bytes per pixel
final int screenBytes = screenWidth * screenHeight * 4;
return screenBytes * 3;
}
}
下面是如何初始化ImageLoader并使用cache的实例:
RequestQueue mRequestQueue; // assume this exists.
ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache(LruBitmapCache.getCacheSize()));
Volley提供了以下的类用来执行JSON请求:
JsonArrayRequest
- 一个为了获取JSONArray返回数据的请求。JsonObjectRequest
- 一个为了获取JSONObject返回数据的请求。允许把一个JSONObject作为请求参数。
TextView mTxtDisplay;
ImageView mImageView;
mTxtDisplay = (TextView) findViewById(R.id.txtDisplay);
String url = "http://my-json-feed";
JsonObjectRequest jsObjRequest = new JsonObjectRequest
(Request.Method.GET, url, null, new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
mTxtDisplay.setText("Response: " + response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO Auto-generated method stub
}
});
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
三、创建自定义的网络请求
大多数的请求类型都已经包含在Volley的工具箱里面。如果你的请求返回数值是一个string,image或者JSON,那么你是不需要自己去实现请求类的。
对于那些你需要自定义的请求类型,你需要执行以下操作:
Request<T>
类,<T>
表示请求返回的数据类型。因此如果你需要解析的响应类型是一个String,可以通过继承Request<String>
来创建你自定义的请求。请参考Volley工具类中的StringRequest与 ImageRequest来学习如何继承Request。parseNetworkResponse()
与deliverResponse()
,下面会详细介绍。parseNetworkResponse()
。
@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));
}
// handle errors
...
}
请注意:
parseNetworkResponse()
的参数是类型是NetworkResponse
,这种参数包含了的响应数据内容有一个byte[],HTTP status code以及response headers.如果你的协议没有标准的cache机制,你可以自己建立一个Cache.Entry
, 但是大多数请求都可以用下面的方式来处理:
return Response.success(myDecodedObject,
HttpHeaderParser.parseCacheHeaders(response));
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));
}
}
}