Android网络请求通信之Volley
一、Volley简介
Volley网络框架是Google公司在2013年发布的一款Android平台上的网络请求通信库。以下是对Volley的简单归纳。
Volley的优点:
- 使网络通信更快、更简单、更健壮,用Volley开发的话,开发效率会得到很大提升,开发出来的网络模块的稳定性也会非常高
- Get、Post网络请求及网络图像的高效率异步处理请求,Volley帮我们实现了网络请求的异步化,而且它的Get和Post请求也是非常高效的
- 对网络请求进行排序、优先级处理
- 网络请求的缓存,当网络比较缓慢时或网络情况不太好的时候,Volley可以将我们上次请求的数据进行简单的缓存,提高用户体验
- Volley具有多级别取消请求,当我们有多个请求在同时进行的时候,可以做到同时取消这些请求的操作,非常的方便
- 和Activity生命周期的联动,当Activity结束、销毁的时候,可以同时取消网络请求的操作,避免APP在后台继续进行网络请求操作,导致APP性能和用户体验都非常差
Volley的缺点:
Volley不适合文件的上传和下载,当我们有上传和下载的需求的时候,可以考虑其他框架
为什么要使用Volley:
- 有高效的Get/Post方式的数据请求交互(效率非常高)
- 网络图片的加载和缓存(不使用Volley的情况下进行网络图片的处理会非常的麻烦,而且非常容易造成内存溢出,如果使用Volley,可以自动为图片进行缓存,不但节省流量,而且提高APP的性能)
- 是Google官方推出的针对Android平台的专用的网络通信库,Google的团队网络请求这部分优化的是非常好的,非常权威
- 性能很稳定,效率很强劲
使用Volley从服务器端获取数据的几种方式:
- StringRequest:对请求返回的数据类型不确定的情况下使用,涵盖了后面的两种请求对象
- JsonObjectRequest:确定请求返回的数据类型是JsonObject时使用,解析效率比StringRequest高一些
- JsonArrayRequest:确定求求返回的数据类型是JsonArray时使用
本帖解决的有关Volley的五个问题:
- Volley的Get和Post请求方式的使用
- Volley的网络请求队列的建立和取消队列请求
- Volley与Activity生命周期的联动
- Volley的简单二次封装
- Volley获取网络图片
二、Volley的网络请求队列的建立和取消队列请求
我们可以建立一个全局的请求队列,再在需要的时候建立一个请求,并将这个请求加入到请求队列中,这样一来整个APP的请求都可以通过这个队列来管理。因为Volley有全局请求队列这一功能,所以Volley更适合于并发的、对效率和性能要求非常好的场景。我们需要建立一个请求队列所在的全局类(继承自Application类),代码如下:
1 public class MyApplication extends Application {
2 private static RequestQueue queue; // Volley的全局请求队列
3
4 @Override
5 public void onCreate() {
6 super.onCreate();
7 // 创建Application的同时初始化Volley全局请求队列
8 queue = Volley.newRequestQueue(getApplicationContext());
9 }
10
11 // 静态方法返回Volley全局请求队列
12 public static RequestQueue getRequestQueue() {
13 return queue;
14 }
15 }
因为涉及到Application的关系,所以我们需要在AndroidMenifest文件的<application>标签中添加application的引用: android:name=".com.tools.MyApplication" 。另外,因为使用Volley需要使用网络,所以我们还需要为项目添加网络权限: <uses-permission android:name="android.permission.INTERNET" /> 。下面贴出AndroidMenifest文件中的所有代码:
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.activity.volleyclient"> 4 5 <uses-permission android:name="android.permission.INTERNET" /> 6 7 <application 8 android:name=".com.tools.MyApplication" 9 android:allowBackup="true" 10 android:icon="@mipmap/ic_launcher" 11 android:label="@string/app_name" 12 android:supportsRtl="true" 13 android:theme="@android:style/Theme.NoTitleBar"> 14 <activity 15 android:name=".MainActivity" 16 android:label="@string/app_name" 17 android:theme="@android:style/Theme.NoTitleBar"> 18 <intent-filter> 19 <action android:name="android.intent.action.MAIN" /> 20 21 <category android:name="android.intent.category.LAUNCHER" /> 22 </intent-filter> 23 </activity> 24 </application> 25 26 </manifest>
到此为止,我们就已经创建好了请求队列。我们通过 MyApplication.getRequestQueue().add(stringRequestGet); 来向请求队列中添加请求(注意在添加之前必须为每个请求设置TAG标记,以便删除);用 MyApplication.getRequestQueue().cancelAll("tag"); 来删除请求队列中的请求。
三、Volley的GET和POST请求方式的使用
上面提到过,Volley获取服务端代码有三种方式:StringRequest、JsonObjectRequest和JsonArrayRequest。由于这三种方式的代码基本一样,而且后两种相比于第一种也有一定的局限性,所以这里就以StringRequest为例,贴出程序源码和结果。
1 public class MainActivity extends Activity { 2 // 基本的URL地址(GET和POST都用到这个地址,只不过GET需要在URL上添加参数,而POST不需要) 3 private static final String BASE_URL = "http://192.168.1.102:8080/VolleyServer/JsonServlet"; 4 private TextView result; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.content_main); 10 result = (TextView) findViewById(R.id.result); 11 12 volleyGet(); 13 volleyPost(); 14 } 15 16 // 使用GET方式从服务端获取到JSON数据并加以解析 17 private void volleyGet() { 18 // 使用StringRequest获取JSON数据 19 String url = BASE_URL + "?key=person"; 20 StringRequest stringRequestGet = new StringRequest(Method.GET, url, new Response.Listener<String>() { 21 @Override 22 public void onResponse(String response) { 23 try { 24 addTextToResult("-->使用StringRequest用Get方式获取JSON数据\n"); 25 manageResponse(response); 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } 29 } 30 }, new Response.ErrorListener() { 31 @Override 32 public void onErrorResponse(VolleyError volleyError) { 33 addTextToResult(volleyError.toString()); 34 } 35 }); 36 // 给Volley的Request请求对象标注TAG,并加入到全局请求队列中 37 stringRequestGet.setTag("StringRequestGet"); 38 MyApplication.getRequestQueue().add(stringRequestGet); 39 } 40 41 // 使用POST方式从服务端获取到JSON数据并加以解析 42 private void volleyPost() { 43 // 使用StringRequest获取JSON数据 44 String url = BASE_URL; 45 StringRequest stringRequestPost = new StringRequest(Method.POST, url, new Response.Listener<String>() { 46 @Override 47 public void onResponse(String response) { 48 try { 49 addTextToResult("-->使用StringRequest用Post方式获取JSON数据\n"); 50 manageResponse(response); 51 } catch (Exception e) { 52 e.printStackTrace(); 53 } 54 } 55 }, new Response.ErrorListener() { 56 @Override 57 public void onErrorResponse(VolleyError volleyError) { 58 addTextToResult(volleyError.toString()); 59 } 60 }) { 61 @Override 62 protected Map<String, String> getParams() throws AuthFailureError { 63 Map<String, String> hashMap = new HashMap<>(); 64 hashMap.put("key", "person"); 65 return hashMap; 66 } 67 }; 68 stringRequestPost.setTag("StringRequestPost"); 69 MyApplication.getRequestQueue().add(stringRequestPost); 70 } 71 72 // 将参数中的文本添加到界面上的TextView中(在原文本的基础上添加) 73 private void addTextToResult(String text) { 74 String currentText = result.getText().toString(); 75 currentText += text; 76 result.setText(currentText); 77 } 78 79 // 将使用Volley获取到的服务端JSON数据进行解析后添加到结果TextView中 80 private void manageResponse(String response) { 81 try { 82 JSONObject jsonObject = new JSONObject(response).getJSONObject("person"); 83 addTextToResult("姓名:" + jsonObject.getString("name") + "\n年龄:" + jsonObject.getInt("age") + "\n地址:" + 84 jsonObject.getString("address") + "\n\n"); 85 } catch (Exception e) { 86 e.printStackTrace(); 87 } 88 } 89 90 @Override 91 protected void onStop() { 92 // 结束Activity的同时销毁全局请求队列中的所有请求 93 super.onStop(); 94 MyApplication.getRequestQueue().cancelAll("StringRequestGet"); 95 MyApplication.getRequestQueue().cancelAll("StringRequestPost"); 96 } 97 }
运行结果如下:
四、Volley与Activity生命周期的联动
所谓的和Activity生命周期的联动,就是在Activity被销毁的时候(调用Activity的onStop()方法的时候),同时销毁所有的Volley请求对象。代码已经包含在MainActivity的代码中,这里再贴一遍:
1 @Override 2 protected void onStop() { 3 // 结束Activity的同时销毁全局请求队列中的所有请求 4 super.onStop(); 5 MyApplication.getRequestQueue().cancelAll("StringRequestGet"); 6 MyApplication.getRequestQueue().cancelAll("StringRequestPost"); 7 }
五、Volley的简单的二次封装
所谓的“二次封装”,就是把上面我们所做的工作——包括实例化StringRequest、为Request添加TAG、将Request放入请求队列中灯操作——都封装成一个类或一个方法,以便以后调用。为了达到封装的目的,我们需要建立两个类:第一个类是VolleyInterface抽象类,用来定义请求成功和请求失败的接口,通过接口回调,让用户可以在主界面自定义实现操作;第二个类是VolleyRequest类,封装GET和POST两种请求方式的模糊代码,结合VolleyInterface类中的接口,完成封装。以下是这两个类的代码:
1 public abstract class VolleyInterface { 2 private Context context; 3 public static Response.Listener<String> listener; // 请求成功的监听器 4 public static Response.ErrorListener errorListener; // 请求失败的监听器 5 6 public VolleyInterface(Context context, Response.Listener<String> listener, Response.ErrorListener errorListener) { 7 this.context = context; 8 this.listener = listener; 9 this.errorListener = errorListener; 10 } 11 12 // 提供给用户编写的请求成功后的操作的接口 13 public abstract void onSuccess(String response); 14 15 // 提供给用户编写的请求失败后的操作的接口 16 public abstract void onError(VolleyError error); 17 18 // 利用接口回调,封装StringRequest中的请求成功的接口 19 public Response.Listener<String> loadingListener() { 20 listener = new Response.Listener<String>() { 21 @Override 22 public void onResponse(String response) { 23 onSuccess(response); 24 } 25 }; 26 return listener; 27 } 28 29 // 利用接口回调,封装StringRequest中的请求失败的接口 30 public Response.ErrorListener loadingErrorListener() { 31 errorListener = new Response.ErrorListener() { 32 @Override 33 public void onErrorResponse(VolleyError error) { 34 onError(error); 35 } 36 }; 37 return errorListener; 38 } 39 }
1 public class VolleyRequest { 2 private static StringRequest request; 3 private static Context context; 4 5 // 使用StringRequest用GET方式从服务端获取JSON数据的封装方法 6 public static void RequestGet(Context context, String url, String tag, VolleyInterface vif) { 7 MyApplication.getRequestQueue().cancelAll(tag); 8 request = new StringRequest(Request.Method.GET, url, vif.loadingListener(), vif.loadingErrorListener()); 9 request.setTag(tag); 10 MyApplication.getRequestQueue().add(request); 11 MyApplication.getRequestQueue().start(); 12 } 13 14 // 使用StringRequest用POST方式从服务端获取JSON数据的封装方法 15 public static void RequestPost(Context context, String url, String tag, final Map<String, String> params, VolleyInterface vif) { 16 MyApplication.getRequestQueue().cancelAll(tag); 17 request = new StringRequest(Request.Method.POST, url, vif.loadingListener(), vif.loadingErrorListener()) { 18 @Override 19 protected Map<String, String> getParams() throws AuthFailureError { 20 return params; 21 } 22 }; 23 request.setTag(tag); 24 MyApplication.getRequestQueue().add(request); 25 MyApplication.getRequestQueue().start(); 26 } 27 }
以下是MainActivity类中的测试代码:
1 public class MainActivity extends Activity { 2 // 基本的URL地址(GET和POST都用到这个地址,只不过GET需要在URL上添加参数,而POST不需要) 3 private static final String BASE_URL = "http://192.168.1.102:8080/VolleyServer/JsonServlet"; 4 private TextView result; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.content_main); 10 result = (TextView) findViewById(R.id.result); 11 12 volleyGet(); 13 volleyPost(); 14 customGet(); 15 customPost(); 16 } 17 18 // 使用GET方式从服务端获取到JSON数据并加以解析 19 private void volleyGet() { 20 // 使用StringRequest获取JSON数据 21 String url = BASE_URL + "?key=person"; 22 StringRequest stringRequestGet = new StringRequest(Method.GET, url, new Response.Listener<String>() { 23 @Override 24 public void onResponse(String response) { 25 try { 26 addTextToResult("-->使用StringRequest用Get方式获取JSON数据\n"); 27 manageResponse(response); 28 } catch (Exception e) { 29 e.printStackTrace(); 30 } 31 } 32 }, new Response.ErrorListener() { 33 @Override 34 public void onErrorResponse(VolleyError volleyError) { 35 addTextToResult(volleyError.toString()); 36 } 37 }); 38 // 给Volley的Request请求对象标注TAG,并加入到全局请求队列中 39 stringRequestGet.setTag("StringRequestGet"); 40 MyApplication.getRequestQueue().add(stringRequestGet); 41 } 42 43 // 使用POST方式从服务端获取到JSON数据并加以解析 44 private void volleyPost() { 45 // 使用StringRequest获取JSON数据 46 String url = BASE_URL; 47 StringRequest stringRequestPost = new StringRequest(Method.POST, url, new Response.Listener<String>() { 48 @Override 49 public void onResponse(String response) { 50 try { 51 addTextToResult("-->使用StringRequest用Post方式获取JSON数据\n"); 52 manageResponse(response); 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } 56 } 57 }, new Response.ErrorListener() { 58 @Override 59 public void onErrorResponse(VolleyError volleyError) { 60 addTextToResult(volleyError.toString()); 61 } 62 }) { 63 @Override 64 protected Map<String, String> getParams() throws AuthFailureError { 65 Map<String, String> hashMap = new HashMap<>(); 66 hashMap.put("key", "person"); 67 return hashMap; 68 } 69 }; 70 stringRequestPost.setTag("StringRequestPost"); 71 MyApplication.getRequestQueue().add(stringRequestPost); 72 } 73 74 // 使用二次封装的方法进行GET请求 75 private void customGet() { 76 VolleyRequest.RequestGet(this, BASE_URL + "?key=person", "CustomRequestGet", new VolleyInterface(this, VolleyInterface.listener, VolleyInterface.errorListener) { 77 @Override 78 public void onSuccess(String response) { 79 addTextToResult("-->使用VolleyRequest用GET方式获取JSON数据\n"); 80 manageResponse(response); 81 } 82 83 @Override 84 public void onError(VolleyError error) { 85 addTextToResult(error.toString()); 86 } 87 }); 88 } 89 90 // 使用二次封装的方法进行POST请求 91 private void customPost() { 92 HashMap<String, String> hashMap = new HashMap<>(); 93 hashMap.put("key", "person"); 94 VolleyRequest.RequestPost(this, BASE_URL, "CustomRequestPost", hashMap, new VolleyInterface(this, VolleyInterface.listener, VolleyInterface.errorListener) { 95 @Override 96 public void onSuccess(String response) { 97 addTextToResult("-->使用VolleyRequest用POST方式获取JSON数据\n"); 98 manageResponse(response); 99 } 100 101 @Override 102 public void onError(VolleyError error) { 103 addTextToResult(error.toString()); 104 } 105 }); 106 } 107 108 // 将参数中的文本添加到界面上的TextView中(在原文本的基础上添加) 109 private void addTextToResult(String text) { 110 String currentText = result.getText().toString(); 111 currentText += text; 112 result.setText(currentText); 113 } 114 115 // 将使用Volley获取到的服务端JSON数据进行解析后添加到结果TextView中 116 private void manageResponse(String response) { 117 try { 118 JSONObject jsonObject = new JSONObject(response).getJSONObject("person"); 119 addTextToResult("姓名:" + jsonObject.getString("name") + "\n年龄:" + jsonObject.getInt("age") + "\n地址:" + 120 jsonObject.getString("address") + "\n\n"); 121 } catch (Exception e) { 122 e.printStackTrace(); 123 } 124 } 125 126 @Override 127 protected void onStop() { 128 // 结束Activity的同时销毁全局请求队列中的所有请求 129 super.onStop(); 130 MyApplication.getRequestQueue().cancelAll("StringRequestGet"); 131 MyApplication.getRequestQueue().cancelAll("StringRequestPost"); 132 MyApplication.getRequestQueue().cancelAll("CustomRequestGet"); 133 MyApplication.getRequestQueue().cancelAll("CustomRequestPost"); 134 } 135 }
运行结果如下图所示:
六、Volley获取网络图片
Volley获取网络图片有三种方式。第一种是使用ImageRequest获取网络图片,加载到Android自带的ImageView中;第二种是使用ImageLoader结合图片缓存获取网络图片,加载到Android自带的ImageView中;第三种是使用ImageLoader结合图片缓存获取网络图片,加载到Volley中提供的NetworkImageView中。下面是将这三种方法在同一个Activity中演示的代码:
1 public class MainActivity extends Activity { 2 private static final String IMAGE_URL = "http://www.baidu.com/img/bdlogo.png"; 3 private ImageView image, image2; 4 private NetworkImageView image3; 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.content_main); 10 image = (ImageView) findViewById(R.id.image); 11 image2 = (ImageView) findViewById(R.id.image2); 12 image3 = (NetworkImageView) findViewById(R.id.image3); 13 14 // Volley加载网络图片 15 volleyImageLoad(); 16 volleyImageCache(); 17 volleyNetworkImageLoad(); 18 } 19 20 // 使用ImageRequest获取网络图片,加载到Android自带的ImageView中 21 private void volleyImageLoad() { 22 ImageRequest request = new ImageRequest(IMAGE_URL, new Response.Listener<Bitmap>() { 23 @Override 24 public void onResponse(Bitmap bitmap) { 25 image.setImageBitmap(bitmap); 26 } 27 }, 100, 100, Bitmap.Config.RGB_565, new Response.ErrorListener() { 28 @Override 29 public void onErrorResponse(VolleyError volleyError) { 30 image.setImageResource(R.mipmap.ic_launcher); 31 } 32 }); 33 request.setTag("ImageRequest"); 34 MyApplication.getRequestQueue().add(request); 35 } 36 37 // 使用ImageLoader结合图片缓存获取网络图片,加载到Android自带的ImageView中 38 private void volleyImageCache() { 39 ImageLoader imageLoader = new ImageLoader(MyApplication.getRequestQueue(), new BitmapCache()); 40 ImageLoader.ImageListener listener = imageLoader.getImageListener(image2, R.mipmap.ic_launcher, R.mipmap.ic_launcher); 41 imageLoader.get(IMAGE_URL, listener); 42 } 43 44 // 使用ImageLoader结合图片缓存获取网络图片,加载到Volley中提供的NetworkImageView中 45 private void volleyNetworkImageLoad() { 46 ImageLoader imageLoader = new ImageLoader(MyApplication.getRequestQueue(), new BitmapCache()); 47 image3.setDefaultImageResId(R.mipmap.ic_launcher); 48 image3.setErrorImageResId(R.mipmap.ic_launcher); 49 image3.setImageUrl(IMAGE_URL, imageLoader); 50 } 51 52 @Override 53 protected void onStop() { 54 // 结束Activity的同时销毁全局请求队列中的所有请求 55 super.onStop(); 56 MyApplication.getRequestQueue().cancelAll("ImageRequest"); 57 } 58 }
1 public class BitmapCache implements ImageLoader.ImageCache { 2 public LruCache<String, Bitmap> cache; 3 public int maxCacheLength = 10 * 1024 * 1024; 4 5 @SuppressLint("NewApi") 6 public BitmapCache() { 7 cache = new LruCache<String, Bitmap>(maxCacheLength) { 8 @Override 9 protected int sizeOf(String key, Bitmap value) { 10 return value.getRowBytes() * value.getHeight(); 11 } 12 }; 13 } 14 15 @SuppressLint("NewApi") 16 @Override 17 public Bitmap getBitmap(String key) { 18 return cache.get(key); 19 } 20 21 @SuppressLint("NewApi") 22 @Override 23 public void putBitmap(String key, Bitmap value) { 24 cache.put(key, value); 25 } 26 }
运行结果如下图所示:
注:第一张图是模糊的,可见用ImageRequest获取到的图片质量不如后两种方法。
最后附上 Volley用到的JAR包