Volley以及用Volley实现照片墙

郭霖的volley完全解析


Volley加载图片实现了两级缓存(网络缓存、文件缓存),没有实现内存的缓存。Volley已经把各种异步任务、图片采样都封装好了。内存缓存使用lrucache类实现,需要我们手动添加进去。没有使用软引用缓存。因为4.0之后的android系统已经不推荐使用软引用缓存了。

2、volley的总体设计

这里写图片描述

这里写图片描述

3、volley可以做什么

JSON,图像等的异步下载; 
处理get、post等网络请求; 
网络请求的排序(scheduling); 
网络请求的优先级处理; 
缓存; 
多级别取消请求; 
和Activity和生命周期的联动(Activity结束时同时取消所有网络请求); 
等等。

它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。

二、图片的三级缓存在volley中的实现

  其实volley可以完全取代我们手写的三级缓存,因为google已经对volley进行了非常好的封装

1、volley的推荐用法-单例模式

  使用volley时,我们推荐把volley的使用封装成单例使用。在application中初始化它。

2、内存缓存

  只有加载图片的时候才会有内存缓存,对于字符串一般都是之后文件缓存。

  google并没有自动的帮我们实现内存的缓存,需要我们自己手动加入进去。内存缓存在单例类中已经体现了(即LruCache),以后我们每次使用的时候都不必再加入内存缓存。LruCache这个类是Android3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。

3、文件缓存

  volley已经默认帮我们实现了文件的缓存。我们通过源代码看一下:

<code class="hljs java has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">    <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
     * Constructs a new ImageLoader.
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> queue The RequestQueue to use for making image requests.
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> imageCache The cache to use as an L1 cache.
     */</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-title" style="box-sizing: border-box;">ImageLoader</span>(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

  以上代码是imageloader中的一段代码,从中我们可以看到在构造imageloader时,我们已经默认的建立了一个L1级的缓存(文件缓存)。

那volley缓存下来的文件到底在哪呢?见下图:

这里写图片描述

  具体的位置就在如图所示的位置。即data/data/应用程序的包名/volley,如果没有修改volley的缓存位置,默认名字叫volley。

4、图片的二次采样的问题

  其实volley默认的已经帮我们做了图片的二次采样,只是需要我们在进行请求的时候,多加入两个参数。我们一般都忽略了这个问题,最后导致的是不断的OOM。

<code class="hljs java has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">    <span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
     * 这是访问网络图片的核心方法
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> requestUrl
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> imageListener
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> maxWidth
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> maxHeight
     *<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @return</span>
     */</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> ImageContainer <span class="hljs-title" style="box-sizing: border-box;">get</span>(String requestUrl, ImageListener imageListener,
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> maxWidth, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> maxHeight) {
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>
<code class="hljs java has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">        Request<?> newRequest =
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ImageRequest(requestUrl, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Listener<Bitmap>() {
                <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onResponse</span>(Bitmap response) {
                    onGetImageSuccess(cacheKey, response);
                }
            }, maxWidth, maxHeight,
            Config.RGB_565, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> ErrorListener() {
                <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onErrorResponse</span>(VolleyError error) {
                    onGetImageError(cacheKey, error);
                }
            });

        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> BatchedImageRequest(newRequest, imageContainer));
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> imageContainer;
    }
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>

  以上代码是imageloader的核心方法,其中有两个参数是maxWidth,maxHeight,我们一般都忽略了这两个参数。默认值是0和0,这样二次采样算法就不起作用了,我们得到的图片是从服务器1:1哪来的。但是一般我们设置了这两个参数,就可以得到我们希望的缩小比例的图片。这样就可以完全的避免OOM。


1. Volley简介

我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据。Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高。

不过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的表现就会非常糟糕。


1. ImageRequest的用法

前面我们已经学习过了StringRequestJsonRequest的用法,并且总结出了它们的用法都是非常类似的,基本就是进行以下三步操作即可:

1. 创建一个RequestQueue对象。

2. 创建一个Request对象。

3. 将Request对象添加到RequestQueue里面。

