Bitmap的加载和Cache

目前比较常用的缓存策略是LruCache(Android3.1提供)和DiskLruCache(是官方文档推荐,但不属于Android SDK,需要自行下载源码编译)。

下载地址: https://android.googlesource.com/platform/libcore/+/android-4.1.1-r1/luni/src/main/java/libcore/io/DiskLruCache.java。

LruCache常被用作内存缓存,而DiskLruCache常被用作磁盘缓存。Lru是Least Recently Used的缩写,

LurCache内部其实是用了一个LinkedHashMap来存储数据的,它的构造如下:

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方法后,会将这次访问的元素移至链表尾部,这样最前面的一个元素就是最近最少使用的了。当元素数量达到指定的最大数量之后是怎么删除的呢?主要是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。

Bitmap 的加载主要是通过BItmapFactory,BitmapFactory有4中方法加载Bitmap:decodeFile,decodeResource,decodeStream和decodeByteArray。其中decodeFile,decodeResource间接调用了decodeStream方法。

很多时候我们图片的大小是大于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){

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;

}

传入的options为解析后的options,其中这个算法的关键部分可以仔细体会一下。

LruCache的典型初始化代码:

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。

DiskLruCache提供了open方法来创建自身,如下所示:

public static DiskLruCache open(File dir, int appVersion, int valueCount, long maxSize);

dir表示缓存的文件的目录。

appVersion表示应用的版本号,一般设为1,版本号发生改变时,DiskLruCache会清空之前所有的缓存文件。

valueCount 表示耽搁节点所对应的数据的个数,一般设为1即可。

maxSize表示缓存的最大值,比如50MB,但是要转换成byte。

DiskLruCache缓存的添加:

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();

DiskLruCache缓存的查找:

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():删除所有缓存文件。

其他方法可自行查阅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容