Bitmap的加载和Cache,目前比较常用的缓存策略是LruCach和DisLruCache,其中LruCache常被用作内存缓存,而DisLruCache常被用做存储缓存。
Lru是Least Recently Used即最近最少使用算法,这种算法的核心思想是,当缓存快满时,会淘汰近期最少使用的缓存目标。
1.Bitmap的高效加载
加载图片,BitmapFactory类提供了四类方法:
decodeFile,decodeResource,decodeStream,decodeByteArray,分别用于从文件系统,资源,输入流,以及字节数组中加载出一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decodeStream方法。
如何高效的加载图片呢?
核心思想就是采用BitmapFactory.Options来加载所需尺寸的图片。通过BitmapFactory.Options来缩放图片,主要是用到了它的inSampleSize参数,即采样率。
当inSampleSize为1时,采样后的图片大小为图片的原始大小,当inSampleSize大于1时,比如为2,那么采样后的图片其宽高均为原图大小的1/2,而像素为原图的1/4,其占用的内存大小也为原图的1/4.
如一张1024*1024像素的图,采用ARGB8888格式存储,那么它占用的内存为1024*1024*4,即4M,如果inSampleSize为2,那么采样后占用的内存为512*512*4,即1M。inSampleSize的缩放比例为(1/inSampleSize)2。
实际情况:
如果ImageView的大小为100*100像素,而图片的原始大小为200*200,那么只需要将采样率inSampleSize设置为2即可。如果图片为200*300呢,采样率还是设置为2,这样缩放后的图片大小为100*150,仍然是适合的。如果采样率为3,那么缩放后的图片大小就会小于ImageView所期望的大小,这样图片就会被拉伸导致模糊。
通过采样率可以高效的加载图片,如何获取采样率呢?
(1)将BitmapFactory.Options的inJustDecodeBounds参数设置为true并加载图片;
(2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数;
(3)根据采样率的规则并结合目标View的所需大小计算采样率inSampleSize;
(4);将BitmapFactory.Options的inJustDecodeBounds参数设置为false并重新加载图片;
注意:inJustDecodeBounds这个参数为true时,BitmapFactory只会解析图片的原始宽高信息,并不会真正地加载图片,所以这个操作是轻量级的。
另外需要注意的是,这个时候BitmapFactory获取的图片宽高信息和图片的位置以及程序运行的设备有关,比如同一张图片放在不同的Drawable目录下或者程序运行在不同屏幕密度的设备上,都可能导致BitmapFactory获取到不同的结果。
public class ImageResizer {
private static final String TAG = "ImageResizer";
public ImageResizer() {
}
public Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
public int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "origin, w= " + width + " h=" + height);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
Log.d(TAG, "sampleSize:" + inSampleSize);
return inSampleSize;
}
}
2.Android中的缓存策略
2.1.LruCache
LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象。
强引用:直接的对象引用;
软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收;
弱引用:当一个对象只有软引用存在时,此对象会随时被gc回收;
另外LruCache是线程安全的。
经典的初始化代码:
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
sizeof方法的作用是计算缓存对象的大小,这里大小的单位需要和总容量的单位一致。上面总容量为当前进程可用内存的1/8,单位为KB,sizeof方法则完成了Bitmap对象的大小计算。除以1024也是把单位转换为KB。