这主要是记录一下Android中的图片缓存的基本策略,实现一个粗糙的图片加载框架
Android的图片加载框架都是万变不离其宗嘛,Bitmap的压缩裁剪,LruCache和DiskLruCache缓存策略的使用,ThreadPoolExecutor线程池的使用,其它各种贴心优化等等。
LruCache这个不多说了可以看我历史悠久的文章LruCach解析和使用 ,或者百度谷歌一下郭神的博客。主要说一下DiskLruCache的使用和结合实现图片加载。
DiskLruCache
DiskLruCache顾名思意磁盘缓存,与LruCache是一对好兄弟,在Android中图片框架中几乎形影不离。不同的是DiskLruCache不是google官方所写,但是得到了官方推荐,DiskLruCache没有编写到SDK中去,如需使用可直接copy这个类到项目中去。如何使用DiskLruCache呢,需要自己手动导入项目中才能使用。
DiskLruCache下载地址:
https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
https://github.com/JakeWharton/DiskLruCache
现在这个时候使用AS的推荐使用JakeWharton大神的Github链接在gradle中直接引入
compile 'com.jakewharton:disklrucache:2.0.2'
DiskLruCache使用
DiskLruCache并不能通过构造方法来创建,它提供了opean方法用于创建自身,如下所示:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
open()方法接收四个参数:
第一个参数:指定的是数据的缓存地址
第二个参数:指定当前应用程序的版本号(如果版本发生改变会清空之前的缓存文件,在实际开发中一般我们并不需要缓存文件随着版本号的改变而清空数据,所以一般设为1即可)
第三个参数:指定同一个key可以对应多少个缓存文件,基本都是传1
第四个参数:指定最多可以缓存多少字节的数据(超过这个设定值后会自动清除一些缓存数据来保证总大小不大于这个设定值)
数据缓存通常都会存放在 /sdcard/Android/data/<package_name>/cache 这个路径下,其中package_name表示当前应用的包名,当应用被卸载后,此目录会一并被删除。这个缓存路径可以根据需求灵活设置路径,一般如果希望应用卸载后就希望删除缓存文件的,那么就选择sdcard上的缓存目录,如果希望保留缓存数据的不随着应用卸载而没有的那就应该选择sdcard上的其他特定目录。现在的手机有些可能没有SD卡,或者没内存了。一般代码都会做判断。如下:
/**
* 获取磁盘缓存地址
*
* @param ctx
* @param uniqueName 唯一标示名
* @return
*/
private File getDiskCacheDir(Context ctx, String uniqueName) {
boolean externalAvailable = (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
|| !Environment.isExternalStorageRemovable());
String cachePath;
if (externalAvailable) {
cachePath = ctx.getExternalCacheDir().getPath();///sdcard/Android/data/package_name/cache 这个路径
} else {
cachePath = ctx.getCacheDir().getPath(); ///data/data/package_name/cache 这个路径。
}
return new File(cachePath + File.separator + uniqueName);
}
DiskLruCache的一些常用方法
方法 | 备注 |
---|---|
DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) | 打开一个缓存目录,如果没有则首先创建它,directory:指定数据缓存地址 appVersion:APP版本号,当版本号改变时,缓存数据会被清除 valueCount:同一个key可以对应多少文件 maxSize:最大可以缓存的数据量 |
Editor edit(String key) | 通过key可以获得一个DiskLruCache.Editor,通过Editor可以得到一个输出流,进而缓存到本地存储上 |
void flush() | 强制缓冲文件保存到文件系统 |
Snapshot get(String key) | 通过key值来获得一个Snapshot,如果Snapshot存在,则移动到LRU队列的头部来,通过Snapshot可以得到一个输入流InputStream |
boolean remove(String key) | 根据key值来删除对应的数据,如果该数据正在被编辑,则不能删除 |
void delete() | 关闭缓存并且删除目录下所有的缓存数据,即使有的数据不是由DiskLruCache 缓存到本目录的 |
void close() | 关闭DiskLruCache,缓存数据会保留在外存中 |
boolean isClosed() | 判断DiskLruCache是否关闭,返回true表示已关闭 |
long size() | 缓存数据的大小,单位是byte |
File getDirectory() | 缓存数据的目录 |
一些关于DiskLruCache的API使用呢,由于篇幅限制不单独写代码解释,如有不懂的可以看Android DiskLruCache完全解析,硬盘缓存的最佳方案。这篇主要是结合来实现图片加载缓存框架,关于简单的图片缓存加载步骤,理论上应该分为以下几点:
- 先根据图片地址从内存(LruCache)中查找是否有该缓存,有则直接读取显示
- 内存中没有在从磁盘中(DiskLruCache)中查找是否有该缓存,有则直接显示,并存入内存中
- 上面两者中都没得话,从网上下载并缓存到磁盘和内存中
- 如果没有磁盘SD卡的话,或者磁盘没有多余的容量,则只能从网上直接拉取显示
ImageLoader实现
- gradle中引入disklrucache
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'com.jakewharton:disklrucache:2.0.2'
}
- AndroidManifest.xml配置网络权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
- 编写一个简单的图片压缩工具类
/**
* 图片压缩
*/
public class ImageCompress {
public ImageCompress() {
}
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);
//计算inSampleSize 采样率
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);
}
/**
* 获取图片的采样率,缩放比例
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int inSampleSize = 1;
if (reqWidth == 0 || reqHeight == 0)
return inSampleSize;
int height = options.outHeight;
int width = options