Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >android bitmap的缓存策略

android bitmap的缓存策略

作者头像
MelonTeam
发布于 2018-01-04 08:58:20
发布于 2018-01-04 08:58:20
1.2K10
代码可运行
举报
文章被收录于专栏:MelonTeam专栏MelonTeam专栏
运行总次数:0
代码可运行

不论是android还是ios设备,流量对于用户而言都是宝贵的。在没有wifi的场景下,如果加载批量的图片消耗用户过多流量,被其知晓,又要被念叨一波~

如何避免消耗过多的流量呢?当程序第一次从网络加载图片后,就将其缓存到移动设备上,这样再次使用这个图片时,就不用再次从网络上下载为用户节省了流量。

 目前常用的一种缓存算法是lru(least recently used),它的核心思想是当缓存满了,会优先淘汰近期最少使用的缓存对象。采用lru算法的缓存有两种:lrucache和disklrucache,lrucache主要用于实现内存缓存,disklrucache则用于存储设备缓存。

1.lrucache

lrucache是api level 12提供的一个泛型类,它内部采用一个linkedhashmap以强引用的方式存储外界的缓存对象,提供了get和put方法来完成缓存的获取和添加操作,当缓存满了,lrucache会remove掉较早使用的缓存对象,然后再添加新的对象。

过去实现内存缓存的常用做法是使用softreference或者使用weakreference,但是并不推荐这种做法,从api level 9以后,gc强制回收掉soft、weak引用,从而导致这些缓存并没有任何效率的提升。

lrucache的实现原理:

根据lru的算法思想,我们需要一种数据结构来快速定位哪个对象是最近访问的,哪个对象是最长时间未访问的,lrucache选择的是linkedhashmap这个数据结构,它是一个双向循环链表。来瞅一眼linkedhashmap的构造函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** 初始化linkedhashmap

     * 第一个参数:initialcapacity,初始大小

     * 第二个参数:loadfactor,负载因子=0.75f

     * 第三个参数:accessorder=true,基于访问顺序;accessorder=false,基于插入顺序<br/>**/

   public linkedhashmap(int initialcapacity, float loadfactor, boolean accessorder) {

       super(initialcapacity, loadfactor);

       init();

       this.accessorder = accessorder;

    }

所以在lrucache中应该选择accessorder = true,当我们调用put、get方法时,linkedhashmap内部会将这个item移动到链表的尾部,即在链表尾部是最近刚刚使用的item,链表头部就是最近最少使用的item。当缓存空间不足时,可以remove头部结点释放缓存空间。

下面举例lrucache的典型使用姿势

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;

    }

};

 <br/>// 向 lrucache 中添加一个缓存对象

private void addbitmaptomemorycache(string key, bitmap bitmap) {

    if (getbitmapfrommemcache(key) == null) {

        mmemorycache.put(key, bitmap);

    }

}



//获取一个缓存对象

private bitmap getbitmapfrommemcache(string key) {

    return mmemorycache.get(key);

}</string>

上述示例代码中,总容量的大小是当前进程的可用内存的八分之一(官方推荐是八分之一哈,你们可以自己视情况定),sizeof()方法计算了bitmap的大小,sizeof方法默认返回的是你缓存item数目,源码中直接return 1(这里的源码比较简单,可以自己看看~)。

如果你需要cache中某个值释放,可以重写entryremoved()方法,这个方法会在元素被put或者remove的时候调用,源码默认是空实现。重写entryremoved()方法还可以实现二级内存缓存,进一步提高性能。思路如下:重写entryremoved(),把删除掉的item,再次存入另一个linkedhashmap中。这个数据结构当做二级缓存,每次获得图片的时候,按照一级缓存 、二级缓存、sdcard、网络的顺序查找,找到就停止。

2.disklrucache

当我们需要存大量图片的时候,我们指定的缓存空间可能很快就用完了,lrucache会频繁地进行trimtosize操作将最近最少使用的数据remove掉,但是hold不住过会又要用这个数据,又从网络download一遍,为此有了disklrucache,它可以保存这些已经下载过的图片。当然,从磁盘读取图片的时候要比内存慢得多,并且应该在非ui线程中载入磁盘图片。disklrucache顾名思义,实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存效果。

ps: 如果缓存的图片经常被使用,可以考虑使用contentprovider。

