Android 基于Vollery.ImageLoader的拓展(替换缓存策略,增加本地持久化)

没有了解过Vollery的朋友,请自行google了解.这里不做复述.

先来看ImageLoader的关键源码:

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;
	}
这个是请求图片的接口,可以看到,他通过图片路径+高度+宽度生成了一个缓存的Key.然后会通过这个Key去mCache中查找是否有缓存图片.如果有的话,会直接直接把图片包装成一个ImageContainer.且直接返回,如果没有找找到的话.会生成一个一个请求,将这条请求入队.执行策略的话,内部是一个优先级的阻塞队列.而ImageRequest的优先级是low.在ImageRequest这个类的getPriority()接口会返回优先级.注意的是,onResponse接口的第二参数,表示是否是及时返回的.如果为true时,一般表示图片在内存中,所以一般如果参数为True的话,不用启动动画效果.为false的话表示进行了网络请求.这时即可对imageview进行一些动画的过度.

	protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth,
			int maxHeight, final String cacheKey) {
		return new ImageRequest(requestUrl, new Listener<Bitmap>() {
			@Override
			public void onResponse(Bitmap response) {
				onGetImageSuccess(cacheKey, response);
			}
		}, maxWidth, maxHeight, Config.RGB_565, new ErrorListener() {
			@Override
			public void onErrorResponse(VolleyError error) {
				onGetImageError(cacheKey, error);
			}
		});
	}
这个接口生产了一个ImageRequest对象.成功时会触发

	protected void onGetImageSuccess(String cacheKey, Bitmap response) {
		// cache the image that was fetched.
		mCache.putBitmap(cacheKey, response);

		// remove the request from the list of in-flight requests.
		BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

		if (request != null) {
			// Update the response bitmap.
			request.mResponseBitmap = response;

			// Send the batched response
			batchResponse(cacheKey, request);
		}
	}
失败时会触发

protected void onGetImageError(String cacheKey, VolleyError error) {
		// Notify the requesters that something failed via a null result.
		// Remove this request from the list of in-flight requests.
		BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

		if (request != null) {
			// Set the error for this request
			request.setError(error);

			// Send the batched response
			batchResponse(cacheKey, request);
		}
	}
可以重写这2个接口,进行本地的保存操作.ImageLoader的源码简单的介绍到这里.接下来看如何在原有基础上增加一些拓展功能.


package com.chediandian.core.image;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;

import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.chediandian.core.image.cache.LruBitmapCache;
import com.chediandian.core.image.cache.local.name.LocalName;
import com.chediandian.core.image.utils.XKImageUtils;

/**
 * <p>
 * <b>ImageLoader增强版</b>
 * </p>
 * <li>支持本地缓存</li> <li>支持取消指定的图片请求</li> <li>比之前更强大的内存缓存池</li>
 * 
 * @version 1.0
 * @since 1.0
 * @author Shun
 * @hide 暂时不对外不暴露
 * 
 */
class XKImageLoader extends ImageLoader {

	private LruBitmapCache mCache;
	private LocalName mLocalName;
	private String mLocalDirsPath;
	private ExecutorService mPool;
	private Map<String, ImageContainer> mRequests;

	/**
	 * 
	 * @param queue
	 * @param imageCache
	 * @param localDirsPath
	 *            本地缓存文件夹路径
	 * @param localName
	 *            文件名策略接口
	 */
	public XKImageLoader(RequestQueue queue, ImageCache imageCache,
			String localDirsPath, LocalName localName) {
		super(queue, imageCache);
		// 这里记录引用,为了添加本地缓存
		mCache = (LruBitmapCache) imageCache;
		mLocalDirsPath = localDirsPath;
		mLocalName = localName;
		mPool = Executors.newFixedThreadPool(2);
		mRequests = new ConcurrentHashMap<String, ImageContainer>();
	}

	/**
	 * 复写get方法,增加本地是否有缓存的逻辑
	 */
	@Override
	public ImageContainer get(String requestUrl, ImageListener imageListener,
			int maxWidth, int maxHeight) {
		String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
		// 不在缓存,则从本地找
		if (mCache.getBitmap(cacheKey) == null) {
			File file = new File(mLocalDirsPath,
					mLocalName.getFileName(cacheKey));
			// 如果缓存文件存在,那么加入到缓存
			if (file.exists()) {
				mPool.execute(new LoadLocalTask(cacheKey, file
						.getAbsolutePath(), requestUrl, imageListener,
						maxWidth, maxHeight));
				return null;
			}
		}
		ImageContainer container = super.get(requestUrl, imageListener,
				maxWidth, maxHeight);
		// 如果bitmap为空,表示容器中没有缓存,即把它添加到请求Map中
		if (null == container.getBitmap()) {
			mRequests.put(cacheKey, container);
		}
		return container;
	}

