Android 整理BitMap的加载和图片缓存

好紧张!!由于任务第一次写博客,话说怎么样才能装作经常写的呢?害羞

先来句口号吧!!!!!

为了联盟!!!


作为一只正在路上的程序员,之前经常被OOM这个异常所关注!! 所以经过查阅资料终于把它甩掉了!!! 好了,下面就把我抄的一些关于图片加载和缓存的代码整理了一下和大家分享交流一下!

一丶 Bitmap的高效加载

首先我们来先想想如何加载一个Bitmap吧! Bitmap 在Android 中指的是一张图片(,好像写了句废话) 通过BitmapFactory 类提供的四个方法: decodeFile、decodeResouce、decodeStream和decodeByteArray,分别用于支持从文件系统、资源、输入流以及字节数组中加载一个Bitmap对象。(只见过decodeResouce这个方法)


那么如何高效的加载Bitmap呢? 看过已经上路的程序员都是这么写的,那就是采用BitmapFactory.Options来加载所需尺寸的图片. 比如我们来用ImageView来显示图片,很多时候ImageView没有图片的原始尺寸那么大,这时候把整个图片加载进来设到imageView 上,显然 没必要,because ImageView没有办法显示原始图片.通过BitmapFactory.Options就可以按一定的采样率来加载缩小后的图片,这样把缩小后的图片在ImageView上显示 就降低了内存的占用 避免的了OOM。

采样率这里就不说了 ,大家可以自己去百度或者谷歌, 主要用到的是它的inSampleSize这个参数.(主要我也不是很明白.有小算法)

上面扯到通过采样率可有效的加载图片,那么下面就抄一下获取采样的流程:

1、 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。

2、从BitmapFactory.Options中取出图片的原始宽高,它们对应于outWidth和outHeight参数。

3、根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。

4、将BitmapFactory.Options的inJustDecodeBounds参数设为tfalse并加重新载图片


通过以上4步,加载出来的图片就是缩放后的,当然也有例外。

PS:说一下inJustDecodeBounds参数为true时,bitmapFactory只会解析图片的原始宽高信息,不会去加载图片。



终于上代码了。打字太费劲了。。。。。。。
通过上面的4步就有了以下的代码,上菜=_=!

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {

		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(res, resId, options);

		options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeResource(res, resId, options);
	}

	private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
		int height = options.outHeight;
		int width = options.outWidth;
		int inSampleSize = 1;

		if (height > reqHeight || width > reqWidth) {
			int halfHeight = height / 2;
			int halfWidth = width / 2;

			while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
				inSampleSize *= 2;
			}
		}
		return inSampleSize;
	}


代码很简单,相信聪明的你们一定能看懂!

SO,问题来了? 怎么使用了????
比如需求所期望的大小为100*100,这时候就可以这么的.
mIageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.id.image,100,100));

二、图片缓存

现在有好多第三方的库,都很好用.那我为啥要自己写呢??????? 唉,都是任务...............

现在一般都是三级缓存内存、硬盘、网络(说是三级,但我个人觉得网络不算缓存吧) 其他的就不说了,自行百度谷歌吧!!

现在常用的一种缓存的算法是LRU(Least Recently Used)意思是最近最少使用这么个算法,大白话就是当缓存满了,快要溢出来了,就会优先的删除近期最少使用的缓存对象, LRU的缓存有两种 :LruCache(内存缓存) 和DIskLruCache(磁盘缓存) 这两者结合一下 嘿嘿嘿! 就可以实现一个具有很高效的ImageLoader。 

我们一个个来看吧!!


1、LruCache

LruCache是一个泛型类,内部采用一个LinkedHashMap 以强引用的方式来存储外界的缓存对象 有get和put方法完成获取和添加的操作.

PS: 自行百度  强引用 软引用 和 弱引用

public class LruCache<K,V>{

    public final LinkedHashMap<K,V> map;

}
上面是LruCache的定义 ,下面来看看怎么实现的. 直接上菜...