其中,StringRequest和JsonRequest都是继承自Request的,所以它们的用法才会如此类似。那么不用多说,今天我们要学习的ImageRequest,相信你从名字上就已经猜出来了,它也是继承自Request的,因此它的用法也是基本相同的,首先需要获取到一个RequestQueue对象,可以调用如下方法获取到:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. RequestQueue mQueue = Volley.newRequestQueue(context);  
接下来自然要去new出一个ImageRequest对象了,代码如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ImageRequest imageRequest = new ImageRequest(  
  2.         "http://developer.android.com/images/home/aw_dac.png",  
  3.         new Response.Listener<Bitmap>() {  
  4.             @Override  
  5.             public void onResponse(Bitmap response) {  
  6.                 imageView.setImageBitmap(response);  
  7.             }  
  8.         }, 00, Config.RGB_565, new Response.ErrorListener() {  
  9.             @Override  
  10.             public void onErrorResponse(VolleyError error) {  
  11.                 imageView.setImageResource(R.drawable.default_image);  
  12.             }  
  13.         });  
可以看到,ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址,这个没什么需要解释的。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。

最后将这个ImageRequest对象添加到RequestQueue里就可以了,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. mQueue.add(imageRequest);  


2. ImageLoader的用法

如果你觉得ImageRequest已经非常好用了,那我只能说你太容易满足了 ^_^。实际上,Volley在请求网络图片方面可以做到的还远远不止这些,而ImageLoader就是一个很好的例子。ImageLoader也可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。

由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步:

1. 创建一个RequestQueue对象。

2. 创建一个ImageLoader对象。

3. 获取一个ImageListener对象。

4. 调用ImageLoader的get()方法加载网络上的图片。

下面我们就来按照这个步骤,学习一下ImageLoader的用法吧。首先第一步的创建RequestQueue对象我们已经写过很多遍了,相信已经不用再重复介绍了,那么就从第二步开始学习吧,新建一个ImageLoader对象,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {  
  2.     @Override  
  3.     public void putBitmap(String url, Bitmap bitmap) {  
  4.     }  
  5.   
  6.     @Override  
  7.     public Bitmap getBitmap(String url) {  
  8.         return null;  
  9.     }  
  10. });  
可以看到,ImageLoader的构造函数接收两个参数,第一个参数就是RequestQueue对象,第二个参数是一个ImageCache对象,这里我们先new出一个空的ImageCache的实现即可。

接下来需要获取一个ImageListener对象,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ImageListener listener = ImageLoader.getImageListener(imageView,  
  2.         R.drawable.default_image, R.drawable.failed_image);  
我们通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对象,getImageListener()方法接收三个参数,第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示的图片,第三个参数指定加载图片失败的情况下显示的图片。

最后,调用ImageLoader的get()方法来加载图片,代码如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);  

get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",  
  2.                 listener, 200200);  

现在运行一下程序并开始加载图片,你将看到ImageView中会先显示一张默认的图片,等到网络上的图片加载完成后,ImageView则会自动显示该图,效果如下图所示。


虽然现在我们已经掌握了ImageLoader的用法,但是刚才介绍的ImageLoader的优点却还没有使用到。为什么呢?因为这里创建的ImageCache对象是一个空的实现,完全没能起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想要写一个性能非常好的ImageCache,最好就要借助Android提供的LruCache功能了,如果你对LruCache还不了解,可以参考我之前的一篇博客Android高效加载大图、多图解决方案,有效避免程序OOM

这里我们新建一个BitmapCache并实现了ImageCache接口,如下所示:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class BitmapCache implements ImageCache {  
  2.   
  3.     private LruCache<String, Bitmap> mCache;  
  4.   
  5.     public BitmapCache() {  
  6.         int maxSize = 10 * 1024 * 1024;  
  7.         mCache = new LruCache<String, Bitmap>(maxSize) {  
  8.             @Override  
  9.             protected int sizeOf(String key, Bitmap bitmap) {  
  10.                 return bitmap.getRowBytes() * bitmap.getHeight();  
  11.             }  
  12.         };  
  13.     }  
  14.   
  15.     @Override  
  16.     public Bitmap getBitmap(String url) {  
  17.         return mCache.get(url);  
  18.     }  
  19.   
  20.     @Override  
  21.     public void putBitmap(String url, Bitmap bitmap) {  
  22.         mCache.put(url, bitmap);  
  23.     }  
  24.   
  25. }  