	/**
	 * 加载本地图片
	 * 
	 * @param path
	 *            图片路径
	 * @param imageListener
	 *            图片监听
	 * @param maxWidth
	 *            最大宽度
	 * @param maxHeight
	 *            最大高度
	 */
	public void loadImage(String path, ImageListener imageListener,
			int maxWidth, int maxHeight) {
		String key = getCacheKey(path, maxWidth, maxHeight);
		// 不在缓存,则从本地找
		if (mCache.getBitmap(key) == null) {
			File file = new File(path);
			// 如果缓存文件存在,那么加入到缓存
			if (file.exists()) {
				mPool.execute(new LoadLocalTask(key, file.getAbsolutePath(),
						path, imageListener, maxWidth, maxHeight));
			}
		} else {
			Bitmap bitmap = mCache.getBitmap(key);
			ImageContainer container = new ImageContainer(bitmap, path, null,
					null);
			imageListener.onResponse(container, true);
		}
	}

	/**
	 * 复写onGetImageSuccess,增加线程池来管理持久化
	 */
	@Override
	protected void onGetImageSuccess(String cacheKey, Bitmap response) {
		// 持久化图片缓存
		File file = new File(mLocalDirsPath, mLocalName.getFileName(cacheKey));
		if (!file.exists()) {
			mPool.execute(new LocalTask(file.getAbsolutePath(), response));
		}
		super.onGetImageSuccess(cacheKey, response);
		// 从请求Map中移除这条请求,因为它已经完成
		mRequests.remove(cacheKey);
	}

	@Override
	protected void onGetImageError(String cacheKey, VolleyError error) {
		super.onGetImageError(cacheKey, error);
		// 从请求Map中移除这条请求,因为它已经完成
		mRequests.remove(cacheKey);
	}

	/**
	 * <p>
	 * <b>持久化图片到本地的Task</b>
	 * </P>
	 * 
	 * @version 1.0
	 * @since 1.0
	 * @author Shun
	 * 
	 */
	private class LocalTask implements Runnable {

		private String mPath;
		private Bitmap mBitmap;

		public LocalTask(String path, Bitmap bitmap) {
			this.mPath = path;
			this.mBitmap = bitmap;
		}