// 获取程序最大的可用内存
		int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		// 设置图片缓存大小为程序内存的1/8
		int cacheSize = maxMemory / 8;

		// 初始化LruCache
		
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				// TODO Auto-generated method stub
				return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
			}
		};

以上就是LruCache的基本初始化,只需要提供缓存的总容量并重写sizeOf方法即可,这个方法的作用就是计算缓存对象的大小,注意单位一致就好,几行代码 写了注释 一定能看懂的。除以1024是为了转换成KB。

剩下的就是缓存的获取和添加。

//获取缓存对象
mMemoryCache.get(key);


//添加缓存对象
mMemoryCache.put(key,bitmap);

2.DiskLruCache

由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来 下载地址

点这里


(1) DisLruCache 的创建

DiskLruCache 并不能通过构造方法来创建 ,它自身提供了open方法来创建 

/**
* @param directory 磁盘缓存在文件系统中的存储路径
* @param appVersion 应用的版本号 一般设为1
* @param valueCount 单个节点所对应的数据个数,一般设为1
* @param maxSize 缓存的大小
*/
 public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

	long DISK_CACHE_SIZE = 10 * 1024 * 1024;
		try {
			// 获取图片缓存路径
			File cacheDir = getDiskCacheDir(context, "thumb");
			if (!cacheDir.exists()) {
				cacheDir.mkdirs();
			}
			// 创建DiskLruCache实例,初始化缓存数据
			mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 
					DISK_CACHE_SIZE);
		} catch (IOException e) {
			e.printStackTrace();
		}

(2) DiskLruCache的缓存添加

DiskLruCache 的缓存添加是通过Editor完成的, Editor表示一个缓存对象的编辑对象。

先上菜。。
                /**
		 * 使用Md5算法对传入的key进行加密返回
		 * 
		 * @param key
		 * @return
		 */
		private String hasKeyForDisk(String key) {
			String cacheKey;
			try {
				MessageDigest mDigest = MessageDigest.getInstance("MD5");
				mDigest.update(key.getBytes());
				cacheKey = bytesToHexString(mDigest.digest());

			} catch (NoSuchAlgorithmException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				cacheKey = String.valueOf(key.hashCode());
			}
			return cacheKey;
		}
		private String bytesToHexString(byte[] bytes) {

			StringBuilder sBuilder = new StringBuilder();
			for (int i = 0; i < bytes.length; i++) {
				String hex = Integer.toHexString(0xFF & bytes[i]);
				if (hex.length() == 1) {
					sBuilder.append('0');
				}
				sBuilder.append(hex);
			}

			return sBuilder.toString();
		}

	}

上面的代码说 的是 将图片的url转成key ,然后就可以获取Editor对象了

对于key来说,如果当前不存在其他的Editor对象 ,那么edit()就会返回一个新的Editor对象,然后就得到一个文件输出流。
 
PS:由于前面在DiskLruCache的open方法中设置了一个节点数据 ,所以下面的常量 DISK_CACHE_INDEX 直接设为0

String key=hasKeyForDisk(url);
DisLruCache.Editor editor=mDisLruCache.editor(key);
if(editor != null){
OutputSttream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
}

有了输出流,So? 当从网络下载图片时,图片就可以通过输出流写入到文件系统上,下面请吃菜!!

               /**
		 * 建立HTTP请求,并获取Bitmap对象。
		 * 
		 * @param imageUrl
		 *            图片的URL地址
		 * @return 解析后的Bitmap对象
		 */
		private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
			HttpURLConnection urlConnection = null;
			BufferedOutputStream out = null;
			BufferedInputStream in = null;
			try {
				final URL url = new URL(urlString);
				urlConnection = (HttpURLConnection) url.openConnection();
				in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
				out = new BufferedOutputStream(outputStream, 8 * 1024);
				int b;
				while ((b = in.read()) != -1) {
					out.write(b);
				}
				return true;
			} catch (final IOException e) {
				e.printStackTrace();
			} finally {
				if (urlConnection != null) {
					urlConnection.disconnect();
				}
				try {
					if (out != null) {
						out.close();
					}
					if (in != null) {
						in.close();
					}
				} catch (final IOException e) {
					e.printStackTrace();
				}
			}
			return false;
		}

	}

