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
然后重新加载图片。
示例代码
1234567891011121314151617181920212223242526272829303132333435363738394041
public 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 典型示例代码:
12345678910111213141516
// 获取当前可用的最大内存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 的创建
1234567
/*** @param directory 缓存路径* @param appVersion 一般为 1,当版本号变更时,会清空缓存文件* @param valueCount 单个节点所对应的数据个数,一般为 1* @param maxSize 缓存总大小,超过的话会清除一些缓存*/ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
- DiskLruCache 的缓存添加
1234567891011
String 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 的缓存查找
123456789101112
Bitmap 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
中执行耗时操作。 - 控制异步任务的执行频率,例如列表滚动的时候不加载图片,等完全停止时才开始加载。
- 如果仍有卡顿现象,可以尝试开启硬件加速。
原文地址:https://www.cnblogs.com/petewell/p/11597445.html
时间: 2024-11-13 08:17:30