Volley(四)—— ImageLoader & NetworkImageView
ImageLoader是一个加载网络图片的封装类,其内部还是由ImageRequest来实现的。但因为源码中没有提供磁盘缓存的设置,所以咱们还需要去源码中进行修改,让我们可以更加自如的设定是否进行磁盘缓存。
2. ImageLoader的用法
如 果你觉得ImageRequest已经非常好用了,那我只能说你太容易满足了 ^_^。实际上,Volley在请求网络图片方面可以做到的还远远不止这些,而ImageLoader就是一个很好的例子。ImageLoader也可以 用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效, 因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。
由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步:
1. 创建一个RequestQueue对象。
2. 创建一个ImageLoader对象。
3. 获取一个ImageListener对象。
4. 调用ImageLoader的get()方法加载网络上的图片。
下面我们就来按照这个步骤,学习一下ImageLoader的用法吧。首先第一步的创建RequestQueue对象我们已经写过很多遍了,相信已经不用再重复介绍了,那么就从第二步开始学习吧,新建一个ImageLoader对象,代码如下所示:
ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
@Override
public void putBitmap(String url, Bitmap bitmap) { } @Override public Bitmap getBitmap(String url) { return null; } });
可以看到,ImageLoader的构造函数接收两个参数,第一个参数就是RequestQueue对象,第二个参数是一个ImageCache对象,这里我们先new出一个空的ImageCache的实现即可。
接下来需要获取一个ImageListener对象,代码如下所示:
ImageListener listener = ImageLoader.getImageListener(imageView,
R.drawable.default_image, R.drawable.failed_image);
我 们通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对 象,getImageListener()方法接收三个参数,第一个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示 的图片,第三个参数指定加载图片失败的情况下显示的图片。
最后,调用ImageLoader的get()方法来加载图片,代码如下所示:
imageLoader.get("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);
get()方法接收两个参数,第一个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的最大宽度和高度,如下所示:
imageLoader.get("http://img.my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",
listener, 200, 200);
现在运行一下程序并开始加载图片,你将看到ImageView中会先显示一张默认的图片,等到网络上的图片加载完成后,ImageView则会自动显示该图,效果如下图所示。
虽然现在我们已经掌握了ImageLoader的用法,但是刚才介绍的ImageLoader的优点却还没有使用到。为什么呢?因为这里创建的 ImageCache对象是一个空的实现,完全没能起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想要写一个性能非常好的 ImageCache,最好就要借助Android提供的LruCache功能了,如果你对LruCache还不了解,可以参考我之前的一篇博客Android高效加载大图、多图解决方案,有效避免程序OOM。
这里我们新建一个BitmapCache并实现了ImageCache接口,如下所示:
public class BitmapCache implements ImageCache {
private LruCache<String, Bitmap> mCache; public BitmapCache() { int maxSize = 10 * 1024 * 1024; mCache = new LruCache<String, Bitmap>(maxSize) { @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); } }
可以看到,这里我们将缓存图片的大小设置为10M。接着修改创建ImageLoader实例的代码,第二个参数传入BitmapCache的实例,如下所示:
ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());
这样我们就把ImageLoader的功能优势充分利用起来了
一、添加对磁盘缓存的控制
我们默默的打开源码,添加如下代码:
private boolean mShouldCache = true;
/** * Set whether or not responses to this request should be cached(Disk Cache). * * @return This Request object to allow for chaining. */ public void setShouldCache(boolean shouldCache) { mShouldCache = shouldCache; } /** * Returns true if responses to this request should be cached. */ public final boolean shouldCache() { return mShouldCache; }
定位到get方法
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight)
找到初始化Request<Bitmap>的地方。
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread. throwIfNotOnMainThread(); final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); // Try to look up the request in the cache of remote images. Bitmap cachedBitmap = mCache.getBitmap(cacheKey); if (cachedBitmap != null) { // Return the cached bitmap. ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); imageListener.onResponse(container, true); return container; } // The bitmap did not exist in the cache, fetch it! ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener); // Update the caller to let them know that they should use the default bitmap. imageListener.onResponse(imageContainer, true); // Check to see if a request is already in-flight. BatchedImageRequest request = mInFlightRequests.get(cacheKey); if (request != null) { // If it is, add this request to the list of listeners. request.addContainer(imageContainer); return imageContainer; } // The request is not already in flight. Send the new request to the network and // track it. Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey); mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer)); return imageContainer; }
把红色代码中间添加:newRequest.setShouldCache(mShouldCache);最终效果如下:
// The request is not already in flight. Send the new request to the network and
// track it.
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
newRequest.setShouldCache(mShouldCache);
mRequestQueue.add(newRequest);
二、ImageLoader
/**
* Constructs a new ImageLoader.
* @param queue The RequestQueue to use for making image requests.
* @param imageCache The cache to use as an L1 cache. */ public ImageLoader(RequestQueue queue, ImageCache imageCache) { mRequestQueue = queue; mCache = imageCache; }
初始化要传入两个参数:①RequestQueue对象;②ImageCache对象(不能传null!!!!)
RequestQueue这个我就不多说了,之前的文章已经讲解过了,下面来说说ImageCache这个对象。
2.1 建立ImageCache对象来实现内存缓存
ImageCache是一个图片的内存缓存对象,源码中叫做L1缓存,其实缓存分为L1、L2两种,L1就是所谓的内存缓存,将展示过的图片放入内存中进行缓存,L2就是磁盘缓存,如果这个图片下载完成,它可以被存放到磁盘中,在没有网络的时候就可以调出来使用了。
为了简单我先空实现ImageCache接口,产生一个MyImageCache对象
class MyImageCache implements ImageCache {
@Override
public Bitmap getBitmap(String url) {
return null; } @Override public void putBitmap(String url, Bitmap bitmap) { } }
这个接口提供的方法简单明了,今后我们可以用自己的内存缓存来完善这个类,当前getBitmap返回的是null,说明这个内存缓存没啥用处,和没缓存一样。
2.2 实现加载网络图片
ImageLoader imageLoader = new ImageLoader(mQueue, new MyImageCache());
ImageListener listener = ImageLoader.getImageListener(iv, R.drawable.default_photo, R.drawable.error_photo);
imageLoader.setShouldCache(true); imageLoader.get("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", listener);
代码的思路是产生ImageLoader后,再初始化一个监听器,监听器中传入imageview对象,还有默认的图片,出错时展示的图片,这个很好理解。最后在imageLoader的get方法中传入URL,还有监听器对象即可。
值得注意的是,get方法还有一种变体:
imageLoader.get("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", listener, 0 ,0);
这里最后传入的数值是得到图片的最大宽、高,其意义和ImageRequest中的宽、高完全一致,可以参考之前的文章。其实,如果你去源码中找找的话,你会发现这两个参数最终都是传给ImageRequest的,所以在此就不做过多讲解了。
2.3 设置缓存
因为我们一上来就修改了源码,所以当我们在执行get()方法前可以通过setShouldCache(false)来取消磁盘缓存,如果你不进行设置的话默认是执行磁盘缓存的。那么如何配置L1缓存呢?刚刚我们的MyImageCache仅仅是一个空实现,现在就开始来完善它。
我的想法是通过LruCache进行图片缓存,分配的缓存空间是5m。如果对LruCache不是很了解,可以看看我之前的文章:详细解读LruCache类
class MyImageCache implements ImageCache {
private LruCache<String, Bitmap> mCache;
public MyImageCache() { int maxSize = 5 * 1024 * 1024; mCache = new LruCache<String, Bitmap>(maxSize) { @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); } }
每次执行get方法时,Volley会到MyImageCache中调用getBitmap(),看看有没有内存缓存,如果你返回了null,那么Volley就会从网络上下载,如果不为null,Volley会直接把取得的bitmap展示到imageview中。当图片展示到屏幕上后(无论是这个图片是从内存中读的,还是从磁盘中读的,或者是从网络上下载的),Volley都会自动调用putBitmap,把图片放入内存中缓存起来。
说明:缓存的size是:bitmap.getRowBytes() * bitmap.getHeight(),这里getRowBytes()是返回图片每行的字节数,图片的size应该乘以高度。
注意:imageLoader.setShouldCache(false);仅仅是设置了不实用磁盘缓存,和内存缓存没有任何关系。如果你想要不实用内存缓存,请在自定义的ImageCache中进行处理。
2.4 其他方法
public final boolean shouldCache()
查看是否已经做了磁盘缓存。
void setShouldCache(boolean shouldCache)
设置是否运行磁盘缓存,此方法需要在get方法前使用
public boolean isCached(String requestUrl, int maxWidth, int maxHeight)
判断对象是否已经被缓存,传入url,还有图片的最大宽高
public void setBatchedResponseDelay(int newBatchedResponseDelayMs)
Sets the amount of time to wait after the first response arrives before delivering all responses. Batching can be disabled entirely by passing in 0.
设置第一次响应到达后到分发所有响应之前的整体时间,单位ms,如果你设置的时间是0,那么Batching将不可用。
三、NetworkImageView
NetworkImageView继承自ImageView,你可以认为它是一个可以实现加载网络图片的imageview,十分简单好用。这个控件在被从父控件分离的时候,会自动取消网络请求的,即完全不用我们担心相关网络请求的生命周期问题。
3.1 XML
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/network_image_view"
android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center_horizontal" />
3.2 JAVA
NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
networkImageView.setDefaultImageResId(R.drawable.default_photo);
networkImageView.setErrorImageResId(R.drawable.error_photo);
networkImageView.setImageUrl("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", imageLoader);
3.3 设置图片的宽高
NetworkImageView没有提供任何设置图片宽高的方法,这是由于它是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心。NetworkImageView最终会始终呈现给我们一张大小比控件尺寸略大的网络图片,因为它会根据控件宽高来等比缩放原始图片,这点需要注意,如果你想要了解详细原理,请看我之前的ImageRequest介绍。
如果你不想对图片进行压缩的话,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样它就会将该图片的原始大小展示出来,不会进行任何压缩。
本文转自 一点点征服 博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/5200499.html,如需转载请自行联系原作者