最后还必须通过Editor的commit()来提交写入操作,如果下载过程发生了异常,可以通过Editor的abort()来退回整个操作,

OutputStream outputStream = editor.newOutputStream(0);
			if (downloadUrlToStream(imageUrl, outputStream)) {
				editor.commit();
			} else {
				editor.abort();
			}
mDisLruCache.flush();


此时此刻,图片已经正确的写入到文件系统中了。

(3) DiskLruCache的缓存查找

和缓存添加的过程差不多,查找也需要把url转成key 然后通过DIskLruCache的get方法得到一个Snapshort对象,然后通过Snapshot对象可得到缓存的文件输入流,然后得到Bitmap对象。

PS:为了避免记载图片过程导致的OOM,一般不建议直接加载原始图片

Bitmap bitmap = null;
		String key = hashKeyFormUrl(url);
		DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
		if (snapshot != null) {

			FileInputStream fileInputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
			FileDescriptor descriptor = fileInputStream.getFD();
			bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(descriptor, reqWidth, reqHeight);
			if (bitmap != null) {
				addBitmapToMemoryCache(key, bitmap);

			}
		}

以上就是DiskLruCache的创建 缓存的添加和查找。。。人家还说DiskLruCache还有remove、delete的方法,有用到的就自己查查吧!!

割====================================================================================================


前面已将Bitmap的加载、LruCache、DiskLruCache的使用扯完了。。你以为结束了,现在才刚刚开始。。。

前面说了由LruCache和DiskLruCache 结合,嘿嘿嘿,就可以实现一个优秀的ImageLoader。

咳咳!又开始抄书了。。。。=_=!

三、ImageLoagder的实现


一个优秀的ImageLoader应该有以下几点:

  • 图片的同步加载
  • 图片的异步加载
  • 图片压缩
  • 内存缓存
  • 磁盘缓存
  • 网络拉取

内存缓存和磁盘缓存是ImageLoader的核心,通过这两级缓存大大的提高了程序的效率。只有当着两级都不可用时才需要拉取图片。

1、图片的压缩功能的实现


public class ImageResizeer {
	private static final String TAG = "ImageResizeer";

	public ImageResizeer() {
	}

	public Bitmap decodeSampledBitMapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {

		BitmapFactory.Options options = new BitmapFactory.Options();

		options.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(res, resId, options);

		options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

		options.inJustDecodeBounds = false;

		return BitmapFactory.decodeResource(res, resId, options);

	}

	public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
		BitmapFactory.Options options = new BitmapFactory.Options();

		options.inJustDecodeBounds = true;

		BitmapFactory.decodeFileDescriptor(fd, null, options);

		options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeFileDescriptor(fd, null, options);

	}

	private int calculateInSampleSize(Options options, int reqWidth, int reqHeight) {
		// TODO Auto-generated method stub
		if (reqWidth == 0 || reqHeight == 0) {
			return 1;
		}

		int height = options.outHeight;
		int wight = options.outWidth;
		int inSampleSize = 1;

		if (height > reqHeight || wight > reqWidth) {
			
			int halfHeight = height / 2;
			int halfWidth = wight / 2;

			while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
				inSampleSize *= 2;
			}
		}
		
		return inSampleSize;
	}

}

2、内存缓存和磁盘缓存的实现

这里选择前面说过的LruCache和DiskLruCache来分别完成内存缓存和磁盘缓存的工作。 在ImageLoader初始化时 ,会创建LruCache和DiskLruCache, 上菜。。。

