解析Android之Volley框架(1)
本文翻译自android官方文档,结合自己测试,整理如下。
使用Volley进行网络通信
Volley是Android官方提出的HTTP网络通信框架,它能够使得Android程序网络传输更容易而且更快。
Volley有以下优点:
- 自动调度网络请求。
- 多并发网络连接。
- 标准的HTTP网络请求缓存。
- 支持请求优先级。
- 取消请求API。我们可以取消单次请求,也可以取消请求块或一个范围。
- 容易定制。
- 很好地排序,能够使得网络异步数据正确地填充UI。
- 调试与追踪工具。
Volley不适合大数据量传输(包括上传和下载)或者数据流操作,这是因为Volley在内存中处理所有的请求。对于大数据量传输可以考虑使用其它工具,例如DownloadManager。
在使用Volley前要导入jar包,具体办法自行解决,哈哈,,,,。
发送一个简单的请求
我们通过创建一个RequestQueue和传递给Volley一个Request对象使用Volley。RequestQueue管理工作线程,这些线程用于网络操作/读写缓存/解析请求等。Requests处理原始请求,Volley把解析结果传递给主线程。
本节中将介绍如何使用Volley.newRequestQueue()
发送请求,添加请求,以及取消请求。
添加INTERNET许可
为了使用Volley,我们必须在manifest文件中添加许可android.permission.INTERNET
,没有该请求的话我们无法使用网络。
使用newRequestQueue()
可以通过Volley.newRequestQueue()
获取一个RequestQueue对象,然后添加一个Request到该请求队列中,这里我们使用StringRequest。具体步骤如下:
- 获取请求队列RequestQueue对象;
- 创建StringRequest对象;
- 将请求对象添加到请求队列中。
代码如下:
public void sendRequestWithVolley(View view){ // 1. 创建请求队列 RequestQueue queue = Volley.newRequestQueue(this); // 2. 创建请求对象 // 2.1 创建url String url = "http://blog.csdn.net/wangyongge85"; // 2.2 创建请求对象,这里使用StringRequest StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d(TAG,response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG,error.getMessage()); } }); // 3. 将请求对象加入到请求队列中 queue.add(stringRequest); }
上述代码中,我们创建了一个StringRequest对象,StringRequest的构造器接收四个参数,分别为:
- HTTP请求方法;
- 目标服务器的URL;
- 服务器响应成功回调方法;
- 服务器响应失败回调方法。
由于Response.Listener和Response.ErrorListener都是函数式接口(只有一个抽象方法),因此可以使用Lambda表达式取代。例如:Response.Listener<String>
可以替换为:(String response)->{Log.d(TAG,s);}
。
通过上述三步我们就完成了HTTP请求。
这里我们使用了Request.Method.GET
方法,若要向服务器发送数据,则使用Request.Method.POST
。但是StringRequest中并没有直接提供设置传递数据参数的构造器,我们只能通过重写父类的getParams()
方法来传递提交的数据,如下:
StringRequest stringRequest2 = new StringRequest(Request.Method.POST, url, (String s)->{Log.d(TAG,s);}, (VolleyError volleyError)-> {Log.d(TAG,volleyError.getMessage()); }) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> map = super.getParams(); map.put("param","value"); return map; } };
Volley总是在main线程中处理响应。将接收的数据在主线程中填充UI是很方便的,可以直接使用响应结果更新UI。
发送请求
为了发送一次请求,我们可以简单的通过RequestQueue的add()
方法,例如上面代码中。一旦我们添加过请求,该请求就会移动到请求队列中,并处理,然后返回原始响应数据并传输到Response.Listener或Response.ErrorListener处理。
当我们调用add()
方法时,Volley将运行一个高速缓存处理线程和一个网络调度线程池。若请求在缓存中的话,缓存响应就将在缓存线程中解析,并将解析响应传递给main线程;若请求不在缓存中的话,该请求将加入到网络请求队列中。第一个可用的网络线程从队列中取请求,执行HTTP事务,在子线程中解析响应,将响应写入缓存中,然后将解析的响应返回给主线程中。
注意到耗时的操作如阻塞I/O和解析解码等都要在子线程中执行。我们可以在任何线程中添加请求,但是处理响应结果总是在main线程中。
下图描述了请求全过程:
取消一次请求
为了取消一次请求,可以调用Request对象的cancel()
方法。一旦被取消,Volley能够保证我们的响应请求绝对不会被调用。这意味着在实际中我们能够在activity的onStop()
中取消所有的延迟请求,我们不需要通过检查是否getActivity()==null
,是否onSaveInstanceState()
已经被调用,或者其它内容回收响应处理者。
为了能够在合适的时间取消请求,我们可以在每次请求时设置一个标签,通过该标签我们能够取消相应的请求。例如下面的代码:
public static final String TAG = "MyTag";StringRequest stringRequest;RequestQueue mRequestQueue;// 设置请求标签stringRequest.setTag(TAG);// 将请求添加到请求队列中mRequestQueue.add(stringRequest);
然后我们可以在Activity中的onStop()
方法取消所有标签为TAG的请求:
@Overrideprotected void onStop () { super.onStop(); if (mRequestQueue != null) { mRequestQueue.cancelAll(TAG); }}
设置RequestQueue请求队列
上一节讲解了如何使用Volley.newRequestQueue()
设置RequestQueue对象,并充分利用Volley默认的请求方法。这一节中我们将通过下面的方法创建RequestQueue队列,方便我们实现自定义的请求方法。下面我们将使用单例模式创建一个RequestQueue对象,这种方式使得该RequestQueue对象的生存周期和我们程序的生存周期一样。
设置网络和缓存
RequestQueue需要完成下列两件事:执行传输请求的网络Network和处理缓存的空间Cache。在Volley中的toolbox包中有默认的实现:DiskBasedCache和BasicNetwork。DiskBasedCache提供一个文件缓存,并且带有内存索引;BasicNetwork提供基于HttpURLConnection或者AndroidHttpClient的网络传输请求,是Volley默认的网络请求实现。
- 在低于API 9 (Gingerbread)中使用的是AndroidHttpClient,在API 9之前,HttpURLConnection是不可靠的。- 在高于API 9之后(包括API 9),使用HttpURLConnection。
为了应用程序能够在Android所有的版本中使用Volley,我们可以检查android设备的版本,选择合适的HTTP客户端来创建BasicNetwork对象,例如:
HttpStack stack;if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); }}Network network = new BasicNetwork(stack);
在上面的例子中,通过HttpStack对象创建了一个BasicNetwork对象。而对于HttpStack对象来说在API 9之前是AndroidHttpClient(实现了HttpStack接口),在API 9之后是HurlStack对象(实现了HttpStack接口,并且内部使用HttpURLConnection实现网络请求)。
下面的代码描述了通过DiskBasedCache对象和BasicNetwork对象创建RequestQueue对象:
RequestQueue mRequestQueue;// 实例化缓存Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap// 使用HttpURLConnection作为Http客户端连接方式Network network = new BasicNetwork(new HurlStack());// 使用cache和network实例化消息队列mRequestQueue = new RequestQueue(cache, network);// 启动消息队列mRequestQueue.start();String url ="http://blog.csdn.net/wangyongge85";// 实例化请求对象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 }});// 将消息添加到消息队列中mRequestQueue.add(stringRequest);...
若我们仅仅只请求一次,不想使用线程池,我们可以在任何需要它的地方创建RequestQueue类。但是更常用的做法是创建一个全局RequestQueue单例对象。下面详细描述这种方法。
使用单例模式
若我们的程序一直使用网络的话,最高效的做法是创建一个全局的RequestQueue单例对象。实现单例的方法有很多种,本节中推荐使用一个单例封装RequestQueue对象和其他Volley方法。不建议在Application子类中的onCreate()
中创建RequestQueue对象。
在实例化RequestQueue时,必须使用Application作为参数,而不能使用Activity作为参数,这种方式保证RequestQueue在整个程序运行期间都存在,而不是当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(); // 实例化ImageLoader,这个在下面部分有详细讲解 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()能够防止内存泄漏 mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); } return mRequestQueue;}public void addToRequestQueue(Request<T> req) { getRequestQueue().add(req);}public ImageLoader getImageLoader() { return mImageLoader;}
下面代码给出了如何使用该单例模式:
// 获取请求队列RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()). getRequestQueue();...// 将请求对象发送到请求队列中MySingleton.getInstance(this).addToRequestQueue(stringRequest);
制定标准请求
本节中将介绍如何使用Volley默认实现的各种请求:
StringRequest
须指定URL,接收未处理的字符串响应。ImageRequest
须指定URL,接收图片响应。JsonObjectRequest
和JsonArrayRequest
都是JsonRequest的子类。指定URL,各自接收Json对象或Json数组响应。
若我们想接收以上数据响应的话,可以不用我们自定义请求,直接使用以上类就可以。
本章节将描述如何使用这些标准的请求方式。
请求图片响应
Volley提供了下列的类来请求图片。这些类之间存在着相互关系,提供不同级别的支持处理图像:
ImageRequest
从给定的URL中获得图片,并且通过解析的bitmap回调。它也能够提供一些方便的方法,例如指定一个大小来重新设置图片等。主要优点在于Volley线程处理机制能够保证耗时的图片操作(解码,重新绘画)自动在子线程中执行。ImageLoader
帮助类,能够加载和缓存从远程URL获取的图片。ImageLoader是一个管理大量的ImageRequest的类。MetworkImageView
建立在ImageLoader上,能够高效地在合适的地方显示通过网络获取的图片,因此可以替换ImageView。若该控件不再可用时,它也能够取消延迟请求。
使用ImageRequest
下面是一个简单的使用ImageRequest的例子。它能够检索指定URL中的图片,然后显示在我们的程序中。注意下面使用到的RequestQueue是通过单例模式创建的:
ImageView mImageView;String url = "http://i.imgur.com/7spzG.png";mImageView = (ImageView) findViewById(R.id.myImage);...// 检索URL指定位置的图片,然后显示在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); } });// 通过单例获取请求队列,然后将请求加入到请求队列中MySingleton.getInstance(this).addToRequestQueue(request);
ImageRequest的构造器接收六个参数,分别为:
- 图片的URL;
- 请求成功的回调;
- 指定允许图片最大的宽度,大于指定值则会压缩,0的话表示按原图大小显示;
- 指定允许图片最大的高度,大于指定值则会压缩,0的话表示按原图大小显示;
- 指定图片的颜色属性;
- 请求失败的回调。
使用ImageLoader和NetworkImageView
我们可以使用ImageLoader和NetworkImageView来高效地显示多个图片,例如在ListView显示。在我们的布局文件中使用NetworkImageView(和ImageView差不多)。
使用ImageLoader
使用ImageLoader将图片显示在ImageView中,例如:
ImageView mImageView;// 加载指定图片private static final String IMAGE_URL = "http://i.imgur.com/7spzG.png";...mImageView = (ImageView) findViewById(R.id.regularImageView);// 实例化ImageLoader,这个在下面部分有详细讲解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); } });mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView, R.drawable.def_image, R.drawable.err_image));
ImageLoader构造器参数为:
- 请求队列对象;
- ImageCache对象,用于缓存图片。
创建ImageLoader对象之后,调用其get()
方法加载图片,get()
方法接收两个参数:
- 图片的URL地址;
ImageListener对象,用于监听图片加载情况,可以通过
ImageLoader.getImageListener()
获得,该方法接收三个参数:- 显示图片的ImageView;
- 加载图片时显示的图片;
- 加载图片失败后显示的图片。
使用NetworkImageView
若要使用NetworkImageView的话,首先在布局文件中设置NetworkImageView,代码如下:
<com.android.volley.toolbox.NetworkImageView android:id="@+id/networkImageView" android:layout_width="150dp" android:layout_height="170dp" android:layout_centerHorizontal="true" />
然后使用ImageLoader将图片显示,代码如下:
ImageLoader mImageLoader;NetworkImageView mNetworkImageView;private static final String IMAGE_URL = "http://i.imgur.com/7spzG.png";...// 获取NetworkImageViewmNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView);// 通过单例模式获取ImageLoadermImageLoader = MySingleton.getInstance(this).getImageLoader();// 默认显示图片networkImageView.setDefaultImageResId(R.drawable.default_image);// 加载失败后显示的图片networkImageView.setErrorImageResId(R.drawable.failed_image);// 设置将要加载的图片,并且指定ImageLoader处理请求mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);
使用单例模式能够允许bitmap缓存超出activity的生命周期。若我们在activity中创建了一个ImageLoader,当用户每次旋转设备时,activity都需要重新创建,ImageLoader也会被重新创建。使用单例的话就会避免这种反复重建。
LRU缓存例子
Volley通过DiskBasedCache类提供了一个标准的缓存实现。该类直接将文件缓存在指定的存储器目录上。但是为了使用ImageLoader,我们应该提供一个自定义的内存LRU bitmap缓存,该缓存实现了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; }}
下面是通过cache实例化ImageLoader的例子:
RequestQueue mRequestQueue;ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache( LruBitmapCache.getCacheSize()));
请求JSON
Volley提供了下列方法处理JSON请求:
JsonArrayRequest
从给定的URL中检索JSONArray响应体。JsonObjectRequest
从给定的URL中检索JSON对象响应体。允许添加额外的JSONObject作为请求体的一部分。
这两个类都是基于基类JsonRequest的实现。我们可以根据下面相同的基本模式使用他们:
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) { } });MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
其中第三个参数为请求体,这里为null。
实现自定义请求
本节中将描述如何实现自定义的请求。
编写自定义请求
大部分请求都可以直接使用Volley提供的API,如:字符串,图片或者JSON。
如我们想实现自定义的请求如:GSON和XML。可以通过下列方法:
- 继承
Request<T>
类,其中T是解析响应的类型。 - 实现抽象方法
parseNetworkResponse()
和deliverResponse()`。
下面详细讲解具体实现方法。
实现parseNetworkResponse()
Response类封装了传递的解析的响应。如下实现代码:
@Overrideprotected 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参数,包括响应加载字节数组,HTTP,响应头。- 必须返回Response,该对象包含了我们定义的响应对象和数据缓存或者错误。
若我们的协议不包括标准的缓存语义,我们应该建立Cache.Entry,但是大多数请求都可以使用下面的方法:
return Response.success(myDecodedObject, HttpHeaderParser.parseCacheHeaders(response));
Volley在子线程中调用parseNetworkPesponse()
。这样能够确保耗时操作(解析JPEG成Bitmap等操作)不会阻塞UI线程。
deliverResponse()
Volley能够在主线程中处理parseNetworkResponse()
返回的结果。大多数采用下述方法:
protected void deliverResponse(T response) { listener.onResponse(response);
在parseNetworkResponse()
方法中,先将服务器响应的数据解析,然后在deliverResponse()
方法中进行回调。
下面以Gson请求为例。
自定义Gson请求
下面我们实现自定义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; /** * GET请求方式,并且返回JSON解析对象 */ 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 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)); } } @Override public Map<String, String> getHeaders() throws AuthFailureError { return headers != null ? headers : super.getHeaders(); }}
更多相关文章
- android 消息机制浅析
- Android进程通信(IPC)之AIDL对象传递
- 解析Android中的线程
- Android四大图片缓存框架之-Fresco(一)
- Android(安卓): 录音实现之AudioRecord类
- Android(安卓)内存泄漏总结
- Android(安卓)AsyncTask使用以及源码解析
- Android基础入门教程——4.2.1 Service初涉
- 【Android】音乐播放器边播边缓存(三)AndroidVideoCache的先下载再