一、前言
Volley 是一个基于 HTTP 的网络开源库,让 Android 应用更快更容易地连接网络,在 GitHub 上可以找到它的源项目。Volley 具有以下优点:
* 自动调度网络请求。
* 支持多并发网络连接。
* 支持缓存。
* 支持请求优先级。
* 支持取消请求,可以取消单个请求,也可以取消包含多个请求的请求块。
* 支持自定义。
* 支持异步数据排序功能。
* 支持调试和具备跟踪工具。
Volley 适用于 RPC(远程过程调用:Remote Procedure Call)类型操作,例如将搜索结果页面作为结构化数据获取。Volley 可以很容易与所有协议集成,支持原始字符串,图像和 JSON。
Volley 不适合大型下载或流媒体操作,因为 Volley 在解析期间将所有响应保存在内存中。对于大型下载操作,可以考虑使用 DownloadManager
。
最简单添加 Volley 的方法是将以下依赖项添加到应用程序的 build.gradle
文件中:
dependencies {
...
compile 'com.android.volley:volley:1.1.1'
}
除此之外,你还可以克隆 Volley 项目库并将其设置为库项目:
- 通过在命令行键入以下内容来克隆项目库:
git clone https://github.com/google/volley
- 将下载的源作为 Android 库模块导入到应用项目中。
二、使用 Volley 发送请求
使用 Volley 之前,必须将 android.permission.INTERNET
权限添加到应用的清单中。不然,应用无法连接到网络。
1. 使用 newRequestQueue
Volley 提供了一个便捷的方法 Volley.newRequestQueue
为你设置并启动 RequestQueue
队列,该 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<String>() {
@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 总是在主线程上传递已解析的响应,这样可以很方便地使用接收到的数据填充 UI 控件。
2. 发送请求
要发送请求,只需构建一个请求并使用 add()
将其添加到 RequestQueue
,如上所示。添加请求后,请求将通过网络获取服务,解析并传递其原始响应。
当调用 add()
时,Volley 运行一个高速缓存处理线程和一个网络分派线程池。当向队列添加请求时,它会被缓存线程拾取并进行分类:如果请求可以从缓存中获取服务,则缓存响应将在缓存线程上进行解析,并将解析后的响应在主线程上传递。如果无法从缓存中为请求提供服务,则将其置于网络队列中。第一个可用的网络线程从队列中获取请求,执行 HTTP 事务,在子线程上解析响应,然后将响应写入缓存,并将解析的响应发送回主线程来进行传递。
可以在任意线程中添加请求,但响应始终在主线程上传递。
3. 取消请求
要取消请求,请对 Request
对象调用 cancel()
。一旦取消,Volley 保证你的响应处理回调永远不会被调用。一般可以在 Activity 的 onStop()
方法中取消所有待处理的请求。
但是,这样的话你必须跟踪所有正在进行的请求。有一种更简单的方法:你可以使用一个标记对象与每个请求进行关联。然后,使用此标记对象获得取消请求的范围。例如,使用 Activity 将所有由它发出的请求进行标记,并从 onStop()
中调用 requestQueue.cancelAll(this)
。同样,在 ViewPager 选项卡中使用各自的选项卡标记所有缩略图图像请求,并在滑动时取消,以确保新选项卡不会被另一个选项卡的请求持有。
以下是使用字符串标记的示例:
- 定义标记并将其添加到你的请求中。
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);
- 在 Activity 的
onStop()
方法中,取消所有具有此标记的请求。
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
取消请求时要注意,该请求的响应是否是必要的。
三、设置 RequestQueue
1. 设置网络和缓存
RequestQueue 需要两样东西都完成它的工作:一个是用于执行请求传输的网络,一个是用于处理缓存的缓存。Volley 工具箱中已经有标准的实现:DiskBasedCache
提供带有内存索引的单文件响应缓存,BasicNetwork
根据你首选的 HTTP 客户端提供网络传输。
BasicNetwork
是 Volley 的默认网络实现。BasicNetwork
必须被用来连接到网络的 HTTP 客户端初始化。这个客户端通常是 HttpURLConnection
。
下面代码段显示了初始化 BasicNetwork
的步骤包括设置 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.example.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
,并在得到响应后调用 stop()
来实现单次请求。
但更常见的情况是创建一个 RequestQueue
的单例,使得它在应用的生命周期内保持运行。
2. 使用单例模式
设置一个 RequestQueue
的单例通常效率最高。推荐的方法是实现封装 RequestQueue
和其他 Volley 功能的单例类。另一种方法是创建 Application
的子类并在 Application.onCreate()
方法中设置 RequestQueue
,但是这种方法并不推荐,因为静态单例可以以更模块化的方式提供相同的功能。
一个关键概念是 RequestQueue
必须使用 Application
上下文进行实例化,而不是 Activity
上下文。这样可以确保 RequestQueue
在应用的生命周期内持续使用,而不是每次重新创建 Activtiy
时重新实例化(例如当用户旋转设备时)。
这里是一个单类,它提供 RequestQueue
和 ImageLoader
功能:
public class MySingleton {
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 并接收原始字符串作为响应。
JsonObjectRequest 和 JsonArrayRequest(两个都是 JsonRequest 的子类)。指定 URL 并分别获取 JSON 对象或数组作为响应。
如果你的预期响应是这些类型之一,则不必实现自定义请求。
1. 请求 JSON
Volley 为 JSON 请求提供以下类:
JsonArrayRequest
- 使用给定的 URL 获取 JSONArray 响应体。JsonObjectRequest
- 使用给定的 URL 获取 JSONObject 响应体,允许将 JSONObject 作为请求体的一部分传入。
这两个类都基于公共基类 JsonRequest。下面代码段是提取 JSON 数据并在 UI 中将其显示为文本:
String url = "http://my-json-feed";
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
mTextView.setText("Response: " + response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO: Handle error
}
});
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest);
五、实现自定义请求
对于那些不支持开箱即用的数据类型,我们需要实现自定义请求类型。
1. 写一个自定义请求
大多数请求在工具箱中都有现成的实现,如果响应是字符串、图像或 JSON,则不需要实现自定义请求。
对于需要实现自定义请求的情况,你只需执行以下操作:
扩展
Request<T>
类,其中<T>
表示请求所期望的已解析响应的类型。因此,如果解析后的响应是字符串,则通过扩展Request<String>
创建自定义请求。实现抽象方法,
parseNetworkResponse()
和deliverResponse()
。
1.1 parseNetworkResponse
对于给定类型(例如字符串,图像或 JSON),Response 封装解析的响应用来传递。以下是一个示例实现 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 状态代码和响应头。
自定义实现必须返回一个 Response<T>
,其中包含你自定义的响应对象、缓存元数据或错误。
如果你的协议具有非标准缓存语义,你可以自己构造一个 Cache.Entry
,但大多数请求如下使用:
return Response.success(myDecodedObject,
HttpHeaderParser.parseCacheHeaders(response));
Volley 在工作线程中调用 parseNetworkResponse()
。这确保了耗时的解析操作(例如将 JPEG 解码为 Bitmap)不会阻塞 UI 线程。
1.2 deliverResponse
Volley 在 parseNetworkResponse()
方法中携带返回的对象回到主线程。大多数请求在此处调用回调接口,例如:
protected void deliverResponse(T response) {
listener.onResponse(response);
六、示例:GsonRequest
Gson 是一个通过反射将 Java 对象转换为 JSON 或者将 JSON 转换为 Java 对象的开源库。你可以定义拥有相同 JSON 键字段的 Java 对象,将类对象传给 Gson,Gson 自动使用响应数据填充字段。
下面是使用 Gson 解析 Volley 请求的完整实现:
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));
}
}
}