Bitmap 的加载和 Cache
Posted petewell
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bitmap 的加载和 Cache相关的知识,希望对你有一定的参考价值。
android 中如何高效地加载 Bitmap 是一个很重要也很容易被我们忽视的问题。
Bitmap 的高效加载
- BitmapFactory 类提供了:
decodeFile
、decodeResource
、decodeStream
、decodeByteArray
以及decodeFileDescriptor
等几类方法来加载一个 Bitmap 对象。 - 高效加载 Bitmap 的核心思想就是设置
BitmapFactory.Options
的inSampleSize
采样率属性来加载所需尺寸的图片。inSampleSize = 1 时加载原图,inSampleSize = 2 时加载的像素为原图的 1/4,以此类推。官方推荐设置 inSampleSize 的值为 2 的指数。
获取采样率的流程:
- 将
BitmapFactory.Options
的inJustDecodeBounds
参数设为true
并加载图片。 - 从
BitmapFactory.Options
中取出图片的原始宽高信息,它们对应于outWidth
和outHeight
。 - 根据采样率的规则并结合目标 View 的所需大小计算出采样率
inSampleSize
。 将
BitmapFactory.Options
的inJustDecodeBounds
参数设为false
然后重新加载图片。示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public Bitmap (Resources res,
int resId, int reqWidth, int reqHeight) {
// 并不会真正地去加载图片
final 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 int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// 图片的原始宽高信息
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
- 将
Android 中的缓存策略
- 缓存策略并没有统一的标准,一般来说缓存策略主要包含缓存的添加、获取和删除这三类操作。
- 常用的缓存算法是 LRU(Least Recently Used),翻译为:近期最少使用算法。它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
- Android 中常见的 LRU 算法缓存有两种:LruCache(内存缓存) 和 DiskLruCache(存储设备缓存),一般情况下会将两者结合使用。
LruCache
LruCache 是一个泛型类,内部采用一个
LinkedHashMap<K, V>
以强引用的方式存储外界的缓存对象,提供了get
和put
等操作方法,当存储满时,会移除较早使用的缓存对象,再添加新的缓存对象。此外,LruCache 是线程安全的。三种引用的区别:
- 强引用:直接的对象引用
- 软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被 gc 回收
- 弱引用:当一个对象只有弱引用存在时,此对象会随时被 gc 回收
LruCache 典型示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 获取当前可用的最大内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // KB
// 设置缓存大小
int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> lruCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap value) {
// 重写此方法计算缓存对象的大小
return value.getRowBytes() * value.getHeight() / 1024;
}
};
// 添加缓存
lruCache.put("liyu", bitmap);
// 获取缓存
Bitmap bitmap = lruCache.get("liyu");
DiskLruCache
DiskLruCache 用于实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存的效果。项目地址:https://github.com/JakeWharton/DiskLruCache
DiskLruCache 的创建
1
2
3
4
5
6
7/**
* @param directory 缓存路径
* @param appVersion 一般为 1,当版本号变更时,会清空缓存文件
* @param valueCount 单个节点所对应的数据个数,一般为 1
* @param maxSize 缓存总大小,超过的话会清除一些缓存
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)DiskLruCache 的缓存添加
1
2
3
4
5
6
7
8
9
10
11String key = hashKeyFormUrl(url); // url 转换下防止特殊字符影响
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX); // DISK_CACHE_INDEX 为 0,因为设置的一个节点只有一个数据
if (downloadUrlToStream(url, outputStream)) { //下载
editor.commit(); // 下载成功提交缓存
} else {
editor.abort(); // 出现异常则取消缓存
}
mDiskLruCache.flush(); // 强制缓冲文件保存到文件系统
}DiskLruCache 的缓存查找
1
2
3
4
5
6
7
8
9
10
11
12Bitmap bitmap = null;
String key = hashKeyFormUrl(url); // url 转换下防止特殊字符影响
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); // 获取缓存快照
if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX); // DISK_CACHE_INDEX 为 0,因为设置的一个节点只有一个数据
FileDescriptor fileDescriptor = fileInputStream.getFD(); // 解决 decodeStream 缩放 bitmap 第二次为 null 的问题
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight); // 缩放图片
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap); // 添加到内存缓存,方便下次快速获取
}
}
ImageLoader 的实现
一个优秀的图片加载器应具备:
- 图片的同步加载
- 图片的异步加载
- 图片压缩
- 内存缓存
- 磁盘缓存
- 网络拉取
完整的 ImageLoader 示例可以参考源码
ImageLoader 使用
- 非 WiFi 环境下使用时应有提示,避免消耗过多流量。
- 列表视图例如 ListView,不要在
getView
中执行耗时操作。 - 控制异步任务的执行频率,例如列表滚动的时候不加载图片,等完全停止时才开始加载。
- 如果仍有卡顿现象,可以尝试开启硬件加速。
以上是关于Bitmap 的加载和 Cache的主要内容,如果未能解决你的问题,请参考以下文章