文章目录
1. Volley概述
Volley是一个Http网络库,可以让你的Android应用能够更简单、更快速的进行网络请求。
源码在GitHub上托管
Volley有如下有点:
- 自动发起网络请求
- 并发网络连接请求
- 使用Http标准的缓存一致性透明的磁盘和内存缓存
- 支持请求优先级
- 支持请求取消
- 支持定制,例如重试和回退
- 强大的异步加载
- Debugging和Tracing工具
Volley执行RPC类型的操作用于填充UI,例如获取一个分页加载的结构化数据。可以很容易与任何协议和输入出为strings、Images及JSON。通过内建的支持你需要的功能,Volley将你从写样板代码中解放出来,让你集中精力在APP的逻辑上来。
Volley不适合大下载和流的操作,由于Volley将所有的响应在内存中解析。对于下载操作,考虑使用DownloadManager。
Volley核心库包含主要的请求分发管道以及一系列公共的应用工具,在Volley的“toolbox”。在AS中通过下面的代码将Volley加入到你的App中
dependencies {
...
compile 'com.android.volley:volley:1.1.1'
}
你也可以将Volley作为你应用的一个库,将源码导入你的工程中。
2. 发送一个简单的请求
在应用层,你可以使用Volley创建一个RequestQueue
,将Request
对象传递给它。RequestQueue
管理网络操作的工作线程,这些工作线程主要工作为读取网络数据并写入缓存,然后解析。Requests解析响应,Volley将解析后的响应回调给主线程中分发。
2.1 添加INTERNET permission
为了使用Volley,你必需添加android.permission.INTERNET权限到你的app的Manifest中。
2.2 使用newRequestQueue
Volley提供了Volley.newRequestQueue
方法用于设置RequestQueue,使用默认的值,开始一个queue。例如:
KOTLIN
val textView = findViewById<TextView>(R.id.text)
// ...
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(this)
val url = "http://www.google.com"
// Request a string response from the provided URL.
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
// Display the first 500 characters of the response string.
textView.text = "Response is: ${response.substring(0, 500)}"
},
Response.ErrorListener { textView.text = "That didn't work!" })
// Add the request to the RequestQueue.
queue.add(stringRequest)
JAVA
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,你可以自由的在响应的handler中修改UI控制,但是这个库中定义的一些重要的API是很关键的,尤其是取消请求的API。
请看设置一个RequestQueue关于如何创建一个RequestQueue,替代Volley.newRequestQueue
2.3 发送一个请求
为了发送一个请求,通常很简单地构造一个Request,然后通过add()方法把它加入到RequestQueue中,如下所示。一旦你将一个Request加入到RequestQueue中,该请求将经历一下过程:通过管道、获取服务、解析响应以及响应传递给上层。
当你调用add()方法,Volley将运行一个Cache处理的线程及一个网络请求分发的线程。当你将一个Request加入到队列中,它将被加入到Cache线程中和被触发:如果请求请求在Cache中就被处理了,Cache的响应将被在Cache线程中解析,被解析的响应将被传递给主线程。如果请求不能在Cache中处理,它将被加入网络请求队列中。第一个可用的网络线程从Queue中取出Request,通过http传输,在工作线程中解析响应,将响应加入response缓存中,并将解析的响应传递给UI线程。
注意:耗时操作,例如I/O及解析、解码在工作线程中完成。你可以在任何线程中添加Request,但是响应总是被传递到主线程。
2.4 取消一个请求
为了取消一个请求,调用cancel()在你的Request对象上。一旦取消,Volley将会确保你的响应将不会被处理。这意味着你可以取消所有请求在Activity调用onStop方法时,不需要在Handles中做非空判定getActivity()==null.
为了充分利用这种行为的优点,你必须为每个请求制定一个跟踪机制,以确保在合适的时机可以取消它们。有一种简单的方法:你可以关联一个tag对象到每个请求,你可以使用这个tag获取请求实例,并取消。例如,你可以用Activity标记所有的请求,在Activity执行onStop()时,调用requestQueue.cancelAll(this) 。类似的,你可以标记所有的thumbnail图片请求在一个ViewPager 页中,在却换到,取消它们,确保新的tab不会持有另一个tab的Request。
下面的示例展示如何使用一个string tag来标记一个Request
- 定义你的tag,并将他加入到你的Requests中
KOTLIN
val TAG = "MyTag"
val stringRequest: StringRequest // Assume this exists.
val requestQueue: RequestQueue? // Assume this exists.
// Set the tag on the request.
stringRequest.tag = TAG
// Add the request to the RequestQueue.
requestQueue?.add(stringRequest)
JAVA
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);
2, 在你的activity的onStop()方法中,取消持有这个tag的所有的请求
KOTLIN
protected fun onStop() {
super.onStop()
requestQueue?.cancelAll(TAG)
}
JAVA
@Override
protected void onStop () {
super.onStop();
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
3. 设置你的RequestQueue
上一节课程中展示了如何使用Volley.newRequestQueue方法设置一个RequestQueue,利用了Volley默认的行为的优点。这篇课程教你如何创建一个RequestQueue,让你可以提供你自定义的行为。
这节课也推荐了通过Singleton方式创建一个RequestQueue,使得RequestQueue和App生命周期一致。
3.1 设置网络并缓存
一个RequestQueue需要两件事情完成它的工作:网络用于传递请求,Cache用于处理缓存。在Volley toolbox中,这些都有默认实现。BasicNetwork必须通过一个HTTP Client被初始化。典型的如HttpUrlConnection。
下面的例子教你设置一个RequestQueue:
KOLTION
// Instantiate the cache
val cache = DiskBasedCache(cacheDir, 1024 * 1024) // 1MB cap
// Set up the network to use HttpURLConnection as the HTTP client.
val network = BasicNetwork(HurlStack())
// Instantiate the RequestQueue with the cache and network. Start the queue.
val requestQueue = RequestQueue(cache, network).apply {
start()
}
val url = "http://www.example.com"
// Formulate the request and handle the response.
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
// Do something with the response
},
Response.ErrorListener { error ->
// Handle error
textView.text = "ERROR: %s".format(error.toString())
})
// Add the request to the RequestQueue.
requestQueue.add(stringRequest)
// ...
JAVA
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);
// ...
如果你只需要一次性请求,那么Sending a Simple Request就足够了。但是大多数情况下,你需要创建一个单例模式的RequestQueue,使得它和app生命周期保持一致。
3.2 使用单例模式
如果你的App需要持续使用网络,使用单例模式的RequestQueue更加适合。
一个关键点是RequestQueue必须使用Application 的context进行初始化,而不是一个Activity的context。这个确保了RequestQueue的生命周期和App保持一致,不用跟随Activity每次重建。
下面的例子使用单例模式提供一个RequestQueue和ImageLoader功能:
KOTLIN
class MySingleton constructor(context: Context) {
companion object {
@Volatile
private var INSTANCE: MySingleton? = null
fun getInstance(context: Context) =
INSTANCE ?: synchronized(this) {
INSTANCE ?: MySingleton(context).also {
INSTANCE = it
}
}
}
val imageLoader: ImageLoader by lazy {
ImageLoader(requestQueue,
object : ImageLoader.ImageCache {
private val cache = LruCache<String, Bitmap>(20)
override fun getBitmap(url: String): Bitmap {
return cache.get(url)
}
override fun putBitmap(url: String, bitmap: Bitmap) {
cache.put(url, bitmap)
}
})
}
val requestQueue: RequestQueue by lazy {
// applicationContext is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
Volley.newRequestQueue(context.applicationContext)
}
fun <T> addToRequestQueue(req: Request<T>) {
requestQueue.add(req)
}
}
JAVA
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操作代码:
KOTLIN
// Get a RequestQueue
val queue = MySingleton.getInstance(this.applicationContext).requestQueue
// ...
// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest)
JAVA
// 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);
4. 一个标准请求
Volley支持下面类型的请求:
- StringRequest。接收一个URL,并且获取一个字符串响应。
- JsonObjectRequest 和JsonArrayRequest(JsonRequest的子类)。接收一个URL获取一个JSON对象或者数组的响应。
如果你希望获取以上类型的响应,有可能不需要实现一个自定义的request.这节课教你如何使用标准请求类型。
4.1 请求JSON
Volley提供以下类用于JSON请求:
- JsonArrayRequest-一个获取JSONArray的响应体的请求。
- JsonObjectRequest- 一个获取JSONObject响应的请求,允许传递一个JSONObject作为请求体的一部分。
两个类都共同继承自JsonRequest。使用方式如下:
KOTLIN
val url = "http://my-json-feed"
val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null,
Response.Listener { response ->
textView.text = "Response: %s".format(response.toString())
},
Response.ErrorListener { error ->
// TODO: Handle error
}
)
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest)
JAVA
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);
下面举例说明实现一个自定义的JSON Request,基于Gson
5. 实现一个自定义请求
5.1 写一个自定义请求
大部分请求已经在toolbox实现,如果你的响应是一个string,image或者JSON,可能你不需要实现一个自定义的Request。
为了满足可能需要自定义Request的情形,你需要照下面步骤执行:
- 继承Request 类, 代表解析的响应类型。所以如果你的解析类型是string,创建一个自定义请求类型通过继承Request. 请看toolbox 类中StringRequest和ImageRequest类,它们都继承子Request.
- 实现一个抽象方法parseNetworkResponse()和deliverResponse()方法。下面有详细描述:
5.2 parseNetworkResponse
下面是parseNetworkResponse()简单实现
KOTLIN
override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
return try {
val json = String(
response?.data ?: ByteArray(0),
Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response))
}
// handle errors
// ...
}
JAVA
@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的所有参数,包含了响应的payload、Http状态码以及响应的Headers。
- 你的实现不需返回一个Response, 包含了你响应的对象和缓存的metadata或者一个错误,例如解析失败的情形。
如果你的协议不是标准的缓存字段,你可以创建一个Cache.Entry,代码示例如下:
KOTLIN
return Response.success(myDecodedObject,
HttpHeaderParser.parseCacheHeaders(response))
JAVA
return Response.success(myDecodedObject,
HttpHeaderParser.parseCacheHeaders(response));
Volley在工作线程中调用parseNetworkResponse().确保耗时的解析操作,例如:解码JPEG到Bitmap中,不会阻塞IO
5.3 deliverResponse
Volley在主线程中完成Object的回调,这个Object通过调用parseNetworkResponse()返回。大部分的请求调用这个接口例如:
KOTLIN
override fun deliverResponse(response: T) = listener.onResponse(response)
JAVA
protected void deliverResponse(T response) {
listener.onResponse(response);
5.4 例子:GsonRequest
Gson是一个常用的java Json转换工具。下面是Volley请求使用Gson的完整实现:
KOTLIN
/**
* 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
*/
class GsonRequest<T>(
url: String,
private val clazz: Class<T>,
private val headers: MutableMap<String, String>?,
private val listener: Response.Listener<T>,
errorListener: Response.ErrorListener
) : Request<T>(Method.GET, url, errorListener) {
private val gson = Gson()
override fun getHeaders(): MutableMap<String, String> = headers ?: super.getHeaders()
override fun deliverResponse(response: T) = listener.onResponse(response)
override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
return try {
val json = String(
response?.data ?: ByteArray(0),
Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response))
} catch (e: UnsupportedEncodingException) {
Response.error(ParseError(e))
} catch (e: JsonSyntaxException) {
Response.error(ParseError(e))
}
}
}
JAVA
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));
}
}
}