可以看到,这里我们将缓存图片的大小设置为10M。接着修改创建ImageLoader实例的代码,第二个参数传入BitmapCache的实例,如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());  
这样我们就把ImageLoader的功能优势充分利用起来了。


3. NetworkImageView的用法

除了以上两种方式之外,Volley还提供了第三种方式来加载网络图片,即使用NetworkImageView。不同于以上两种方式,NetworkImageView是一个自定义控制,它是继承自ImageView的,具备ImageView控件的所有功能,并且在原生的基础之上加入了加载网络图片的功能。NetworkImageView控件的用法要比前两种方式更加简单,大致可以分为以下五步:

1. 创建一个RequestQueue对象。

2. 创建一个ImageLoader对象。

3. 在布局文件中添加一个NetworkImageView控件。

4. 在代码中获取该控件的实例。

5. 设置要加载的图片地址。

其中,第一第二步和ImageLoader的用法是完全一样的,因此这里我们就从第三步开始学习了。首先修改布局文件中的代码,在里面加入NetworkImageView控件,如下所示:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="fill_parent"  
  4.     android:orientation="vertical" >  
  5.   
  6.     <Button  
  7.         android:id="@+id/button"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="Send Request" />  
  11.       
  12.     <com.android.volley.toolbox.NetworkImageView   
  13.         android:id="@+id/network_image_view"  
  14.         android:layout_width="200dp"  
  15.         android:layout_height="200dp"  
  16.         android:layout_gravity="center_horizontal"  
  17.         />  
  18.   
  19. </LinearLayout>  
接着在Activity获取到这个控件的实例,这就非常简单了,代码如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);  
得到了NetworkImageView控件的实例之后,我们可以调用它的setDefaultImageResId()方法、setErrorImageResId()方法和setImageUrl()方法来分别设置加载中显示的图片,加载失败时显示的图片,以及目标图片的URL地址,如下所示:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. networkImageView.setDefaultImageResId(R.drawable.default_image);  
  2. networkImageView.setErrorImageResId(R.drawable.failed_image);  
  3. networkImageView.setImageUrl("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",  
  4.                 imageLoader);  
其中,setImageUrl()方法接收两个参数,第一个参数用于指定图片的URL地址,第二个参数则是前面创建好的ImageLoader对象。

好了,就是这么简单,现在重新运行一下程序,你将看到和使用ImageLoader来加载图片一模一样的效果,这里我就不再截图了。

这时有的朋友可能就会问了,使用ImageRequest和ImageLoader这两种方式来加载网络图片,都可以传入一个最大宽度和高度的参数来对图片进行压缩,而NetworkImageView中则完全没有提供设置最大宽度和高度的方法,那么是不是使用NetworkImageView来加载的图片都不会进行压缩呢?

其实并不是这样的,NetworkImageView并不需要提供任何设置最大宽高的方法也能够对加载的图片进行压缩。这是由于NetworkImageView是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心,NetworkImageView会始终呈现给我们一张大小刚刚好的网络图片,不会多占用任何一点内存,这也是NetworkImageView最简单好用的一点吧

当然了,如果你不想对图片进行压缩的话,其实也很简单,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。

这样我们就把使用Volley来加载网络图片的用法都学习完了,今天的讲解也就到此为止,下一篇文章中我会带大家继续探究Volley的更多功能。感兴趣的朋友请继续阅读Android Volley完全解析(三),定制自己的Request。---->只有JSON、String和Image的Request,所以可以自定义一个XML的。



Volley框架可以用来请求String,XML,Json,Image以及一些自定义的类型,这篇文章主要讲解使用Volley请求大量图片,并使用GridView展示出来,这个功能在很多应用中都会用到,如PPS等类型的播放器,淘宝等等。

像这类应用无非就是要解决一下问题:

