Volley框架的简单使用(获取网络资源-字符串,json数据与图片,将矩形变为圆形)
来源于郭神:http://blog.csdn.net/guolin_blog/article/details/17482095
HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码。于是乎,一些Android网络通信框架也就应运而生,比如说AsyncHttpClient,它把HTTP所有的通信细节全部封装在了内部,我们只需要简单调用几行代码就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上显示网络图片的操作变得极度简单,开发者不用关心如何从网络上获取图片,也不用关心开启线程、回收图片资源等细节,Universal-Image-Loader已经把一切都做好了。
Android开发团队也是意识到了有必要将HTTP的通信操作再进行简单化,于是在2013年Google I/O大会上推出了一个新的网络通信框架——Volley。Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。除了简单易用之外,Volley在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。
下图所示的这些应用都是属于数据量不大,但网络通信频繁的,因此非常适合使用Volley。
2. 下载Volley
介绍了这么多理论的东西,下面我们就准备开始进行实战了,首先需要将Volley的jar包准备好,如果你的电脑上装有Git,可以使用如下命令下载Volley的源码:
- git clone https://android.googlesource.com/platform/frameworks/volley
下载完成后将它导入到你的Eclipse工程里,然后再导出一个jar包就可以了。如果你的电脑上没有Git,那么也可以直接使用我导出好的jar包,下载地址是:http://download.csdn.net/detail/sinyu890807/7152015 。
新建一个Android项目,将volley.jar文件复制到libs目录下,这样准备工作就算是做好了。
如果数据乱码见:Volley框架的使用--自定义数据的请求方式
public class MainActivity extends Activity { /** * 请求队列,RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象 * 基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。 */ private RequestQueue requestQueue; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestQueue = Volley.newRequestQueue(this);//创建网络请求队列(执行网络连接的) //downloadStringToGet("http://www.baidu.com");//这里不加http://会报空指针异常 //downloadStringToPost("http://www.qubaobei.com/ios/cf/dish_list.php"); //getJsonObjectData("http://www.qubaobei.com/ios/cf/dish_list.php?stage_id=1&page=1&limit=20"); getJsonArrayData("http://app.bilibili.com/promo/ipad3/267/banner.ipad3.json"); } /** * 通过http协议的GET请求,从网络获取字符串形式的数据,步骤: * 1. 创建一个RequestQueue对象。 * 2. 创建一个StringRequest对象。 * 3. 将StringRequest对象添加到RequestQueue里面。 */ public void downloadStringToGet(String url){ //参数说明:1.目标服务器的URL地址,2.服务器响应成功的回调,3.服务器响应失败的回调。 StringRequest stringRequest = new StringRequest(url, new Response.Listener<String>() { @Override public void onResponse(String s) { //主线程中运行的 Log.i("tag", "请求成功,数据:" + s); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { //主线程运行的 Log.i("tag", "请求失败" + volleyError.getMessage(), volleyError);//抛出异常(不会终止程序) } }); //将这个StringRequest对象添加到RequestQueue里面就可以执行网络连接了 requestQueue.add(stringRequest); } /** * 通过http协议的POST请求,从网络获取字符串形式的数据 */ public void downloadStringToPost(String url){ //在上面的基础上增加了参数1指定请求方式,并重写了getParams()方法 StringRequest stringRequest = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() { @Override public void onResponse(String s) { Log.i("tag", "请求成功,数据:" + s); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.i("tag", "请求失败" + volleyError.getMessage(), volleyError);//抛出异常(不会终止程序) } }){ @Override protected Map<String, String> getParams() throws AuthFailureError { //重写此方法来指定Post的数据 Map<String, String> map = new HashMap<String, String>(); map.put("stage_id", "1"); map.put("page", "1"); map.put("limit", "20"); return map; } }; //将这个StringRequest对象添加到RequestQueue里面就可以执行网络连接了 requestQueue.add(stringRequest); } /** * json数据请求,JsonObject数据 */ public void getJsonObjectData(String url){ JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject jsonObject) { Log.i("tag", "请求成功,数据:" + jsonObject); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.i("tag", "请求失败" + volleyError.getMessage(), volleyError);//抛出异常(不会终止程序) } }); requestQueue.add(jsonObjectRequest);//开始执行 } /** * json数据请求,JsonArray数据 */ public void getJsonArrayData(String url){ JsonArrayRequest JsonArrayRequest = new JsonArrayRequest(url, new Response.Listener<JSONArray>() { @Override public void onResponse(JSONArray jsonArray) { Log.i("tag", "请求成功,数据:" + jsonArray); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError volleyError) { Log.i("tag", "请求失败" + volleyError.getMessage(), volleyError);//抛出异常(不会终止程序) } }); requestQueue.add(JsonArrayRequest);//开始执行 } }
================上面介绍的获取字符串资源,下面介绍下载图片:===============
1. ImageRequest的用法
public class MainActivity extends Activity { /** * 请求队列,RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象 * 基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。 */ private RequestQueue requestQueue; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.imageView); requestQueue = Volley.newRequestQueue(this);//创建网络请求队列(执行网络连接的) //下载图片 downloadImageView("http://d.hiphotos.baidu.com/image/h%3D360/sign=e0a211de5eafa40f23c6c8db9b65038c/562c11dfa9ec8a13f075f10cf303918fa1ecc0eb.jpg"); } /** * ImageRequest的参数说明: * 第一个参数就是图片的URL地址。 * 第二个参数是图片请求成功的回调. * 第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。 * 第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。 * 第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。 */ public void downloadImageView(String imgUrl){ ImageRequest imageRequest = new ImageRequest(imgUrl, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { Log.i("tag","图片下载成功"); imageView.setImageBitmap(bitmap); } }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {//另一个重载方法ImageView.ScaleType.设置缩放类型 @Override public void onErrorResponse(VolleyError volleyError) { Log.i("tag","图片下载我失败"+volleyError.getMessage(),volleyError); } }); //将这个imageRequest对象添加到RequestQueue里面就可以执行网络连接了 requestQueue.add(imageRequest); } }
上面都是使用requestQueue.add()方法来开始执行下载任务,而下面的几种方式都是使用imageLoader.get()方法来开始执行下载任务的
2. ImageLoader的用法,内部已经帮我们做好了磁盘缓存,默认为5兆,路径为手机内部存储对应的包名/cache/volley
如果你觉得ImageRequest已经非常好用了,那我只能说你太容易满足了。实际上,Volley在请求网络图片方面可以做到的还远远不止这些,而ImageLoader就是一个很好的例子。ImageLoader也可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。
由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步:
1. 创建一个RequestQueue对象。
2. 创建一个ImageLoader对象。
3. 获取一个ImageListener对象。
4. 调用ImageLoader的get()方法加载网络上的图片。
public class MainActivity extends Activity { /** * 请求队列,RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象 * 基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。 */ private RequestQueue requestQueue; private ImageLoader imageLoader; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.imageView); //第一步:创建网络请求队列 requestQueue = Volley.newRequestQueue(this); //第二步:创建一个ImageLoader对象。参数说明:1.请求队列,2.图片的内存缓存 imageLoader = new ImageLoader(requestQueue,new BitmapCache()); //下载图片 downloadImageView("http://d.hiphotos.baidu.com/image/h%3D360/sign=e0a211de5eafa40f23c6c8db9b65038c/562c11dfa9ec8a13f075f10cf303918fa1ecc0eb.jpg",imageView); } /** * 将网络上的图片下载设置到imageView上 */ public void downloadImageView(String imgUrl,ImageView iamgeView){ //第三步:获取监听,参数说明:1.显示图片的ImageView控件,2.默认显示的图片,3.下载失败显示的图片。 ImageLoader.ImageListener imageListener = ImageLoader.getImageListener(iamgeView, R.mipmap.ic_launcher, R.mipmap.ic_launcher); //第四步:加载图片,类似于上面那几种方式的将请求对象添加到请求队列中 imageLoader.get(imgUrl,imageListener); //如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度 ,将矩形图片变为圆形的重载方法见下面的补充 //imageLoader.get(imgUrl,imageListener,200,200); } /** * 作为ImageLoader缓存的设置 */ public class BitmapCache implements ImageLoader.ImageCache { private LruCache<String, Bitmap> mCache; public BitmapCache() { int maxMemory = (int) (Runtime.getRuntime().maxMemory());//获取应用的最大可用内存 Log.i("TAG", "该应用的最大可用内存为:" + maxMemory/1024 + "KB");//64M,1Kb=1024个字节 //使用最大可用内存值的1/8作为缓存的大小 int cacheSize=maxMemory / 8; mCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight(); } }; } @Override public Bitmap getBitmap(String url) { return mCache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { mCache.put(url, bitmap); } } }
在ListView中使用Volley的ImageLoader加载图片会出现图片错位的问题
错位问题我们都知道是复用convertView并且异步加载图片导致的。
方法一:可以换成使用NetworkImageView去避免
方法二:直接去重写getImageListener方法,使用url加个Tag就可以了
下面直接看重写怎么做,直接去到Volley源码找一下哪里获取bitmap的
可以看到源码里只要不为空就直接设置了,所以导致错位问题
所以在获取监听对象之前设置下tag,然后重写一下加个判断就好了 (自定义类继承ImageLoader,重写getImageListener方法)
3. NetworkImageView的用法
除了以上两种方式之外,Volley还提供了第三种方式来加载网络图片,即使用NetworkImageView。不同于以上两种方式,NetworkImageView是一个自定义控制,它是继承自ImageView的,具备ImageView控件的所有功能,并且在原生的基础之上加入了加载网络图片的功能。NetworkImageView控件的用法要比前两种方式更加简单,大致可以分为以下五步:
1. 创建一个RequestQueue对象。
2. 创建一个ImageLoader对象。
3. 在布局文件中添加一个NetworkImageView控件。
4. 在代码中获取该控件的实例。
5. 设置要加载的图片地址。
其中,第一第二步和ImageLoader的用法是完全一样的,因此这里我们就从第三步开始学习了。首先修改布局文件中的代码,在里面加入NetworkImageView控件,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Request" /> <com.android.volley.toolbox.NetworkImageView android:id="@+id/network_image_view" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center_horizontal" /> </LinearLayout>
接着在Activity获取到这个控件的实例,这就非常简单了,代码如下所示:
networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
得到了NetworkImageView控件的实例之后,我们可以调用它的setDefaultImageResId()方法、setErrorImageResId()方法和setImageUrl()方法来分别设置加载中显示的图片,加载失败时显示的图片,以及目标图片的URL地址,如下所示:
networkImageView.setDefaultImageResId(R.drawable.default_image); //默认显示的图片 networkImageView.setErrorImageResId(R.drawable.failed_image); //下载失败显示的图片 networkImageView.setImageUrl("//img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", imageLoader); //图片地址和图片装载器
其中,setImageUrl()方法接收两个参数,第一个参数用于指定图片的URL地址,第二个参数则是前面创建好的ImageLoader对象。
好了,就是这么简单,现在重新运行一下程序,你将看到和使用ImageLoader来加载图片一模一样的效果
这时有的朋友可能就会问了,使用ImageRequest和ImageLoader这两种方式来加载网络图片,都可以传入一个最大宽度和高度的参数来对图片进行压缩,而NetworkImageView中则完全没有提供设置最大宽度和高度的方法,那么是不是使用NetworkImageView来加载的图片都不会进行压缩呢?
其实并不是这样的,NetworkImageView并不需要提供任何设置最大宽高的方法也能够对加载的图片进行压缩。这是由于NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心,NetworkImageView会始终呈现给我们一张大小刚刚好的网络图片,不会多占用任何一点内存,这也是NetworkImageView最简单好用的一点吧。
当然了,如果你不想对图片进行压缩的话,其实也很简单,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。
取消下载任务:由于volley默认是4个线程,当线程还在等待,当前页面却销毁了的时候,也就需要取消掉正在下载或者等待下载的线程.
stringRequest.setTag("tag");//为请求数据的对象,设置tag //根据tag来取消队列中的线程 requestQueue.cancelAll("tag"); requestQueue.stop();//将请求队列中的线程任务暂停 requestQueue.start();//开始
补充:对于ImageLoader,可以将矩形图片裁剪为圆形图片,
//第四步:加载图片,类似于上面那几种方式的将请求对象添加到请求队列中 上面使用的方法 imageLoader.get(imgUrl,imageListener);
/* (String requestUrl, 图片请求地址 @NotNull ImageLoader.ImageListener imageListener, 请求成功的监听 int maxWidth, 最大宽度 int maxHeight, 最大高度 @NotNull ImageView.ScaleType scaleType, 缩放类型 ImageRequest.Transformation transformation 显示图片前的处理 这里CircleBitmap为将矩形图片变为圆形图片 */ imageLoader.get("url",imageListener,60,60, ImageView.ScaleType.CENTER_CROP,new CircleBitmap());//重载的一个方法
CircleBitmap类如下:
import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import com.android.volley.toolbox.ImageRequest; /** * 将矩形图片变为圆形的工具类 */ public class CircleBitmap implements ImageRequest.Transformation { @Override public Bitmap transform(Bitmap source, int maxWidth, int maxHeight) { int min = source.getHeight()>source.getWidth()?source.getWidth():source.getHeight(); return createCircleImage(source,min); } /** * 创建圆形图片的一个方法 * @param source * @param min * @return */ private Bitmap createCircleImage(Bitmap source, int min) { final Paint paint = new Paint(); paint.setAntiAlias(true); Bitmap target = Bitmap.createBitmap(min, min, Bitmap.Config.ARGB_8888); /** * 产生一个同样大小的画布 */ Canvas canvas = new Canvas(target); /** * 首先绘制圆形 */ canvas.drawCircle(min / 2, min / 2, min / 2, paint); /** * 使用SRC_IN 交互 */ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); /** * 绘制图片 */ canvas.drawBitmap(source, 0, 0, paint); return target; } @Override public String key() { return null; } }