简介
由于Bitmap的特殊性以及Android对单个应用所施加的内存限制,比如16MB,导致加载Bitmap时很容会议出现内存溢出。如何高效加载Bitmap是一个很重要也容易被开发者忽视的问题。
Android中缓存策略是一个通用的思想,实际开发中经常需要用到Bitmap做缓存。通过缓存策略我么不需要每次都从网络上请求图片或者从存储设备中加载图片。目前比较常用的缓存策略时LruCache和DiskLruCache,其中LruCache常被用作内存缓存,而DiskLruCache常被用作存储缓存。Lru是最近最少使用算法,这种算法的核心思想为:当缓存快满时,会淘汰近期最少使用的缓存目标。
Bitmap的高效加载
如何加载一个Bitmap
Bitmap在Android中指的是一张图片,可以是png,jpg等常见的图片格式。BitmapFactory提供了四类方法加载Bitmap:decodeFile decodeResource decodeStream decodeByteArray,分别用于从文件系统、资源、输入流以及字节数组加载出一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decodeStream方法,这四类方法最终在Android的底层实现,对应着BitmapFactory类的几个native方法。
如何高效加载Bitmap
核心思想:采用BitmapFactory.Options
来加载所需尺寸的图片,它可以按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM,上面的四类方法都支持Options参数。
通过Options来缩放图片,主要是用到它的inSampleSize
参数,即采样率。当该参数为1时,采样后的图片为原始大小,当大于1时,比如2,此时采样后图片宽高为原图大小的一半,而像素数为原来的四分之一,内存大小也为原图的四分之一,小于1时没有效果,相当于1。官方文档指出它的取值应该总是2的指数,比如1,2,4,8,16等等,如果外界传递一个不为2的指数的数字,那么系统会向下取整一个最接近2的指数来替代,但这个结论不在所有Android版本上有效,只充当一个建议。
获取采样率
1.将BitmapFactory.Options的inJustDecodeBounds参数设置为true并加载图片
2.从BitmapFactory.Options中去除图片的原始宽高信息,他么对应于outWidth和outHeight参数
3.根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize
4.将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片
当inJustDecodeBounds参数为true时,BitmapFactory只会解析图片的原始宽高信息,并不会真正地加载图片,此时获取的宽高信息和图片位置和程序运行的设备有关,同一张图片放在不同的drawable目录下或者程序运行在不同屏幕密度地设备上都可能导致BitmapFactory获取到不同的结果,这个现象和Android的资源加载机制有关。
实例
Android中的缓存策略
缓存策略主要包括缓存的添加、获取和删除这三类操作。目前常用的一种缓存算法是LRU,近期最少使用算法。核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LruCache和DiskLruCache,其中LruCache常被用作内存缓存,而DiskLruCache常被用作存储缓存。通过两者结合就可以方便地实现一个具有很高实用价值的ImageLoader。
LruCache
它是一个泛型类,并且线程安全,内部采用LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCache会移除较早使用的缓存对象,然后添加新的缓存对象
强引用、软引用、弱引用的区别
强引用:直接的对象引用
软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收
弱引用:当一个对象只有弱引用存在时,此对象会随时被gc回收
定义
LruCache的典型初始化过程(演示)
只需要提供缓存的总容量大小并重写sizeOf方法即可。sizeOf方法的作用时计算缓存对象的大小,这里大小的范围需要和总容量的单位一致。对于上面的示例代码来说,总容量的大小为当前进程的可用内存的1/8,单位为KB,而sizeOf方法则完成了Bitmap对象的大小计算。除以1024也是为了将单位转换成KB。getRowBytes方法的作用是: Return the number of bytes between rows in the bitmap's pixels
.某些情况下还需要重写LruCache的entryRemoved方法,它一出就缓存时会调用entryRemoved方法,因此可以在此方法中完成一些资源回收工作(如果有需要)。
缓存的获取和添加
它还支持删除操作,通过remove方法即可删除一个指定的缓存对象。
DiskLruCache
它用于实现磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存效果
1.DiskLruCache的创建
它并不能通过构造方法来创建,它提供了open方法用于创建自身
第一个参数表示磁盘缓存在文件系统中的存储路径。路径可以选择SD卡上的其他指定目录,具体是指/sdcard/Android/data/package_name/cache,还可以选择data下的当前应用目录,具体可以根据需要灵活设定。如果应用卸载后就希望删除缓存文件,那么就选择SD卡上的缓存目录,若希望保留缓存数据那就选择SD卡上的其他特定目录。
第二个参数表示应用的版本号,一般为1即可。当版本号发生改变时它会清空之前所有的缓存文件,实际开发中作用并不大,因为一般情况下即使应用版本号发生改变缓存文件依然有效。
第三个参数表示单个结点所对应的数据个数,一般设为1。
第四个参数表示缓存的总大小,比如16MB,当缓存超过这个大小就会清除一些缓存从而保证总大小不大于这个设定值
典型创建过程
getDiskCacheDir疑似已经被移除,当 SD 卡存在或者 SD 卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir() 方法来获取缓存路径
。前者获取到的就是/sdcard/Android/data//cache 这个路径,而后者获取到的是/data/data//cache这个路径。最后将获取到的路径和一个 uniqueName 进行拼接,作为最终的缓存路径返回。uniqueName是对不同类型的数据进行区分而设定的一个唯一值,比如bitmap吗,file等文件夹。
2.DiskLruCache的缓存添加
添加操作通过Editor完成,Editor表示一个缓存对象的编辑对象。以图片为例,先要获取图片url所对应的key,然后根据key就可以通过edit()获取Editor对象,如果这个缓存正在被编辑,那么edit()会返回null,即DiskLruCache不允许同时编辑一个缓存对象。之所以要把url转换成key,是因为图片的url中很可能有特殊字符,会影响url在Android中的使用,一般采用url的md5值作为key。
将图片的url转成key之后就可以获取Editor对象了。对于这个key来说,如果当前不存在其他Editor对象,那么Edit()就会返回一个新的Editor对象,通过它就可以得到一个文件输出流。由于前面的open1方法中设置了一个结点只能有一个数据,因此下面的DISK_CACHE_INDEX常量直接设为0即可
有了文件输出流,但从网络上下载图片时就可以通过这个文件输出流写入到系统文件上
经过上面的步骤之后还要通过Editor的commit()来提交写入操作,如果图片的下载过程发生异常,可以使用Editor的abort()来回退整个操作。
3.DiskLruCache的缓存查找
缓存查找过程也需要将url转换为key,然后通过DiskLruCache的get方法得到一个Snapshot对象,接着通过Snapshot对象即得到缓存的文件输入流,然后得到Bitmap对象。为了避免OOM一般不建议直接加载原始图片。但是通过BitmapFactory.Options对象来加载一张缩放后的图片对FileInputStream的缩放存在问题,因为它是一种有序的文件流,而两次decodeStream的调用影响了文件流的位置属性,导致第二次decodeStream时得到的是null。为了解决这个问题可以通过文件流来得到它所对应的文件描述符,然后通过BitmapFactory.decodeFileDescriptor方法来加载一张缩放后的图片。
文末
在这里如果你要想成为架构师或者技术更近一步,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、架构师筑基必备技能
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
二、Android百大框架源码解析
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
三、Android性能优化实战解析
- 腾讯Bugly:对字符串匹配算法的一点理解
- 爱奇艺:安卓APP崩溃捕获方案——xCrash
- 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
- 百度APP技术:Android H5首屏优化实践
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 携程:从智行 Android 项目看组件化架构实践
- 网易新闻构建优化:如何让你的构建速度“势如闪电”?
- …
四、高级kotlin强化实战
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
-
从一个膜拜大神的 Demo 开始
-
Kotlin 写 Gradle 脚本是一种什么体验?
-
Kotlin 编程的三重境界
-
Kotlin 高阶函数
-
Kotlin 泛型
-
Kotlin 扩展
-
Kotlin 委托
-
协程“不为人知”的调试技巧
-
图解协程:suspend
五、Android高级UI开源框架进阶解密
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
六、NDK模块开发
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
七、Flutter技术进阶
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
八、微信小程序开发
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,可点击下方CSDN官方认证卡片免费获取。