学习内容:
- 如何有效加载 Bitmap
- Android 常用的缓存策略
- LurChche - 内存缓存
- DiskLurCache - 存储缓存
- 优化列表的卡顿现象
1. Bitmap 的高效加载
如何加载图片?
四类方法:
- BitmapFactory.decodeFile / decodeResource / decodeStream / decodeByteArray
- 分别对应从 文件系统 / 资源 / 输入流 / 以及字节数组 中加载 Bitmap 对象
- 关系:decodeFile 和 decodeResource 间接调用了 decodeStream
如何高效加载图片?
- 核心思想:采用 BitmapFactory.Options 加载所需尺寸的图片
- 说明:主要是 inSampleSize 参数,即采样率。inSampleSize 为 1 时,表示原始大小;当 inSampleSize 为 2 时,采样后的图片 宽/高 均变为原来的 1/2,即整个图片缩小为原来的 1/4。inSampleSize 必须大于 1 才能起作用,效果以此类推。
具体方法(获取采样率):
- 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true 并加载图片
- 从 BitmapFactory.Options 中取出图片的原始宽/高信息,对应于 outWidth 和 outHeight 参数
- 计算所需的采样率 inSampleSize
- 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 false,然后重新加载图片
inJustDecodeBounds 参数设为 true 时,BitmapFactory 只会解析图片的原始 宽/高 信息,并不会真正的加载图片
2. Android 中的缓存策略
为什么需要缓存?
两方面原因:提高程序效率 + 节约流量开销
缓存策略
一般来说,缓存策略主要包括 缓存的添加、获取 和 删除 这三个操作。
目前常用的一种缓存算法是 LRU,即最近最少使用算法,核心思想是缓存满了时,优先淘汰最近最少使用的缓存对象。
2.1 LruCache
兼容性
LruCache 是 Android 3.1 提供的一个缓存类,如果需要兼容 3.1 以下的版本,则需要使用 support-v4 兼容包中提供的 LruCache
实现思想
LruCache 是一个泛型类,内部采用一个 LinkedHashMap 以强引用的方式存储外界的缓存对象,通过 get 和 set 方法完成缓存的获取和添加,当缓存满时,LruCache 会移除较早使用的缓存对象,然后添加新的缓存对象。
LruCache 是线程安全的。
关于 强引用、软引用和弱引用:
- 强引用:直接的对象引用
- 软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被 GC 回收
- 弱引用:当一个对象只有弱引用存在时,此对象随时被 GC 回收
原理
留待后续学习。
具体使用
- 创建:提供缓存的总容量大小并重写 sizeOf 方法
- 获取:get 方法
- 添加:put 方法
- 删除:remove 方法删除指定的缓存对象
2.2 DiskLruCache
简述
DiskLruCache 用于实现存储设备缓存,通过将缓存对象写入文件系统从而实现缓存的效果。
源码地址:https://github.com/JakeWharton/DiskLruCache
使用方式
DiskLruCache 的创建
- 通过
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
方法创建自身。 - 参数:
- directory:磁盘缓存在文件系统中的存储路径
- appVersion:应用的版本号,一般设为 1 即可
- valueCount:单个节点所对应的数据的个数,一般设为 1 即可
- maxSize:缓存总大小,超过这个设定值后,会清除一些缓存
典型代码如下:
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50; //50MB File diskCacheDir = getDiskCacheDir(mContext,"bitmap"); if (!diskCacheDir.exists()) { distCacheDir.mkdirs(); } mDiskLurCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
- 通过
DiskLruCache 的缓存添加
- 核心:通过 Editor 完成,Editor 表示一个缓存对象的编辑对象
步骤:
获取图片 url 对应的 key
public static String hashKeyFromUrl(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 static 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(); }
根据 key 通过 edit() 获取 Editor 对象,得到输出流,并再下载图片时通过该输出流写入到文件系统,最后通过 commit() 提交。
注意:此部分应当通过 子线程 执行,避免下载图片造成 ANR;String key = Util.hashKeyFromUrl(url); //得到DiskLruCache.Editor DiskLruCache.Editor editor = diskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(Util.IMG_URL, outputStream)) { publishProgress(""); //写入缓存 editor.commit(); } else { //写入失败 editor.abort(); } }
关于下载图片:
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(), IO_BUFFER_SIZE); out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE); int b; while ((b = in.read()) != -