Volley
引子
Σ(っ °Д °;)っ重要的话终究还是讲了三遍...Android主要有三种形式实现网络编程
传送门👇
HttpURLConnection:https://blog.csdn.net/nishigesb123/article/details/89328097
Apache HTTP Client:https://blog.csdn.net/nishigesb123/article/details/89352227
前文已经提到了两种,当然今天并不是要讲最后的那种,而是介绍一个优秀的网络通信框架——Volley,学习了HttpURLConnection和Apache HTTP Client之后,虽然已经能够实现大部分网络编程的需求,但是显然它们还是有些复杂,而它们的使用率也是居高不下,重复使用比比皆是。为了解决这个矛盾,就有了Volley(当然不只是Volley还有其他的)试图去简化网络通信操作。
概述
- Volley是在2013年Google I/O大会上推出来一个网络通信框架。
- Volley除了上面提到的“简单易用”之外,在性能方面也比较出色。
- 它的设计目标:去处理数据量不大,但通信频繁的网络操作。
- 它的缺点:对于大数据量的网络操作,比如下载文件,Volley的表现就会非常糟糕。
功能
- JSON,图像等的异步下载
- 网络请求的排序(scheduling)
- 网络请求的优先级处理
- 缓存
- 多级别取消请求
- 和Activity和生命周期的联动(Activity结束时同时取消所有网络请求)
下载
项目地址:https://github.com/google/volley
git克隆:
git clone https://android.googlesource.com/platform/frameworks/volley
mvnrepository上也可以找到,算曲线救国嘛?
https://mvnrepository.com/artifact/com.mcxiaoke.volley/library
然后Android Studio并不需要特地去搞到Jar...
之前文章提到了通过Android Studio直接添加Gson依赖的步骤,步骤就不重复了,可以看到volley也是可以搜的到的
StringRequest
RequestQueue
RequestQueue是一个请求队列对象。
创建RequestQueue对象:
RequestQueue mQueue = Volley.newRequestQueue(context);
- 它可以缓存所有的HTTP请求, 然后按照一定的算法并发地发出这些请求。
- RequestQueue内部的设计就是非常合适高并发的
- 我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的
- 基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。
出处:https://blog.csdn.net/guolin_blog/article/details/17482095(包括👆许多内容的出处)
要发出一条HTTP请求,需要创建一个StringRequest对象:
发送一个字符串的请求
StringRequest stringRequest = new StringRequest('http://ww.baidu.com,new Response.Listener<String>() {
public void onResponse(String response) { Log.d("TAG", responsel);}},
new Response.Errorlistener() {
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
};
发出一条POST请求
StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorlistener){
protected Map getParams() throws AuthFailureError {
Map<String,String> map = new HashMap<String,String>();
map.put("params1", "value1");
map.put("params2", "value2");
return map;
}
};
StringRequest的构造函数需要传入三个参数:
- 目标服务器的URL地址
- 服务器响应成功的回调
- 服务器响应失败的回调
将StringRequest对象添加到RequestQueue
mQueue.add(stringRequest);
涉及网络,所以也需要必要的网络权限。
<uses-permission android:name="android.permission.INTERNET"/>
GET方式案例
需要一个按钮,配置对应点击事件
package com.example.a4_16volley;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
public class MainActivity extends AppCompatActivity {
RequestQueue queue=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建一个请求队列
queue = Volley.newRequestQueue(this);
}
//发送一个字符串的请求
public void StringRequest(View view){
String url="https://blog.csdn.net";
//创建一个字符串请求 参数(请求方式,URL,响应的回调接口,错误的回调接口)
StringRequest request=new StringRequest(Request.Method.GET,
url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
}
效果如👇
输出信息:
POST方式案例
包括部分上面案例的代码,同样需要一个按钮。
package com.example.a4_16volley;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
RequestQueue queue=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建一个请求队列
queue = Volley.newRequestQueue(this);
}
//发送一个字符串的请求
public void StringRequest(View view){
String url="https://blog.csdn.net";
//创建一个字符串请求 参数(请求方式,URL,响应的回调接口,错误的回调接口)
StringRequest request=new StringRequest(Request.Method.GET,
url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
//发送一个带参数的POST请求
public void sendParansPostString(View view){
//需要准备一个服务器...
String url = "http://10.0.2.2:8080/contact/android";
StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
}){
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> params=new HashMap<>();
params.put("username","admin");
params.put("password","admin");
return params;
}
};
queue.add(request);
}
}
效果如👇
客户端:
服务器端:
JsonRequest
如果接到的数据的Json,可以用JsonRequest去创建一个请求。
- 类似于StringRequest,JsonRequest也是继承自Request类的。
- JsonRequest是一个抽象类,无法直接创建它的实例
- 但JsonRequest有两个直接的子类JsonObjectRequest和JsonArrayRequest。
这部分没有合适的API,就不演示了,代码中示范的API数据实际上是string类型的...
参考代码:
//发送一个JsonRequest
public void sendJsonRequest(View view) {
//毒霸天气的API,最后的数字0表示本地
String url = "http://weather.123.duba.net/static/weather_info/0.html";
/***
* //请求参数对象封装为为JSONObject 放在JsonObjectRequest的jsonRequest对象,就是下面null的位置
* // JSONObject jsonObject=new JSONObject();
* // try {
* // jsonObject.put("param","value");
* // } catch (JSONException e) {
* // e.printStackTrace();
* // }
*/
//参数(请求方式,URL,请求参数,响应的回调接口,错误的回调接口)
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
ImageRequest
ImageRequest是一个图片请求对象,它继承自Request<Bitmap>,所以请求得到的结果是一个bitmap。
步骤类似的
-
创建RequestQueue对象。
-
创建ImageRequest对象。
-
将ImageRequest对象添加到RequestQueue里面。
ImageRequest的构造函数接收六个参数:
第一个参数就是图片的URL地址,这个没什么需要解释的。
第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。
第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。
第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。
第六个参数是图片请求失败的回调。
出处:https://blog.csdn.net/UUUUUltraman/article/details/89329891#t3
案例
需要注意的是,为了显示测试效果,ImageRequest搭配了一个ImageView,请读者自行添加并注册组件。
//发送一个ImageRequest
public void sendImageRequest(View view){
String url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
ImageRequest request=new ImageRequest(url, new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
imageView.setImageBitmap(response);
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
效果如👇 (用黑洞会被告侵权吗 {{{(>_<)}}} 233
ImageLoader
除了ImageRequest,Volley还内置了一种加载网络图片的实现办法——ImageLoader
- ImageLoader的内部也是使用ImageRequest来实现的,但ImageLoader不再是继承自Request
- ImageLoader要比ImageRequest更加高效,不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。
使用步骤稍有不同:
基本案例
- 首先,RequestQueue当然还是要的(但是实际上是作为ImageLoader的参数)
- 然后创建一个ImageLoader对象这个差不多,也不难理解
- 有差别的地方在于需要一个ImageListener对象
- 最后也不需要再queue.add,而是通过ImageLoader的get()方法加载图片。
//使用ImageLoader加载网络图片
public void ImageLoader(View view){
String url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
//新建一个ImageLoader对象
ImageLoader imageLoader=new ImageLoader(queue, new ImageLoader.ImageCache() {
//对图片进行缓存
@Override
public Bitmap getBitmap(String url) {
return null;
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
});
//获取一个ImageListener对象
//getImageListener()方法接收三个参数
// 第一个参数指定用于显示图片的ImageView控件
// 第二个参数指定加载图片的过程中显示的图片
// 第三个参数指定加载图片失败的情况下显示的图片
ImageLoader.ImageListener listener=ImageLoader.getImageListener(imageView,R.mipmap.ic_launcher,R.mipmap.ic_launcher);
//加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
imageLoader.get(url,listener,500,500);
}
效果如下👇 (ImageView共享的上一个案例的,URL也没有改)
点击后👉
实现一个带缓存的ImageCache
前文提到ImageLoader要比ImageRequest更加高效,上面的案例无法体现ImageLoader的优越性。
对代码进行一定的修改,我们自己写一个BitmapCache
//使用ImageLoader加载网络图片
public void ImageLoader(View view) {
String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
//新建一个ImageLoader对象
ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
//获取一个ImageListener对象
//getImageListener()方法接收三个参数
// 第一个参数指定用于显示图片的ImageView控件
// 第二个参数指定加载图片的过程中显示的图片
// 第三个参数指定加载图片失败的情况下显示的图片
ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
//加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
imageLoader.get(url, listener, 500, 500);
}
//创建BitmapCache实现ImageCache接口
private class BitmapCache implements ImageLoader.ImageCache {
private LruCache<String, Bitmap> cache;
//最大缓存大小
private int maxCache = 10 * 1024;
public BitmapCache() {
cache = new LruCache(maxCache);
}
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
}
效果的话,和👆的案例是一样的,就不再演示截图了。
主要是性能上的优化(第一次网络加载,第二次及以后读取缓存,而上一个案例,每点击一次都会进行加载)
NetworkImageView
万万没想到,Volley加载图片还有一种方式(Volley是有多喜欢加载图片...)——NetworkImageView
- NetworkImageView是一个自定义控件
- NetworkImageView继承自ImageView的
- NetworkImageView具备ImageView控件的所有功能
- NetworkImageView在ImageView的基础之上加入了加载网络图片的功能
- NetworkImageView能够在内部自动的完成图片压缩工作,因此在内存占用方面是最优的。
实现步骤
既然是类似ImageView的组件,一开始的配置自然和ImageView差不多
<com.android.volley.toolbox.NetworkImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/networkImageView"
app:layout_constraintTop_toBottomOf="@id/ImageLoader"
/>
布局文件配置完毕后需要注册组件,👇下面两句放哪里就不用多说了吧
private NetworkImageView networkImageView;
networkImageView = findViewById(R.id.networkImageView);
一般来讲然后直接在下面接上相关设置语句就算是配置完成了,优雅一点可以封装成一个方法
networkImageView.setDefaultImageResId(默认的);
networkImageView.setErrorImageResId(失败的);
networkImageView.setImageUrl("yoururl",imageLoader);
然后参数需要一个,imageLoader的话可以参考上面的案例的相关内容
本文完整代码
放一个这篇文章的完整代码,最下面就是networkImageView的代码
package com.example.a4_16volley;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
RequestQueue queue = null;
private ImageView imageView;
private NetworkImageView networkImageView;
private String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建一个请求队列
queue = Volley.newRequestQueue(this);
imageView = findViewById(R.id.imageView);
networkImageView = findViewById(R.id.networkImageView);
networkImageView();
}
//发送一个字符串的请求
public void StringRequest(View view) {
String url = "https://blog.csdn.net";
//创建一个字符串请求 参数(请求方式,URL,响应的回调接口,错误的回调接口)
StringRequest request = new StringRequest(Request.Method.GET,
url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
//发送一个带参数的POST请求
public void sendParansPostString(View view) {
//需要准备一个服务器...
String url = "http://10.0.2.2:8080/contact/android";
StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
}) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> params = new HashMap<>();
params.put("username", "admin");
params.put("password", "admin");
return params;
}
};
queue.add(request);
}
//发送一个JsonRequest
public void sendJsonRequest(View view) {
//毒霸天气的API,最后的数字0表示本地
String url = "http://weather.123.duba.net/static/weather_info/0.html";
/***
* //请求参数对象封装为为JSONObject 放在JsonObjectRequest的jsonRequest对象,就是下面null的位置
* // JSONObject jsonObject=new JSONObject();
* // try {
* // jsonObject.put("param","value");
* // } catch (JSONException e) {
* // e.printStackTrace();
* // }
*/
//参数(请求方式,URL,请求参数,响应的回调接口,错误的回调接口)
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
System.out.println(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
//发送一个ImageRequest
public void sendImageRequest(View view) {
ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
imageView.setImageBitmap(response);
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println(error);
}
});
queue.add(request);
}
/* //使用ImageLoader加载网络图片
public void ImageLoader(View view) {
String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555568604477&di=68c94aa34a2971979975c8c193869a2d&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F8511061308%2F641";
//新建一个ImageLoader对象
ImageLoader imageLoader = new ImageLoader(queue, new ImageLoader.ImageCache() {
//对图片进行缓存
@Override
public Bitmap getBitmap(String url) {
return null;
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
});
//获取一个ImageListener对象
//getImageListener()方法接收三个参数
// 第一个参数指定用于显示图片的ImageView控件
// 第二个参数指定加载图片的过程中显示的图片
// 第三个参数指定加载图片失败的情况下显示的图片
ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
//加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
imageLoader.get(url, listener, 500, 500);
}*/
//使用ImageLoader加载网络图片
public void ImageLoader(View view) {
//新建一个ImageLoader对象
ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());
//获取一个ImageListener对象
//getImageListener()方法接收三个参数
// 第一个参数指定用于显示图片的ImageView控件
// 第二个参数指定加载图片的过程中显示的图片
// 第三个参数指定加载图片失败的情况下显示的图片
ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
//加载图片,参数1为url地址,参数2为ImageListener对象,参数3、4最大宽高
imageLoader.get(url, listener, 500, 500);
}
//创建BitmapCache实现ImageCache接口
private class BitmapCache implements ImageLoader.ImageCache {
private LruCache<String, Bitmap> cache;
//最大缓存大小
private int maxCache = 10 * 1024;
public BitmapCache() {
cache = new LruCache(maxCache);
}
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
}
//networkImageView
private void networkImageView() {
networkImageView.setDefaultImageResId(R.mipmap.ic_launcher);
networkImageView.setErrorImageResId(R.mipmap.ic_launcher);
networkImageView.setImageUrl(url,new ImageLoader(queue,new BitmapCache()));
}
}
效果的话如👇,因为启动Activity就调用了,所以会直接在对应位置显示图片