一、Bitmap的高效加载
a.核心思想
采用BitmapFactory.Options按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM,提高了Bitmap加载时的性能。
b. 工具类
BitmapFactory类提供了四类方法:decodeFile、decodeResource、decodeStream和decodeByteArray
decodeFile():
从文件系统加载出一个Bitmap对象decodeResource():
从资源文件加载出一个Bitmap对象decodeStream():
从输入流加载出一个Bitmap对象decodeByteArray():
从字节数组加载出一个Bitmap对象
二、缓存策略
缓存策略主要包含缓存的添加、获取和删除这三类操作。
Q1:
为什么要删除缓存呢?
因为内存缓存和存储设备的缓存大小有容量限制,当缓存容量满了,但是程序还需要向其添加缓存,就需要删除一些旧的缓存并添加新的缓存。
1. LRU缓存算法
核心思想:
当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
分类:
LruCache和DiskLruCache
1.1 LruCache
实现原理:LruCache是一个泛型类
,它内部采用一个LinkedHashMap
以强引用
的方式存储外界的缓存对象,其提供了get
和put
方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象。
注:几种引用的含义
强引用:直接的对象引用,不会被gc回收;
软引用:当系统内存不足时,对象会被gc回收;
弱引用:随时会被gc回收。
使用方法:
- 计算当前可用的内存大小;
- 分配LruCache缓存容量;
- 创建LruCache对象并传入最大缓存大小的参数、重写sizeOf()用于计算每个缓存对象的大小;
- 通过put()、get()和remove()实现数据的添加、获取和删除。
实例:
//初始化LruCache对象
public void initLruCache()
{
//1.获取当前进程的可用内存,转换成KB单位
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
//2.分配缓存的大小
int maxSize = maxMemory / 8;
//3.创建LruCache对象并重写sizeOf方法
lruCache = new LruCache<String, Bitmap>(maxSize)
{
@Override
protected int sizeOf(String key, Bitmap value) {
// TODO Auto-generated method stub
return value.getWidth() * value.getHeight() / 1024;
}
};
}
//4.LruCache对数据的操作
public void fun()
{
//添加数据
lruCache.put("lizhuo", bm1);
lruCache.put("sushe", bm2);
lruCache.put("jiqian", bm3);
//获取数据
Bitmap b1 = (lruCache.get("lizhuo"));
Bitmap b2 = (lruCache.get("sushe"));
Bitmap b3 = (lruCache.get("jiqian"));
//删除数据
lruCache.remove("sushe");
}
1.2 DiskLruCache
介绍:
DiskLruCache用于实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存的效果。
与LruCache区别:
DiskLruCache非泛型类
,不能添加类型,而是采用文件存储
,存储和读取通过I/O流处理。
使用方法:
- 计算分配DiskLruCache的容量;
- 设置缓存目录;
- 创建DiskLruCache对象,注意不能通过构造方法来创建, 而是提供open()方法;
- 利用Editor、Snapshot和remove()实现数据的添加、获取和删除。
- 调用flush()将数据写入磁盘。
(1)DiskLruCache的创建
public static DiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)
参数解读
:
①directory:磁盘缓存的存储路径。有两种目录:
SD 上的缓存目录:/sdcard/Android/data/package_name/cache
目录,当应用被卸载后会被删除。
其他目录:应用卸载后缓存数据还在。
②appVersion:当前应用的版本号,一般设为1。
③valueCount:单个节点所对应的数据的个数,一般设为1。
④maxSize:缓存的总大小,超出这个设定值后DiskLruCache会清除一些缓存
典型的DiskLruCache的创建过程如下:
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
//若缓存地址的路径不存在就创建一个
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
//用于获取到缓存地址的路径
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {
//当SD卡存在或者SD卡不可被移除,获取路径 /sdcard/Android/data/<application package>/cache
cachePath = context.getExternalCacheDir().getPath();
} else {
//反之,获取路径/data/data/<application package>/cache
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
//用于获取到当前应用程序的版本号
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
(2)添加缓存操作:通过Editor完成
- 获取资源的key值,采用url的md5值作为key;
- 通过
DiskLruCache.edit()
获取对应key的Editor; - 通过
Editor.newOutputStream(0)
得到一个输出流; - 通过OutputStream写入数据;
Editor.commit()
提交写操作,若发生异常,则调用Editor.abort()
进行回退。
核心代码:
//1.返回url的MD5算法结果
String key = hashKeyFormUrl(url);
//2.获取Editor对象
Editor editor = mDiskLruCache.edit(key);
//3.创建输出流,其中常量DISK_CACHE_INDEX = 0
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
//4.写入数据
outputStream.wirte(data);
//5.提交写操作
editor.commit();
(3)查找缓存操作:和缓存添加的过程类似
- 获取资源的key值,采用url的md5值作为key;
- 通过DiskLruCache.get()获取对应key的Snapshot对象;
- 通过Snapshot.getInputStream(0)得到一个输入流(可向下转型为FileInputStream);
- 通过InputStream读取数据。
核心代码:
//1.返回url的MD5算法结果
String key = hashKeyFormUrl(url);
//2.获取Snapshot对象
Snapshot snapshot = mDiskLruCache.get(key);
//3.创建输入流,其中常量DISK_CACHE_INDEX = 0
InputStream inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
//4.读出数据
int data = inputStream.read();
补充
Q:
FileInputStream是一种有序的文件流,调用两次 BitmapFactory.decodeStream()
会影响文件流的位置属性,导致第二次解析结果为空。
A:
通过文件流得到其对应的文件描述符,再调用 BitmapFactory.decodeFileDescriptor()
来加载一张缩放后的图片。
2. ImageLoader的实现
a.介绍: ImageLoader内部封装了Bitmap的高效加载、LruCache和DiskLruCache。
b.应具备功能:
- 同步加载
- 异步加载
- 图片压缩
- 内存缓存
- 磁盘缓存
- 网络拉取
c.使用场景:
- 实现照片墙效果 ,此处实例
- 优化 ListView/GridView卡顿现象,几点办法:
(1) 不要在Adapter的getView()中执行耗时操作,比如直接加载图片。
(2) 控制异步任务的执行频率,在列表滑动时停止加载图片,而列表停下时再加载图片,此处实例。
(3) 开启硬件加速,给Activity添加配置android:hardwareAccelerated=“true”。
android中listview卡顿的终极原因解析