Bitmap的加载和Cache分析
(1)内部其实是用了一个LinkedHashMap来存储数据。
构造函数如下:
/**
* @param maxSize for caches that do not override {@link #sizeOf}, this is
* the maximum number of entries in the cache. For all other caches,
* this is the maximum sum of the sizes of the entries in this cache.
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75f, true);
}
说明:构造了一个初始容量为0,负载因子为0.75,accessOrder为true LinkedHashMap。accessOrder为true意味着链表中元素的顺序为访问顺序,即调用get方法后,会将这次访问的元素移至链表尾部,这样最前面的一个元素就是最近最少使用的了。
(2)当元素数量达到指定的最大数量后,主要是LinkedHashMap中removeEldestEntry方法删除的。例如可以这样重新这个方法:
final int MAX_ENTRIES = 50;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
说明:这样当put新元素的时候,如果removeEldestEntry返回了true,就会删除最老的那个元素。而返回true 的条件就是LinkedHashMap的size大于我们指定的值。这就是LruCache的实现原理。DiskLruCache类似。
Bitmap 的加载主要是通过BitmapFactory,BitmapFactory有4中方法加载Bitmap:decodeFile,decodeResource,decodeStream和decodeByteArray。其中decodeFile,decodeResource间接调decodeStream方法。
1.很多时候我们图片的大小是大于ImageView的大小的,这个时候我们就需要对Bitmap进行缩放,如何缩放呢?
主要是通过采样率来进行缩放。设置采样率的方式为 BitmapFactory.Options的inSampleSize参数。当inSampleSize为1时,表示是图片的原始大小不缩放当inSampleSize大于1,比如为2时,那么采样后的图片的宽带都为原来的1/2,而像素数为原图的1/4,其占有的内存大小也为1/4。而且采样率必须为大于1的整数才有效果,当小于1时,其作用相当于1,无缩放效果。另外最新的官方文档中指出,inSampleSize的取值应该总是2的指数,比如,1,2,4,8,16等等。如果外界传递给系统的inSampleSize不为2的指数,那么系统会向下取整并选择一个最接近2的指数来代替。比如传3,系统会取2来代替。但是通过验证,这个结论并非在所有的Android版本都适用,建议开发的时候按2的指数取。
(1)将BitmapFactory.Options的inJustDecodeBounds参数设置为true并加载图片。(设置为true之后并不会真正的去加载图片,只会解析图片的原始宽高。这个操作是轻量级的,但是得注意这个操作获取的图片的宽高信息跟图片的位置以及程序运行的设备有关,比如放在不同的Drawable下面或运行在不同分辨率的机器上)。
(2)从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
(3)根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){
inal 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;
}
传入的options为解析后的options,其中这个算法的关键部分仔细体会一下。
int maxMemory = (int) ((Runtime.getRuntime().maxMemory()) / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
sizeOf方法是计算缓存对象大小的。这里大小需要和总容量的单位一致,上面的为KB。
public static DiskLruCache open(File dir, int appVersion, int valueCount, long maxSize);
(1)dir表示缓存的文件的目录。
(2)appVersion表示应用的版本号,一般设为1,版本号发生改变时,DiskLruCache会清空之前所有的缓存文件。
(3)valueCount 表示耽搁节点所对应的数据的个数,一般设为1即可。
(4)maxSize表示缓存的最大值,比如50MB,但是要转换成byte。
DiskLruCache的缓存的添加时通过Editor完成的,Editor表示一个缓存对象的编辑对象。
例如:
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null){
OutputStream outputStream = editor.newOutputSream(index);
}
因为前面设置了valueCount为1,这里index可以直接传0;拿到这个outputSream之后,就可以把从网络上下载下路的文件流写入里面,注意最后写完了,要调一下editor的commit方法,即editor.commit();
Bitmap bitmap = null;
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if(snapShot != null){
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(index);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
之所以通过fileDescriptor是因为,通过采样率来缩放图片之后,会对FileInputStream的缩放存在问题,因为FileInputStream是一种有序的文件流,而两次decodeStream调用影响了文件流的位置属性,导致了第二次decodeStream时得到的是null;所以这里用文件描述符来解决。
另外:mDiskLruCache.remove(key):删除某个文件。
mDiskLruCache.delete():删除所有缓存文件。
参考:
《Android 开发艺术探索》
[1]: http://www.jianshu.com/p/f48158295510