private LruCache<String, Bitmap> mMemoryCache;
	private DiskLruCache mDiskLruCache;

	private ImageLoader(Context context){
		mContext=context.getApplicationContext();
		int maxMemory=(int) (Runtime.getRuntime().maxMemory()/1024);
		int cacheSize=maxMemory/8;
		mMemoryCache=new LruCache<String,Bitmap>(cacheSize){
			
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				// TODO Auto-generated method stub
				return bitmap.getRowBytes()*bitmap.getHeight()/1024;
			}
		};
			
		File disCacheDir=getDiskCacheDir(mContext,"bitmap");
		if (!disCacheDir.exists()) {
			disCacheDir.mkdirs();
		}
		if (getUsableSpace(disCacheDir)>DISK_CACHE_SIZE) {
			try {
				mDiskLruCache=DiskLruCache.open(disCacheDir, 1, 1, DISK_CACHE_SIZE);
				mIsDiskLruCacheCreated=true;
			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}
			
		}
	
	}

上面再创建磁盘缓存是 做了一个判断,就是有可能磁盘剩余空间小于磁盘缓存所需要的大小,就会磁盘缓存创建失败,就会失效。

创建完毕后,就要提供方法来完成缓存的添加和获取。 先看内存缓存的 添加和获取。。

上菜了。。。

private void addBitmapToMemoryCache(String key, Bitmap bitmap) {

		if (getBiitmapFromMemCache(key) == null) {
			mMemoryCache.put(key, bitmap);
		}
	}

	private Object getBiitmapFromMemCache(String key) {
		// TODO Auto-generated method stub
		return mMemoryCache.get(key);
	}


磁盘缓存的添加和读取,之前有说到 ,下面我在贴一下

DiskLruCache 缓存添加主要通过Editor来完成 由commit和abort 方法来提交和撤销对文件系统的写操作
loadBitMapFromHttp这个方法中就是
读呢? 是需要通过Snapshot来完成的 通过Snapshot可以得到磁盘缓存对象的FileInputStream,但是 输入流无法便捷的进行压缩,
所以就通过FileDescriptor来加载压缩后的图片 最后将Bitmap添加到内存缓存中。
loadBitmapFromDiskCache这个方法就是

 
	private Bitmap loadBitMapFromHttp(String url, int reqWidth, int reqHeight) throws IOException {

		if (Looper.myLooper() == Looper.getMainLooper()) {
			throw new RuntimeException("不能从UI线程访问网络");
		}

		if (mDiskLruCache == null) {
			return null;
		}

		String key = hashKeyFromUrl(url);
		DiskLruCache.Editor editor = mDiskLruCache.edit(key);
		if (editor != null) {
			OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
			if (downLoadUrlToStream(url, outputStream)) {
				editor.commit();
			} else {
				editor.abort();
			}
			mDiskLruCache.flush();
		}
		return loadBitMapFromHttp(url, reqWidth, reqHeight);
	}

	private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException {

		if (Looper.myLooper() == Looper.getMainLooper()) {
			throw new RuntimeException("不能从UI线程访问网络");
		}

		if (mDiskLruCache == null) {
			return null;
		}

		Bitmap bitmap = null;
		String key = hashKeyFromUrl(url);
		DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
		if (snapshot != null) {

			FileInputStream fileInputStream = snapshot.getInputStream(DISK_CACHE_SIZE);
			FileDescriptor descriptor = fileInputStream.getFD();
			bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileInputStream, reqWidth, reqHeight);
			if (bitmap != null) {
				addBitmapToMemoryCache(key, bitmap);
			}

		}

		return bitmap;
	}


3、同步加载和异步加载接口实现

同步加载,同步加载需要在外部在线程中调用,为啥呢?因为它比较耗时。

