LZ阅读的是中文翻译版本:http://hukai.me/android-training-course-in-chinese/basics/activity-lifecycle/recreating.html,试验机系统版本7.1。
5、Android网络连接
5.7 使用Volley执行网络数据传输
Volley是一个HTTP库,能够帮助Android App更方便地执行网络操作,最重要的是,它更快速高效。我这里是通过在搜索引擎搜索jar包,然后添加到module的libs目录来使用它的。
Volley有如下优点:
- 自动调度网络请求
- 高并发网络连接
- 通过标准的HTTP cache coherence(高速缓存一致性)缓存磁盘和内存透明的响应
- 支持指定请求的优先级
- 撤销请求API。我们可以取消单个请求,或者指定取消请求队列中的一个区域
- 框架容易被定制
- 强大的指令可以使得异步加载网络数据并正确地显示到UI的操作更加简单
通过提供内置的功能,Volley可以使得我们免去重复编写样板代码,使我们可以把关注点放在App的功能逻辑上。
Volley不适合用来下载大的数据文件。因为Volley会保持在解析的过程中所有的响应。
发送简单的网络请求
使用Volley的方式是,创建一个RequestQueue并传递Request对象给它。RequestQueue管理用来执行网络操作的工作线程,从缓存中读取数据,写数据到缓存,并解析Http的响应内容。请求解析原始的响应数据,Volley会把解析完的响应数据分发给主线程。
下面演示发送一个获取String的网络请求
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的newRequestQueue方法使用默认值创建了一个请求队列,创建了一个StringRequest对象用于获取字符串,最后将StringRequest对象添加到请求队列里,这样就算完成了一次请求。当然,在传递给StringRequest的参数里有两个回调接口,其中Request.Listener是在请求成功获得响应时调用的对象,在其onResponse方法里处理响应数据,更新UI,而Response.ErrorListener则是在请求出现问题是调用的对象,在其onErrorResponse方法里有一个VolleyError对象,可以在该方法里通过该对象获得具体的错误信息。
为了发送一个请求,你只需要构造一个请求并通过add方法将该请求添加到RequestQueue中,一旦添加了这个请求,它会通过队列得到处理,然后得到原始的响应数据并返回。
当执行add方法时,Volley触发执行一个缓存处理线程以及一系列网络处理线程。当添加一个请求到队列中,它将被缓存线程所捕获并触发:如果这个请求可以被缓存处理,那么会在缓存线程中执行响应数据的解析并返回到主线程。如果请求不能被缓存所处理,它会被放到网络队列中,网络线程池中的第一个可用的网络线程会从队列中获取到这个请求并执行HTTP操作,解析工作线程的响应数据,把数据写到缓存中并把解析之后的数据返回到主线程。
你可以在任何线程中添加一个请求,但是响应结果都是返回到主线程的。
下图演示了一个请求的生命周期
取消一个请求
对Request对象调用cancel方法即可。一旦取消,Volley会确保你的响应不会被执行,即上文提到的两个回调接口。这意味着在实际操作中你可以在Activity的onStop方法中取消所有等待在队列中的请求。
为了取消一系列的请求,你可以跟踪所有已经发送的请求,以便在需要的时候可以取消他们。你可以为每一个请求对象绑定一个TAG,然后通过调用RequestQueue的cancelAll(TAG)方法来取消所有绑定了TAG的请求。
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);
}
}
一个RequestQueue需要两部分来支持它的工作:一部分是网络操作,用来传输请求,另外一部分是用来处理缓存操作的Cache。在Volley的tools中包含了标准的实现方式:DiskBasedCache提供了每个文件与对应响应数据一一映射的缓存实现;BasicNetwork提供了一个机遇AndroidHttpClient或者HttpURLConnection的网络传输。
BasicNetwork是Volley默认的网络操作实现方式,一个BasicNetwork必须使用App用于连接网络的Http Client进行初始化。这个Client通常是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的单例,这样它能够持续保持在整个App的生命周期中。
一个关键的概念是RequestQueue的单例必须使用ApplicationContext来实例化,而不是Activity Context。因为这样才能确保RequestQueue在我们App的生命周期中一直存活,而不会因为Activity的重建而被重建。
下面是一个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;
}
}
使用上述单例类来执行网络请求
// 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);
创建标准的网络请求
前面已经演示了StringRequest请求字符串数据。下面演示一下ImageRequest和JsonRequest,这都是Volley内置的Request子类对象。它们使我们的开发效率更高,速度更快。
请求一张图片
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);
跟StringRequest类似。
下面演示用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));
为了更加方便的加载图片,可以使用Volley内置的NetworkImageView替代ImageView
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);
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);
对于需要自定义的请求类型,需要执行以下操作
- 继承Request<T>类,<T>表示解析过的响应请求预期的数据类型。因此如果我们需要解析的响应数据类型是一个String,可以通过继承Request<String>来创建自定义的请求
- 实现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
...
}
演示deliverResponse的实现
protected void deliverResponse(T response) {
listener.onResponse(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));
}
}
}