1、避免OOM,在使用GridView等控件显示大量图片时,如果对缓存处理不当,是非常容易出现OOM的。
2、GridView错位显示,比如GridView中的某个ImageView等待某一个图片(没有返回),然而你此时又滑动了GridView,那么此时的GridView中的ImageView需要的是另外一张图片,如果你之前的图片已经返回回来是不能显示出来的。
把这两个问题解决了,此类问题也就差不多了,剩下的就是效率问题了,这点Volley已经处理好了,我们需要处理的就是以上两点


1、解决OOM
我就顺便讲讲解决OOM的常用策略吧
OOM出现的情况可能是显示的图片非常大但是数量不是很多,也有可能是显示的图片非常多但是图片不是很大,我们先看第一种情况
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
我们直接看官网怎么处理这个问题,我翻译了一下

对于某些图片,分辨率往往比我们需要的高,比如我们使用Galley控件展示我们使用手机拍摄的照片时,这些照片的分辨率都比我们的屏幕分辨率高,所以在显示的时候我们应该降低这些图片的分辨率,因为此时高分辨率的图片在视觉上不会给我们带来任何差别,反而会占用我们移动设备的珍贵内存。下面我们就学习如何将高分辨率的图片降低它的分辨率

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

我们在使用BitmapFactory读取图片数据的时候,如果我们把options.inJustDecodeBounds设置为true,那么BitmapFactory只会读取图片的高度和宽度等信息,不会将图片数据读入内存

options还有一个属性options.inSampleSize,BitmapFactory在解析图片数据的时候,会将图片的宽度和高度变为原来的1/inSampleSize,这样图片大大小就会变为1/(inSampleSize*inSampleSize),比如inSampleSize=2 那么图片大小会变为原来的1/4
那么inSampleSize这个值怎么获取呢,通过以下算法即可

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.     optiosns 就是上面我得到的options 
  3.     reqWidth 就是我们需要图片的宽度 
  4.     reqHeight 就是我们需要图片的高度 
  5. */  
  6. public static int calculateInSampleSize(  
  7.             BitmapFactory.Options options, int reqWidth, int reqHeight) {  
  8.     // Raw height and width of image  
  9.     final int height = options.outHeight;  
  10.     final int width = options.outWidth;  
  11.     int inSampleSize = 1;  
  12.   
  13.     if (height > reqHeight || width > reqWidth) {  
  14.   
  15.         final int halfHeight = height / 2;  
  16.         final int halfWidth = width / 2;  
  17.   
  18.         // Calculate the largest inSampleSize value that is a power of 2 and keeps both  
  19.         // height and width larger than the requested height and width.  
  20.         while ((halfHeight / inSampleSize) > reqHeight  
  21.                 && (halfWidth / inSampleSize) > reqWidth) {  
  22.             inSampleSize *= 2;  
  23.         }  
  24.     }  
  25.   
  26.     return inSampleSize;  
  27. }  

解析图片至需要的宽度和高度

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
  2.         int reqWidth, int reqHeight) {  
  3.   
  4.     // First decode with inJustDecodeBounds=true to check dimensions  
  5.     final BitmapFactory.Options options = new BitmapFactory.Options();  
  6.     //首先将这个值设置为true, 所以只会获取高度和宽度  
  7.     options.inJustDecodeBounds = true;  
  8.     BitmapFactory.decodeResource(res, resId, options);  
  9.   
  10.     // 根据需要的宽度和高度  图片的实际高度和宽度  计算缩小比例  
  11.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  12.   
  13.     // 然后将该值设置为false,真正解析图片  
  14.     options.inJustDecodeBounds = false;  
  15.     return BitmapFactory.decodeResource(res, resId, options);  
  16. }  

通过以上的步骤遍可以将图片缩小至某一大小,从而避免OOM