		@Override
		public void run() {
			FileOutputStream out = null;
			try {
				if (null != mBitmap) {
					out = new FileOutputStream(mPath);
					mBitmap.compress(CompressFormat.JPEG, 100, out);
				}
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} finally {
				if (out != null) {
					try {
						out.close();
						out = null;
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	/**
	 * 加载本地图片Task
	 * 
	 * @version 1.0
	 * @since 1.0
	 * @author Shun
	 * 
	 */
	private class LoadLocalTask implements Runnable {
		String mCacheKey;
		String mPath;
		String mUrl;
		ImageListener mImageListener;
		int mWidth;
		int mHeight;

		public LoadLocalTask(String cacheKey, String path, String url,
				ImageListener imageListener, int width, int height) {
			this.mCacheKey = cacheKey;
			this.mPath = path;
			this.mUrl = url;
			this.mImageListener = imageListener;
			this.mWidth = width;
			this.mHeight = height;
		}

		@Override
		public void run() {
			Bitmap bitmap = XKImageUtils.loadBitmap(mPath, mWidth, mHeight);
			if (null != bitmap) {
				mCache.putBitmap(mCacheKey, bitmap);
			}
			// 这个Hanlder是父类的全局变量
			mHandler.postDelayed(new DeliverTask(bitmap, mUrl, mImageListener),
					100);
		}
	}

	/**
	 * 转发到主线程的Task
	 * 
	 * @version 1.0
	 * @since 1.0
	 * @author Shun
	 * 
	 */
	private class DeliverTask implements Runnable {
		Bitmap mCacheBitmap;
		String mUrl;
		ImageListener mImageListener;

		public DeliverTask(Bitmap cacheBitmap, String url,
				ImageListener imageListener) {
			this.mCacheBitmap = cacheBitmap;
			this.mUrl = url;
			this.mImageListener = imageListener;
		}

		@Override
		public void run() {
			ImageContainer container = new ImageContainer(mCacheBitmap, mUrl,
					null, null);
			if (null != mCacheBitmap)
				mImageListener.onResponse(container, true);
			else
				mImageListener.onErrorResponse(null);
		}
	}

	/**
	 * 
	 * <p>
	 * <b>取消所有图片请求</b>
	 * </p>
	 * 注意的是,此类接口对本地图片无效
	 */
	public void cancelRequest() {
		for (String key : mRequests.keySet()) {
			ImageContainer container = mRequests.get(key);
			if (null != container)
				container.cancelRequest();
		}
		mRequests.clear();
	}

	/**
	 * 
	 * <p>
	 * <b>取消指定的请求</b>
	 * </p>
	 * 
	 * @param url
	 *            请求的URL
	 */
	public void cancelRequest(String url) {
		cancelRequest(url, 0, 0);
	}

	/**
	 * 
	 * <p>
	 * <b>取消指定的请求</b>
	 * </p>
	 * 
	 * @param url
	 *            请求的URL
	 * @param maxWidth
	 *            加载时设置的宽度
	 * @param maxHeight
	 *            加载时设置的高度
	 */
	public void cancelRequest(String url, int maxWidth, int maxHeight) {
		String key = getCacheKey(url, maxWidth, maxHeight);
		ImageContainer container = mRequests.remove(key);
		if (null != container) {
			container.cancelRequest();
		}
	}

	/**
	 * 
	 * <p>
	 * <b>清除所有缓存</b>
	 * </p>
	 */
	public void clearCache() {
		if (null != mCache) {
			mCache.clear();
		}
	}

	/**
	 * 
	 * <p>
	 * <b>清除指定缓存</b>
	 * </p>
	 * 
	 * @param urlOrPath
	 *            可以是Url可以是本地路径
	 */
	public boolean cearCache(String urlOrPath) {
		return clearCache(urlOrPath, 0, 0);
	}

	/**
	 * 
	 * <p>
	 * <b>清除指定缓存</b>
	 * </p>
	 * 注意maxWidth,maxHeight必须与缓存请求时的大小保持一致
	 * 
	 * @param urlOrPath
	 *            可以是Url可以是本地路径
	 * @param maxWidth
	 *            加载时设置的宽度
	 * @param maxHeight
	 *            加载时设置的高度
	 */
	public boolean clearCache(String urlOrPath, int maxWidth, int maxHeight) {
		if (null != mCache) {
			Bitmap bitmap = mCache.remove(getCacheKey(urlOrPath, maxWidth,
					maxHeight));
			if (null != bitmap) {
				if (!bitmap.isRecycled()) {
					bitmap.recycle();
				}
				bitmap = null;
				return true;
			} else {
				return false;
			}
		}
		return false;
	}
}
可以看到 我重写了onGetImageSuccess,onGetImageError,get 3个接口来实现本地化功能.大家可以看一下,注释先的很清楚,DeliverTask主要用于异步加载本地图片后调度到主线程.LocalTask主要用于图片的持久化.LoadLocalTask加载本地图片.需要注意的是mHandler. mHandler在源码中是私有的.我自己改成了受保护的.

get接口中,会先检查是否有缓存,没有缓存的话从本地找,如果本地有的话会讲一个任务放到线程池中去执行. 最后由 DeliverTask 调度到主线程中 关键代码:

		String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
		// 不在缓存,则从本地找
		if (mCache.getBitmap(cacheKey) == null) {
			File file = new File(mLocalDirsPath,
					mLocalName.getFileName(cacheKey));
			// 如果缓存文件存在,那么加入到缓存
			if (file.exists()) {
				mPool.execute(new LoadLocalTask(cacheKey, file
						.getAbsolutePath(), requestUrl, imageListener,
						maxWidth, maxHeight));
				return null;
			}
		}
重写onGetImageSuccess的目的是为了在图片获取成功时将图片保存到本地 如果本地不存在,会执行一个持久化任务, 同样也是通过线程.

		File file = new File(mLocalDirsPath, mLocalName.getFileName(cacheKey));
		if (!file.exists()) {
			mPool.execute(new LocalTask(file.getAbsolutePath(), response));
		}

至此,一个支持本地功能的ImageLoader完成,看注释应该很容易看懂代码,这里就不细讲了 :)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值