/**
	 * 从内存缓存或磁盘缓存或网络工作中加载图片
	 * 
	 * @param uri http url
	 * @param reqWidth
	 *            实际宽度
	 * @param reqHeight
	 *            实际高度
	 * @return
	 */
	public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
		Bitmap bitmap = loadBitmapFromMemCache(uri);
		if (bitmap != null) {
			Log.e(TAG, "url--------->" + uri);
			return bitmap;
		}

		try {
			bitmap = loadBimapFromHttp(uri, reqWidth, reqHeight);
			if (bitmap != null) {
				Log.e(TAG, "url--------->" + uri);
				return bitmap;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		if (bitmap == null && !mIsDiskLruCacheCreated) {
			Log.e(TAG, "错误!!!!");
			bitmap = downloadBitMapFromUrl(uri);
		}
		return bitmap;

	}


聪明的你们是不是已经看不来了??loadBitmap这个方法的实现过程 :首先是从内存中读取图片,接着是磁盘缓存中读取,最后在是网络拉取.

PS:这个方法不能再主线程中.

异步加载 


/**
	 * 异步加载
	 */

	public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) {

		imageView.setTag(TAG_KEY_URI, uri);
		Bitmap bitmap = loadBitmapFromMemCache(uri);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		}

		Runnable loadBitmapTask = new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
				if (bitmap != null) {
					LoaderResult result = new LoaderResult(imageView, uri, bitmap);
					mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();

				}
			}
		};
		THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
	}

从bindBitmap方法 的实现来看, 首先会从内存缓存中读取图片,如果有 就直接返回,否则会在线程池中去调用loadBitmap方法,当图片加载成功后再将图片和图片地址及所需要绑定的imageView封装成一个LoaderResult对象,再通过mMainHandler向主线程发送一个消息.这样就可以在主线程中给imageView设置图片了。

再来看看上面用到的线程池和Handler,先说线程池 THREAD_POOL_EXECUTOR的实现

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
	private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
	private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
	private static final long KEEP_ALIVE = 10L;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);

@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
return new Thread(r, "ImageLoader:" + mCount.getAndIncrement());
}
};

	public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
			KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactory);

之所以采用线程池,我看到大神们都是这么说的,如果直接采用普通线程去加载图片,随着列表的滑动可能会产生大量的线程,并不利于整体效率的提升。

大神们还说,这里也没有选择AsyncTask, 是因为AsynTask在3.0以上的版本无法实现并发的效果,

还有一个Handler的实现,相对就比较简单了. imageLoader 直接采用主线程的Looper来构造Handler对象,这样就使得imageLoader可以在非主线程中构造了
为了解决由于View复用所导致的列表错位问题  这里在给imageView设置图片之前都会检查它的url有没有发生该有 ,如果发生改变就不在设置图片了.

	private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
		public void handleMessage(android.os.Message msg) {

			LoaderResult result = (LoaderResult) msg.obj;
			ImageView imageView = result.imageView;

			String uri = (String)imageView.getTag(TAG_KEY_URI);
			// 判断url是否发生改变
			if (uri.equals(result.uri)) {
				imageView.setImageBitmap(result.bitmap);

			} else {
				Log.e(TAG, "url已经改变");
			}
		};
	};


 
到这 ImageLoader就已经完全的扯完了 ,下面我贴一下完整的代码;
public class ImageLoader {

	private static final String TAG = "ImageLoader";
	public static final int MESSAGE_POST_RESULT = 1;

	private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

	private static final int CORE_POOL_SIZE = CPU_COUNT + 1;

	private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

	private static final long KEEP_ALIVE = 10L;

	private static final int TAG_KEY_URI = R.id.imageloader_uri;

	private static final long DISK_CACHE_SIZE = 50 * 1024 * 1024;
	private static final int IO_BUFFER_SIZE = 8 * 1024;
	private static final int DISK_CACHE_INDEX = 0;
	private boolean mIsDiskLruCacheCreated = false;

	private static final ThreadFactory sThreadFactory = new ThreadFactory() {
		private final AtomicInteger mCount = new AtomicInteger(1);

		@Override
		public Thread newThread(Runnable r) {
			// TODO Auto-generated method stub
			return new Thread(r, "ImageLoader:" + mCount.getAndIncrement());
		}
	};

