如果需要研究DiskLruCache 可以去看看弘扬大神(http://blog.csdn.net/lmj623565791/article/details/47251585)对其源码的解析,以及郭霖大神(http://blog.csdn.net/guolin_blog/article/details/28863651)对DisLruCachede 使用的完全解析
工具类的编辑也是大量参考了郭霖大神的博客
首先会跟大家简单介绍 DiskLruCache的使用
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
参数说明:directory 磁盘缓存位置;appVersion APP版本号;valueCount 指定同一个key可以对应多少个缓存文件,基本都是传1;maxSize 指定最多可以缓存多少字节的数据。
需要注意的是:每当版本号改变,缓存路径下存储的所有数据都会被清除掉。
Journal文件解读:第一行是个固定的字符串“libcore.io.DiskLruCache”,标志着我们使用的是DiskLruCache技术。第二行是DiskLruCache的版本号,这个值是恒为1的。第三行是应用程序的版本号,我们在open()方法里传入的版本号是什么这里就会显示什么。第四行是valueCount,这个值也是在open()方法中传入的,通常情况下都为1。第五行一下的数据:有个前缀开始,后面紧跟着缓存图片的key,后缀就是图片的字节数。
DIRTY前缀,通常我们看到DIRTY这个字样都不代表着什么好事情,意味着这是一条脏数据。没错,每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件中写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。然后调用commit()方法表示写入缓存成功,这时会向journal中写入一条CLEAN记录,意味着这条“脏”数据被“洗干净了”,调用abort()方法表示写入缓存失败,这时会向journal中写入一条REMOVE记录。也就是说,每一行DIRTY的key,后面都应该有一行对应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自动删除掉。
READ前缀除了DIRTY、CLEAN、REMOVE之外,还有一种前缀是READ的记录,每当我们调用get()方法去读取一条缓存数据时,就会向journal文件中写入一条READ记录。
值得注意的是,在用户存取操作达到2000次的时候就会触发重构journal的事件,这时会自动把journal中一些多余的、不必要的记录全部清除掉,保证journal文件的大小始终保持在一个合理的范围内
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
// 存入磁盘
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
//downloadUrlToStream(String urlString, OutputStream outputStream)下载图片的方法
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();//这样就存入磁盘了
} else {
editor.abort();//中断写入
}
//mDiskLruCache.flush(); flush之后才会被记录到Journal文件中
}
注意:方法newOutputStream(int index)有参数,由于前面在设置valueCount的时候指定的是1,所以这里index传0就可以了。在写入操作执行完之后,我们还需要调用一下commit()方法进行提交才能使写入生效,调用abort()方法的话则表示放弃此次写入。
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
//读取磁盘缓存
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
//将缓存数据解析成Bitmap对象
Bitmap bitmap = null;
if (imageView != null || view != null) {
if (fileDescriptor != null) {
//据说decodeFileDescriptor 解析速度会比较快
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
}
接下来在简单说说 LRUCache
LRUCache没什么好说的 源码也比较简单 抽空大家自己看看
//创建内存缓存区域
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
LruCache mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
完整代码
public class ImageCacheUtils {
//需要依赖 compile 'com.jakewharton:disklrucache:2.0.2'
private final String TAG = "ImageCacheUtils";
/**
* 记录所有正在下载或等待下载的任务。
*/
private Set taskCollection;
/**
* 图片内存缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
* 《官方推荐LruCache》
*/
private LruCache mMemoryCache;
/**
* 图片硬盘缓存核心类。
*/
private DiskLruCache mDiskLruCache;
public ImageCacheUtils(Context context) {
taskCollection = new HashSet<>();
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
try {
// 获取图片缓存路径
File cacheDir = getDiskCacheDir(context, "thumb");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache
.open(cacheDir, getAppVersion(context), 1, 100 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将一张图片存储到LruCache中。
*
* @param key LruCache的键,这里传入图片的URL地址。
* @param bitmap LruCache的键,这里传入从网络上下载的Bitmap对象。
*/
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
*/
private Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
public void removeCache() {
mMemoryCache.evictionCount();
mMemoryCache.evictAll();
}
/**
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
*
* @param imageView 对应显示的控件 imageView
* @param imageUrl 图片地址
* @param view 可以为空,如果是ListView GridVIew等 imageView需要设置Tag img.setTag(imageUrl);
*/
public void loadBitmaps(@NonNull ImageView imageView, @NonNull String imageUrl, ViewGroup view) {
Bitmap bitmap;
String key = hashKeyForDisk(imageUrl);
bitmap = getBitmapFromMemoryCache(key);
if (bitmap == null) {
DiskLruCache.Snapshot snapshot;
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
try {
snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
BitmapWorkerTask task;
if (view != null)
task = new BitmapWorkerTask(view);
else
task = new BitmapWorkerTask(imageView);
taskCollection.add(task);
task.execute(imageUrl);
} else {
fileInputStream = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
try {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
} catch (OutOfMemoryError e) {
removeCache();
}
imageView.setImageBitmap(bitmap);
addBitmapToMemoryCache(key, bitmap);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
if (imageView != null)
imageView.setImageBitmap(bitmap);
}
}
/**
* @param imageUrl url
* 可以用于后台预先缓存 不需ui显示
*/
public void loadBitmaps(String imageUrl) {
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapshot;
try {
snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
BitmapWorkerTask task;
task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(imageUrl);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* bitmap转byteArr
*
* @param bitmap bitmap对象
* @return 字节数组
*/
private static byte[] bitmap2Bytes(Bitmap bitmap) {
if (bitmap == null) return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
/**
* @param imgUrl
* @return 从缓存中获取Bitmap 转 Byte[]()
*/
public byte[] getBitmapByte(String imgUrl) {
Bitmap bitmap;
byte[] bimapbyt;
String key = hashKeyForDisk(imgUrl);
DiskLruCache.Snapshot snapshot;
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
try {
snapshot = mDiskLruCache.get(key);
fileInputStream = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
bimapbyt = bitmap2Bytes(bitmap);
return bimapbyt;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 取消所有正在下载或等待下载的任务。
*/
public void cancelAllTasks() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
}
}
}
/**
* 根据传入的uniqueName获取硬盘缓存的路径地址。
*/
private File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* 获取当前应用程序的版本号。
*/
private int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (Exception e) {
e.printStackTrace();
}
return 1;
}
/**
* 使用MD5算法对传入的key进行加密并返回。
*/
private String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
// Log.e(TAG, "hashKeyForDisk: ***********************MD5 " + key + " ?? " + cacheKey);
} 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();
}
/**
* 将缓存记录同步到journal文件中。
*/
public void fluchCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// // Raw height and width of image
// // 源图片的高度和宽度
// final int height = options.outHeight;
// final int width = options.outWidth;
// int inSampleSize = 1;
// if (height > reqHeight || width > reqWidth) {
// // 计算出实际宽高和目标宽高的比率
// final int heightRatio = Math.round((float) height / (float) reqHeight);
// final int widthRatio = Math.round((float) width / (float) reqWidth);
// // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// // 一定都会大于等于目标的宽和高。
// inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
// }
// return inSampleSize;
// }
/**
* 异步下载图片的任务。
*
* @author guolin
*/
private class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private ViewGroup view;
private ImageView imageView;
private BitmapWorkerTask(ViewGroup view) {
this.view = view;
}
private BitmapWorkerTask(ImageView imageView) {
this.imageView = imageView;
}
private BitmapWorkerTask() {
}
/**
* 图片的URL地址
*/
private String imageUrl;
/**
* @param params 图片地址
* @return bitmap
*/
@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapShot;
try {
final String key = hashKeyForDisk(imageUrl);
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 (imageView != null || view != null) {
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。
if (view != null) {
ImageView v = (ImageView) view.findViewWithTag(imageUrl);
if (v != null && bitmap != null) {
v.setImageBitmap(bitmap);
addBitmapToMemoryCache(hashKeyForDisk(imageUrl), bitmap);
}
} else {
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
addBitmapToMemoryCache(hashKeyForDisk(imageUrl), bitmap);
}
}
//下载完成 移除任务
taskCollection.remove(this);
}
/**
* 建立HTTP请求,并获取Bitmap对象。
*
* @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();
// 设置连接主机超时时间
urlConnection.setConnectTimeout(5 * 1000);
//设置从主机读取数据超时
urlConnection.setReadTimeout(5 * 1000);
urlConnection.connect();
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;
}
}
}