以前一直用图片框架,这次因为项目中图片量不多,于是想试试封装一个小型的缓存工具,效果还不错
代码主要来自郭大神的文章Android照片墙完整版,完美结合LruCache和DiskLruCache
主要实现LurCache+DiskLruCache缓存模式,这里先介绍一下这两个类:
LurCache
是系统提供的缓存类,可以将图片直接缓存在其中,只有在程序内存不够的时候,LurCache中的内容才会被释放,这也是相比软引用和弱引用的好处之一,Android2.3以后软引用和弱引用变得很容易被回收DiskLruCache 通常将数据缓存在 “/sdcard/Android/data//cache” 下,在应用被卸载的时候,目录下的数据将被一起删除
DiskLruCache 不是系统提供过的类,需要下载源码
下面是取自 郭霖 文章的DiskLruCache下载方法
由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这>个类从网上下载下来,然后手动添加到项目当中。DiskLruCache的源码在Google Source上,地址如下:
android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java
如果Google Source打不开的话,也可以点击这里下载DiskLruCache的源码。下载好了源码之后,只需要>在项目中新建一个libcore.io包,然后将DiskLruCache.Java文件复制到这个包中即可。
代码
缓存实现:在得到图片url时,先以url为key,在内存中查看有没有此图片,没有的话再重SD卡缓存目录中查找此图>片,依然没有找到的话,则开启异步任务,将图片下载到SD卡缓存目录,同时添加到内存缓存中;
/**
* 第一次调用方法getInstance()需要传入Context进行初始化,建议使用ApplicationContext进行初始化,
* 否则单例会一直持有context导致Activity无法被回收
* 可以在Apllication中 ImageLoader.getInstance(getApplicationContext());
*/
ImageLoader.getInstance().load(imageView, image_url);
ImageLoader
public class ImageLoader {
private static ImageLoader mImageLoader;
ImageCache imageCache;
/**
* 构造方法私有化
*/
private ImageLoader(Context context) {
imageCache = new ImageCache(context);
}
/**
* 获取单例
* 这里持有了一个Context 所以切记在第一次使用geInstance(ApplicationContext)来初始化,以免造成内存泄露
*/
public static ImageLoader getInstance(Context... contexts) {
if (contexts != null && contexts.length > 0)
if (mImageLoader == null)
synchronized (ImageLoader.class) {
if (mImageLoader == null)
mImageLoader = new ImageLoader(contexts[0]);
}
return mImageLoader;
}
/**
* 加载图片
*/
public void load(final ImageView view, String url) {
//先查看缓存中有没有图片
Bitmap bitmap = imageCache.getBitmapFromMemoryCache(imageCache.hashKeyForDisk(url));
if (bitmap != null)
view.setImageBitmap(bitmap);
else {
//RXjava 异步加载图片
Observable.just(url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String imageUrl) {
//在SD卡中查找有没有图片,没有则重新下载
return imageCache.loadImage(imageUrl);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
//更新ImageView
if (bitmap != null)
view.setImageBitmap(bitmap);
}
});
}
}
}
ImageCache
public class ImageCache {
/**
* 图片在内存中的缓存池,在内存不够时会将近期使用最少的图片回收
*/
private LruCache<String, Bitmap> mMemoryCache;
/**
* 图片硬盘缓存类
*/
private DiskLruCache mDiskLruCache;
public ImageCache(Context context) {
//初始化
initLruCache();
initDiskCache(context);
}
/**
* 加载图片
*/
public Bitmap loadImage(String imageUrl) {
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapShot = null;
try {
String key = hashKeyForDisk(imageUrl);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
// 缓存被写入后,再次查找key对应的缓存
snapShot = mDiskLruCache.get(key);
}
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
// 将缓存数据解析成Bitmap对象
Bitmap bitmap = null;
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
if (bitmap != null) {
// 将Bitmap对象添加到内存缓存当中
addBitmapToMemoryCache(key, bitmap);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
return null;
}
/**
* 建立HTTP请求,并获取Bitmap对象。
*
* @param urlString 图片的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;
}
/**
* 将一张图片存储到LruCache中。
*
* @param key LruCache的键,这里传入图片的URL地址。
* @param bitmap LruCache的键,这里传入从网络上下载的Bitmap对象。
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
*/
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 初始化内存缓存
*/
public void initLruCache() {
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
/**
* 初始化硬盘缓存
*/
public void initDiskCache(Context context) {
try {
// 获取图片硬盘缓存路径(Constants.CACHE_DIR是我的常量,这里传入一个文件夹名用于缓存目录就可以了)
File cacheDir = getDiskCacheDir(context, Constants.CACHE_DIR);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache
.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 根据传入的uniqueName获取硬盘缓存的路径地址。
*/
private File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
//SD卡存在或SD卡为内置不可被移除的时候
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
//获取缓存目录(/sdcard/Android/data/<application package>/cache)
cachePath = context.getExternalCacheDir().getPath();
} else {
//获取应用程序缓存目录,当设备内存不足时,可能会被删除( /data/data/<application package>/cache)
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* 获取当前应用程序的版本号。
* 每当版本号改变,缓存路径下存储的所有数据都会被清除掉,DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取
*/
private int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),
0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* 使用MD5算法对传入的key进行加密并返回。
*/
public String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}