再来讲讲第二种OOM,这种OOM是因为有太多的图片,所以解决方案就是使用内存缓存和磁盘缓存
在Andorid中有两个类
LruCache和DiskLruCache这个两个类分别对应内存缓存和磁盘缓存
具体使用见
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
到此为止OOM的问题就解决了,下面看看错位问题
方案一:
对于每一个请求的URL就设置到ImageView的tag中如imageView.setTag(url).当某一个请求成功返回一张图片时,判断ImageView的tag是否是当前请求的url
如果是的,那么就显示出来,并加入缓存,如果不是的,那么仅仅加入缓存
方案二:
一旦GridView滑动,那么取消所有的请求,当GridView停止时,里面开始请求可以看见的ImageView需要的图片,今天的例子我就是使用第二种方案,需要设置OnScrollListener

好了 我们现在开始写代码吧

先总结一下Volley框架的使用步骤吧
(1) 初始化RequestQueue 
(2) 创建自己的Request
(3) 将Request加入到RequestQueue中,
但是对于图片的请求步骤稍微有点不同,图片的请求涉及到ImageLoader实现的,其实ImageLoader也是通过以上三步实现图片请求的
下面我就在Appliaction中初始化Volley使用的环境吧


[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class VolleyApplication extends Application   
  2. {  
  3.   private static final String TAG = "VolleyApplication";  
  4.   //请求队列  
  5.   private RequestQueue mRequestQueue;  
  6.   private static  VolleyApplication instance;  
  7.   //用于图片请求  
  8.   private ImageLoader mImageLoader;  
  9.   //使用单例模式  
  10.   public static VolleyApplication getInstance()  
  11.   {  
  12.     return instance;  
  13.   }  
  14.     
  15.   public RequestQueue getRequestQueue()  
  16.   {  
  17.     return mRequestQueue;  
  18.   }  
  19.     
  20.   public ImageLoader getImageLoader()  
  21.   {  
  22.     return mImageLoader;  
  23.   }  
  24.   @Override  
  25.   public void onCreate()   
  26.   {  
  27.     super.onCreate();  
  28.     //初始化内存缓存目录  
  29.     File cacheDir = new File(this.getCacheDir(), "volley");  
  30.     /** 
  31.     初始化RequestQueue,其实这里你可以使用Volley.newRequestQueue来创建一个RequestQueue,直接使用构造函数可以定制我们需要的RequestQueue,比如线程池的大小等等 
  32.     */  
  33.     mRequestQueue=new RequestQueue(new DiskBasedCache(cacheDir), new BasicNetwork(new HurlStack()), 3);  
  34.       
  35.     instance=this;  
  36.       
  37.     //初始化图片内存缓存  
  38.     MemoryCache mCache=new MemoryCache();  
  39.     //初始化ImageLoader  
  40.     mImageLoader =new ImageLoader(mRequestQueue,mCache);  
  41.     //如果调用Volley.newRequestQueue,那么下面这句可以不用调用  
  42.     mRequestQueue.start();  
  43.   }  
  44. }  

使用LrcCache实现的一个图片缓存,这个基本上可以通用

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class MemoryCache implements ImageCache   
  2. {  
  3.   private static final String TAG = "MemoryCache";  
  4.   private LruCache<String, Bitmap> mCache;  
  5.     
  6.   public MemoryCache()  
  7.   {  
  8.     //这个取单个应用最大使用内存的1/8  
  9.     int maxSize=(int)Runtime.getRuntime().maxMemory()/8;  
  10.     mCache=new LruCache<String, Bitmap>(maxSize){  
  11.       @Override  
  12.       protected int sizeOf(String key, Bitmap value) {  
  13.       //这个方法一定要重写,不然缓存没有效果  
  14.         return value.getHeight()*value.getRowBytes();  
  15.       }  
  16.     };  
  17.   }  
  18.   
  19.   @Override  
  20.   public Bitmap getBitmap(String key) {  
  21.     return mCache.get(key);  
  22.   }  
  23.   
  24.   @Override  
  25.   public void putBitmap(String key, Bitmap value) {  
  26.     mCache.put(key, value);  
  27.   }  
  28. }  
开始写代码吧

(1) 首先给出Activity的布局吧

[html]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.      <GridView     
  7.         android:id="@+id/grid_image"    
  8.         android:layout_width="match_parent"    
  9.         android:layout_height="wrap_content"    
  10.         android:columnWidth="128dip"    
  11.         android:stretchMode="columnWidth"    
  12.         android:numColumns="2"    
  13.         android:verticalSpacing="1dip"    
  14.         android:horizontalSpacing="1dip"  
  15.         android:gravity="center"    
  16.         ></GridView>   
  17. </LinearLayout>  

(2) 然后每个Item的布局

[html]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    
  3.     xmlns:tools="http://schemas.android.com/tools"    
  4.     android:layout_width="wrap_content"    
  5.     android:layout_height="wrap_content" >    
  6.     
  7.     <ImageView     
  8.         android:id="@+id/photo"    
  9.         android:layout_width="128dip"    
  10.         android:layout_height="128dip"    
  11.         android:src="@drawable/empty_photo"    
  12.         android:layout_centerInParent="true"    
  13.         />    
  14.     
  15. </RelativeLayout>  

(3) Activity代码

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class ImageActivity extends Activity   
  2. {  
  3.   private static final String TAG = "ImageActivity";  
  4.   private GridView mGridView;  
  5.   private ImageAdapter adapter;  
  6.   @Override  
  7.   protected void onCreate(Bundle savedInstanceState) {  
  8.     super.onCreate(savedInstanceState);  
  9.     this.setContentView(R.layout.imagelayout);  
  10.     initViews();  
  11.   }  
  12.     
  13.   private void initViews()  
  14.   {  
  15.     Log.i(TAG,"initViews");  
  16.     mGridView=(GridView)this.findViewById(R.id.grid_image);  
  17.     adapter=new ImageAdapter(this,mGridView);  
  18.     mGridView.setAdapter(adapter);  
  19.       
  20.   }  
  21. }  

(4) 最重要部分代码

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class ImageAdapter extends BaseAdapter implements OnScrollListener  
  2. {  
  3.   private static final String TAG = "ImageAdapter";  
  4.   private Context context;  
  5.   private String[] items=Images.imageThumbUrls;  
  6.   private GridView mGridView;  
  7.     
  8.   /** 
  9.    * 标识是否是第一次执行,如果是第一次执行 onScrollStateChanged是不调用的 
  10.    */  
  11.   private boolean isFirstEnter;  
  12.   /** 
  13.    * 第一个可以看见的item 
  14.    */  
  15.   private int firstSeeItem;  
  16.     
  17.   /** 
  18.    * 记录上一次可以看见的第一个,因为如果已经到顶部,向下滑动GridView也会执行onScrollStateChanged 所以第一个可以见的没有变化,那么就不执行 
  19.    */  
  20.   private int orifirstItem;  
  21.   /** 
  22.    * 可以看见item的总数 
  23.    */  
  24.   private int totalSeeItem;  
  25.     
  26.   public ImageAdapter(Context context,GridView mGridView)  
  27.   {  
  28.     this.context=context;  
  29.     this.mGridView=mGridView;  
  30.     //注册这个是为了在滑动的时候停止下载图片,不然很卡  
  31.     mGridView.setOnScrollListener(this);  
  32.     isFirstEnter=true;  
  33.   }  
  34.   
  35.   @Override  
  36.   public int getCount() {  
  37.     return items.length;  
  38.   }  
  39.   
  40.   @Override  
  41.   public Object getItem(int position) {  
  42.     return items[position];  
  43.   }  
  44.   
  45.   @Override  
  46.   public long getItemId(int position) {  
  47.     return position;  
  48.   }  
  49.   
  50.   @Override  
  51.   public View getView(int position, View convertView, ViewGroup parent) {  
  52.     Log.v(TAG, "imagedown--->getView");  
  53.     ImageView imgView=null;  
  54.     if(convertView==null)  
  55.     {  
  56.       convertView=LayoutInflater.from(context).inflate(R.layout.imageitems, null);  
  57.     }  
  58.     imgView=(ImageView)convertView.findViewById(R.id.photo);  
  59.     imgView.setImageResource(R.drawable.empty_photo);  
  60.     //通过GridView的findViewByTag方法找到该View,防止错位发生  
  61.     imgView.setTag(items[position]);  
  62.       
  63.     return convertView;  
  64.   }  
  65.   
  66.   @Override  
  67.   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)   
  68.   {  
  69.     Log.v(TAG, "imagedown--->onScroll");  
  70.     firstSeeItem=firstVisibleItem;  
  71.     totalSeeItem=visibleItemCount;  
  72.       
  73.     if(isFirstEnter && visibleItemCount>0)  
  74.     {  
  75.       orifirstItem=firstVisibleItem;  
  76.       startLoadImages(firstSeeItem,totalSeeItem);  
  77.       isFirstEnter=false;  
  78.     }  
  79.   }  
  80.   
  81.   @Override  
  82.   public void onScrollStateChanged(AbsListView view, int scrollState)   
  83.   {  
  84.     Log.v(TAG, "imagedown--->onScrollStateChanged");  
  85.     if(orifirstItem!=firstSeeItem)  
  86.     {  
  87.       if(scrollState==SCROLL_STATE_IDLE)  
  88.       {  
  89.         startLoadImages(firstSeeItem,totalSeeItem);  
  90.         orifirstItem=firstSeeItem;  
  91.       }else  
  92.       {  
  93.         ImageUtils.cancelAllImageRequests();  
  94.       }  
  95.     }  
  96.      
  97.       
  98.   }  
  99.   /** 
  100.   开始下载图片 
  101.   first就是第一个可以看见的position 
  102.   total就是可以看见个Item个数 
  103.   */  
  104.   private void startLoadImages(int first,int total)  
  105.   {  
  106.     Log.v(TAG, "imagedown--->startLoadImages,first-->"+first+",total-->"+total);  
  107.     for(int i=first;i<first+total;i++)  
  108.     {  
  109.       ImageUtils.loadImage(items[i], mGridView);  
  110.     }  
  111.   }  
  112. }  