disklrucache的实现原理:

lrucache采用的是linkedhashmap这种数据结构来保存缓存中的对象,那么对于disklrucache呢?由于数据是缓存在本地文件中,相当于是持久保存的一个文件,即使app kill掉,这些文件还在滴。so ,,,,, 到底是啥?disklrucache也是采用linekedhashmap这种数据结构,但是不够,需要加持buff

日志文件。日志文件可以看做是一块“内存”,map中的value只保存文件的简要信息,对缓存文件的所有操作都会记录在日志文件中。

disklrucache的初始化:

下面是disklrucache的创建过程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final long disk_cache_size = 1024 * 1024 * 50; //50mb 

    

    file diskcachedir = getdiskcachedir(mcontext, "bitmap");

    if (!diskcachedir.exists()) {

        diskcachedir.mkdirs();

    }



    if (getusablespace(diskcachedir) > disk_cache_size) {

        try {

            mdisklrucache = disklrucache.open(diskcachedir, 1, 1,

                    disk_cache_size);

        } catch (ioexception e) {

            e.printstacktrace();

        }

    }

瞅了一眼,可以知道重点在open()函数,其中第一个参数表示文件的存储路径,缓存路径可以是sd卡上的缓存目录,具体是指/sdcard/android/data/package_name/cache,package_name表示当前应用的包名,当应用被卸载后, 此目录会一并删除掉。如果你希望应用卸载后,这些缓存文件不被删除,可以指定sd卡上其他目录。第二个参数表示应用的版本号,一般设为1即可。第三个参数表示单个结点所对应数据的个数,一般设为1。第四个参数表示缓存的总大小,比如50mb,当缓存大小超过这个设定值后,disklrucache会清除一些缓存保证总大小不会超过设定值。

disklrucache的数据缓存与获取缓存:

数据缓存操作是借助disklrucache.editor类完成的,editor表示一个缓存对象的编辑对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new thread(new runnable() {  

    @override  

    public void run() {  

        try {  

            string imageurl = "http://d.url.cn/myapp/qq_desk/friendprofile_def_cover_001.png";  

            string key = hashkeyfordisk(imageurl);  //md5对url进行加密,这个主要是为了获得统一的16位字符

            disklrucache.editor editor = mdisklrucache.edit(key);  //拿到editor,往journal日志中写入dirty记录

            if (editor != null) {  

                outputstream outputstream = editor.newoutputstream(0);  

                if (downloadurltostream(imageurl, outputstream)) {  //downloadurltostream方法为下载图片的方法,并且将输出流放到outputstream

                    editor.commit();  //完成后记得commit(),成功后,再往journal日志中写入clean记录

                } else {  

                    editor.abort();  //失败后,要remove缓存文件,往journal文件中写入remove记录

                }  

            }  

            mdisklrucache.flush();  //将缓存操作同步到journal日志文件,不一定要在这里就调用

        } catch (ioexception e) {  

            e.printstacktrace();  

        }  

    }  

}).start();

上述示例代码中,每次调用edit()方法时,会返回一个新的editor对象,通过它可以得到一个文件输出流;调用commit()方法将图片写入到文件系统中,如果失败,通过abort()方法进行回退。

而获取缓存和缓存的添加过程类似,将url转换为key,然后通过disklrucache的get方法得到一个snapshot对象,接着通过snapshot对象得到缓存的文件输入流。有了文件输入流,bitmap就get到了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    bitmap bitmap = null;

    string key = hashkeyformurl(url);

    disklrucache.snapshot snapshot = mdisklrucache.get(key);

    if (snapshot != null) {

        fileinputstream fileinputstream = (fileinputstream)snapshot.getinputstream(disk_cache_index);

        filedescriptor filedescriptor = fileinputstream.getfd();

        bitmap = mimageresizer.decodesampledbitmapfromfiledescriptor(filedescriptor,

                reqwidth, reqheight);

        ......

    }

disklrucache优化思考:

disklrucache是基于日志文件的,每次对缓存文件操作都需要进行日志记录,我们可以不用日志文件,在第一次构造disklrucache时,直接从程序访问缓存目录下的文件,并将每个缓存文件的访问时间作为初始值记录在map中的value值,每次访问或保存缓存都更新相应key对应的缓存文件的访问时间,避免了频繁地io操作。