	public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
			KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), sThreadFactory);

	private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
		public void handleMessage(android.os.Message msg) {

			LoaderResult result = (LoaderResult) msg.obj;
			ImageView imageView = result.imageView;

			String uri = (String) imageView.getTag(TAG_KEY_URI);
			// 判断url是否发生改变
			if (uri.equals(result.uri)) {
				imageView.setImageBitmap(result.bitmap);

			} else {
				Log.e(TAG, "url已经改变");
			}
		};
	};

	private Context mContext;
	private ImageResizeer mImageResizeer = new ImageResizeer();
	private LruCache<String, Bitmap> mMemoryCache;
	private DiskLruCache mDiskLruCache;

	private ImageLoader(Context context) {
		mContext = context.getApplicationContext();
		int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		int cacheSize = maxMemory / 8;
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				// TODO Auto-generated method stub
				return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
			}
		};

		File disCacheDir = getDiskCacheDir(mContext, "bitmap");
		if (!disCacheDir.exists()) {
			disCacheDir.mkdirs();
		}
		if (getUsableSpace(disCacheDir) > DISK_CACHE_SIZE) {
			try {
				mDiskLruCache = DiskLruCache.open(disCacheDir, 1, 1, DISK_CACHE_SIZE);
				mIsDiskLruCacheCreated = true;
			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}

		}

	}

	/**
	 * 创建一个ImageLoader的实例
	 * 
	 * @param context
	 * @return
	 */
	public static ImageLoader build(Context context) {
		return new ImageLoader(context);
	}

	private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
		if (getBitmapFromMemCache(key) == null) {
			mMemoryCache.put(key, bitmap);
		}
	}

	private Bitmap getBitmapFromMemCache(String key) {

		return mMemoryCache.get(key);
	}

	/**
	 * 从内存 磁盘缓存中或网络加载图片 然后绑定imageview
	 * 
	 * @param uri
	 *            http url
	 * @param imageView
	 * 
	 */
	public void bindBitMap(final String uri, final ImageView imageView) {

		bindBitMap(uri, imageView, 0, 0);
	}

	/**
	 * 
	 * @param uri
	 * @param imageView
	 * @param reqWidth
	 * @param reqHeight
	 */
	private void bindBitMap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) {

		imageView.setTag(TAG_KEY_URI, uri);
		Bitmap bitmap = loadBitmapFromMemCache(uri);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		}

		Runnable loadBitmapTask = new Runnable() {

			@Override
			public void run() {

				Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
				if (bitmap != null) {

					LoaderResult result = new LoaderResult(imageView, uri, bitmap);
					mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
				}
			}
		};

		THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
	}

	/**
	 * 
	 * 从内存缓存或磁盘缓存或网络加载图片
	 * 
	 * @param uri
	 *            http url
	 * @param reqWidth
	 *            需要的宽度
	 * @param reqHeight
	 *            需要的高度
	 * @return
	 */
	public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
		Bitmap bitmap = loadBitmapFromMemCache(uri);
		if (bitmap != null) {
			Log.e(TAG, "loadBitmapFromMemCache------>" + uri);
			return bitmap;
		}

		try {
			bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
			if (bitmap != null) {
				Log.e(TAG, "loadBitmapFromDiskCache----->" + uri);
				return bitmap;
			}
			bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
			Log.e(TAG, "loadBitmapFromHttp----->" + uri);
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}

		if (bitmap == null && !mIsDiskLruCacheCreated) {
			Log.e(TAG, "DiskCache创建失败");
			bitmap = downloadBitmapFromUrl(uri);

		}

	}

	/**
	 * 
	 * 
	 * @param urlString
	 * @return
	 */
	private Bitmap downloadBitmapFromUrl(String urlString) {

		Bitmap bitmap = null;
		HttpURLConnection connection = null;
		BufferedInputStream inputStream = null;
		try {
			final URL url = new URL(urlString);
			connection = (HttpURLConnection) url.openConnection();
			inputStream = new BufferedInputStream(connection.getInputStream(), IO_BUFFER_SIZE);
			bitmap = BitmapFactory.decodeStream(inputStream);
		} catch (Exception e) {

			Log.e(TAG, "downloadBitmapFromUrl-->" + e);
		} finally {
			if (connection != null) {
				connection.disconnect();
			}
			MyUtils.close(inputStream);
		}

		return null;
	}

	private Bitmap loadBitmapFromMemCache(String url) {

		final String key = hashKeyFormUrl(url);
		Bitmap bitmap = getBitmapFromMemCache(key);
		return bitmap;
	}

	private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException {

		if (Looper.myLooper() == Looper.getMainLooper()) {
			Log.e(TAG, "不能从UI线程中读取图片");
		}
		if (mDiskLruCache == null) {
			return null;
		}

		Bitmap bitmap = null;
		String key = hashKeyFormUrl(url);
		DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
		if (snapshot != null) {
			FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);

			FileDescriptor descriptor = fileInputStream.getFD();
			bitmap = mImageResizeer.decodeSampledBitmapFromFileDescriptor(descriptor, reqWidth, reqHeight);
			if (bitmap != null) {
				addBitmapToMemoryCache(key, bitmap);

			}
		}
		return bitmap;
	}

	private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException {

		if (Looper.myLooper() == Looper.getMainLooper()) {
			throw new RuntimeException("不能从UI线程访问网络");
		}

		if (mDiskLruCache == null) {
			return null;
		}

		String key = hashKeyFormUrl(url);
		DiskLruCache.Editor editor = mDiskLruCache.edit(key);
		if (editor != null) {
			OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
			if (downloadUrlToStream(url, outputStream)) {
				editor.commit();
			} else {
				editor.abort();
			}
			mDiskLruCache.flush();
		}

		return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
	}

	private boolean downloadUrlToStream(String url, OutputStream outputStream) {

		HttpURLConnection connection = null;
		BufferedInputStream in = null;
		BufferedOutputStream out = null;
		try {
			final URL url2 = new URL(url);
			connection = (HttpURLConnection) url2.openConnection();
			in = new BufferedInputStream(connection.getInputStream(), IO_BUFFER_SIZE);
			out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

			int b;

			while ((b = in.read()) != -1) {
				out.write(b);
			}
			return true;
		} catch (Exception e) {

			Log.e(TAG, "downloadBitmap-->" + e);
		} finally {
			if (connection != null) {
				connection.disconnect();
			}
			MyUtils.close(out);
			MyUtils.close(in);
		}

		return false;
	}

	private String hashKeyFormUrl(String url) {
		String cachekey;

		try {
			final MessageDigest mDigest = MessageDigest.getInstance("MD5");
			mDigest.update(url.getBytes());
			cachekey = bytesToHexString(mDigest.digest());

		} catch (Exception e) {
			cachekey = String.valueOf(url.hashCode());
		}
		return cachekey;

	}

	private String bytesToHexString(byte[] bytes) {

		StringBuilder sBuilder = new StringBuilder();
		for (int i = 0; i < bytes.length; i++) {
			String hex = Integer.toHexString(0xFF & bytes[i]);
			if (hex.length() == 1) {
				sBuilder.append('0');
			}
			sBuilder.append(hex);
		}
		return sBuilder.toString();
	}

	private File getDiskCacheDir(Context context, String uniqueName) {

		boolean externalStorageAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
		final String cachePath;
		if (externalStorageAvailable) {
			cachePath = context.getExternalCacheDir().getPath();

		} else {
			cachePath = context.getCacheDir().getPath();
		}

		return new File(cachePath + File.separator + uniqueName);
	}

	private long getUsableSpace(File path) {
		if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
			return path.getUsableSpace();
		}
		final StatFs statFs = new StatFs(path.getPath());
		return (long) statFs.getBlockSize() * (long) statFs.getAvailableBlocks();

	}

	private static class LoaderResult {
		public ImageView imageView;
		public String uri;
		public Bitmap bitmap;

		public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
			this.imageView = imageView;
			this.uri = uri;
			this.bitmap = bitmap;
		}

	}
}


这里呢 我用的demo是郭神的....他的博客也有写缓存的很全面...感兴趣的可以去看看

到这就全部完事了....手好疼..........

最后说在说一句: 

为了联盟!!!!!!













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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值