(5) 请求图片工具类

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class ImageUtils  
  2. {  
  3.   private static final String TAG = "ImageUtils";  
  4.     
  5.   public static void loadImage(final String url,final GridView mGridView)  
  6.   {  
  7.         
  8.       ImageLoader imageLoader=VolleyApplication.getInstance().getImageLoader();  
  9.       ImageListener listener=new ImageListener() {  
  10.         ImageView tmpImg=(ImageView)mGridView.findViewWithTag(url);  
  11.         @Override  
  12.         public void onErrorResponse(VolleyError arg0) {  
  13.           //如果出错,则说明都不显示(简单处理),最好准备一张出错图片  
  14.           tmpImg.setImageBitmap(null);  
  15.         }  
  16.           
  17.         @Override  
  18.         public void onResponse(ImageContainer container, boolean arg1) {  
  19.             
  20.           if(container!=null)  
  21.           {  
  22.              tmpImg=(ImageView)mGridView.findViewWithTag(url);  
  23.             if(tmpImg!=null)  
  24.             {  
  25.               if(container.getBitmap()==null)  
  26.               {  
  27.                 tmpImg.setImageResource(R.drawable.empty_photo);  
  28.               }else  
  29.               {  
  30.                 tmpImg.setImageBitmap(container.getBitmap());  
  31.               }  
  32.             }  
  33.           }  
  34.         }  
  35.       };  
  36.       ImageContainer newContainer=imageLoader.get(url, listener,128,128);  
  37.   }  
  38.     
  39.   /** 
  40.    *  取消图片请求 
  41.    */  
  42.   public static void cancelAllImageRequests() {  
  43.     ImageLoader imageLoader = VolleyApplication.getInstance().getImageLoader();  
  44.     RequestQueue requestQueue =   VolleyApplication.getInstance().getRequestQueue();  
  45.     if(imageLoader != null && requestQueue!=null){  
  46.       int num = requestQueue.getSequenceNumber();  
  47.       //这个方法是我自己写的,Volley里面是没有的,所以只能使用我给的Volley.jar才有这个函数  
  48.       imageLoader.drain(num);  
  49.     }  
  50.   }  
代码下载: http://download.csdn.net/detail/yuanzeyao2008/7363999





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值