3. 缓存策略对比与总结

  • lrucache是android中已经封装好的类,disklrucache需要导入相应的包才可以使用。
  • 可以在ui线程中直接使用lrucache;使用disklrucache时,由于缓存或者获取都需要对本地文件进行操作,因此要在子线程中实现。
  • lrucache主要用于内存缓存,当app kill掉的时候,缓存也跟着没了;而disklrucache主要用于存储设备缓存,app kill掉的时候,缓存还在。
  • lrucache的内部实现是linkedhashmap,对于元素的添加或获取用put、get方法即可。而disklrucache是通过文件流的形式进行缓存,所以对于元素的添加或获取通过输入输出流来实现。

文章写得比较匆忙,如果有错别字或者理解错的,欢迎和楼主交流~谢谢

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-04-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
几个小表情,还是蛮到位!不写代码的时候还是可以伪装下PS的!
几个小表情,还是蛮到位!不写代码的时候还是可以伪装下PS的!
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
ImageLoader
这次做一个图片加载器,里面涉及到线程池,bitmap的高效加载,LruCache,DiskLruCache。接下来我先介绍这四个知识点
提莫队长
2019/02/21
4810
Bitmap的加载和Cache
“Bitmap,表示位图,由像素点构成。Bitmap的承载容器是jpg、png等格式的文件,是对bitmap的压缩。当jpg、png等文件需要展示在手机上的控件时,就会解析成Bitmap并绘制到view上。通常处理图片时要避免过多的内存使用,毕竟移动设备的内存有限。”
胡飞洋
2020/07/23
6690
Android 三重缓存
在 Android 应用中不可避免地要显示很多图片,如果不做处理,不管图片是否显示过,每次启动时都需要从网络拉取,这就极大影响了图片加载速度和浪费用户流量,并且整个应用中的图片内存无法控制在一个总的范围内。因此,图片缓存在一个图片加载模块中很重要并且不可缺少。
全栈程序员站长
2022/08/31
4980
Android 三重缓存
【Android 内存优化】Bitmap 硬盘缓存 ( Google 官方 Bitmap 示例 | DiskLruCache 开源库 | 代码示例 )
在上一篇博客 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 内存复用 | 弱引用 | 引用队列 | 针对不同 Android 版本开发不同的 Bitmap 复用策略 | 工具类代码 ) 中 , 使用 LruCache 缓存内存数据 , 同时兼顾 Bitmap 内存复用 , 使用弱引用 Bitmap 对象集合维护 Bitmap 复用池 , 确保该复用池中的 Bitmap 对象寿命都很短 , 每次 GC 都会清理一遍复用池 ; 当 LruCache 中的数据由于最近不常使用 , 从 LruCache 内存中移除 , 此时将其放入 Bitmap 复用池中 , 将该 Bitmap 对象纳入复用机制管理 ;
韩曙亮
2023/03/27
9990
【Android 内存优化】Bitmap 硬盘缓存 ( Google 官方 Bitmap 示例 | DiskLruCache 开源库 | 代码示例 )
Android照片墙完整版,完美结合LruCache和DiskLruCache
用户1158055
2018/01/05
1.6K0
Android照片墙完整版,完美结合LruCache和DiskLruCache
Android DiskLruCache完全解析,硬盘缓存的最佳方案
本文主要介绍了如何通过LruCache和DiskLruCache两种缓存技术来优化Android应用中照片的加载速度,并分析了缓存中的各种状态,总结了缓存优化的方法论和具体实现细节。
用户1158055
2018/01/05
1.7K0
Android DiskLruCache完全解析,硬盘缓存的最佳方案
高频面试点:Android性能优化之内存优化(下篇)
链接:https://juejin.im/post/5e72b2d151882549236f9cb8
陈宇明
2020/12/16
6830
Android 内存缓存:手把手教你学会LrhCache算法
当 accessOrder 参数设置为true时,存储顺序(遍历顺序) = 外部访问顺序
Carson.Ho
2019/02/22
9670
【Android 内存优化】Bitmap 内存缓存 ( Bitmap 缓存策略 | LruCache 内存缓存 | LruCache 常用操作 | 工具类代码 )
Glide 开源库 : 官方建议凡是使用到 Bitmap 解码 , 显示 , 缓存等操作 , 直接使用 Glide 开源库进行上述操作 , 不建议直接操作 Bitmap 对象 ;
韩曙亮
2023/03/27
2.3K0
[翻译]Bitmap的异步加载和缓存
本文主要介绍了如何在Android开发中实现高效的图片加载和显示。首先介绍了如何通过自定义ImageView和Bitmap类来优化图片加载和显示,然后介绍了如何使用AsyncDrawable和BitmapWorkerTask类来实现异步加载和显示图片,最后给出了一组示例代码和注意事项。
用户1172465
2018/01/08
2K0
android内置存储器memory和第三方外部存储disk管理
http://blog.csdn.net/intbird/article/details/38338713
全栈程序员站长
2022/07/06
3380
DiskLruCache解析
https://github.com/JakeWharton/DiskLruCache
用户1205080
2018/10/18
1K0
LruCache解析
概述 LRU(Least Recently Used),即最近最少使用算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。 该算法被应用在 LruCache和 DiskLruCache,分别用于实现内存缓存和磁盘缓存。 LruCache的介绍 LruCache是个泛型类,主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap中,当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。 LruCache的使用 // 设置LruCa
用户1205080
2018/10/18
8080
LruCache解析
Android 开发艺术探索笔记三
常用的缓存策略:LruCache与DiskLruCache,其中LruCache用作内存缓存,而DiskLruCache用作磁盘缓存。
Yif
2019/12/26
6090
使用LRU算法缓存图片,android 3.0
在您的UI中显示单个图片是非常简单的,如果您需要一次显示很多图片就有点复杂了。在很多情况下 (例如使用 ListView, GridView 或者 ViewPager控件), 显示在屏幕上的图片以及即将显示在屏幕上的图片数量是非常大的(例如在图库中浏览大量图片)。 在这些控件中,当一个子控件不显示的时候,系统会重用该控件来循环显示 以便减少对内存的消耗。同时垃圾回收机制还会 释放那些已经载入内存中的Bitmap资源(假设您没有强引用这些Bitmap)。一般来说这样都是不错的,但是在用户来回滑动屏幕
xiangzhihong
2018/01/30
1.1K0
【专业知识】Android中的磁盘缓存
前言: 在上一篇文章中介绍了内存缓存,内存缓存的优点就是很快,但是它又有缺点: 空间小,内存缓存不可能很大; 内存紧张时可能被清除; 在应用退出时就会消失,做不到离线; 基于以上的缺点有时候又需要另外一种缓存,那就是磁盘缓存。大家应该都用过新闻客户端,很多都有离线功能,功能的实现就是磁盘缓存。 DiskLruCache: 在Android中用到的磁盘缓存大多都是基于DiskLruCache实现的,具体怎么使用呢? 创建一个磁盘缓存对象: public static DiskLruCache open(Fil
程序员互动联盟
2018/03/13
1.1K0
Android性能优化之内存优化
Tips:本篇是《深入探索Android内存优化》的基础篇,如果没有掌握Android内存优化的同学建议系统学习一遍。
做个快乐的码农
2021/11/23
2.8K0
Android性能优化之内存优化
Android训练课程(Android Training) - 高效的显示图片
了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式。如果你不小心,位图可以快速消耗可用的内存预算而导致应用程序崩溃,引发可怕的异常:
张云飞Vir
2020/03/16
3.3K0
android bitmap的内存分配和优化
首先Bitmap在Android虚拟机中的内存分配,在Google的网站上给出了下面的一段话  大致的意思也就是说,在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalv
xiangzhihong
2018/02/01
1.5K0
android bitmap的内存分配和优化
OKHttp源码解析(七)--中阶之缓存机制
Entry内部类是实际用于存储的缓存数据的实体类,每一个url对应一个Entry实体
隔壁老李头
2018/08/30
1.2K0
相关推荐
ImageLoader
更多 >
LV.1
腾讯终端团队
作者相关精选
交个朋友
加入架构与运维工作实战群
高并发系统设计 运维自动化实践
加入架构与运维学习入门群
系统架构设计入门 运维体系构建指南
加入前端趋势交流群
追踪前端新趋势 交流学习心得
换一批
加入讨论
的问答专区 >
高级后端开发工程师擅长3个领域
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    本文部分代码块支持一键运行,欢迎体验
    本文部分代码块支持一键运行,欢迎体验