Glide 这样用,更省内存!!!
一、前言
Glide 是 Google 官方推荐的一款图片加载库,使用起来也非常的简单便利,Glide 它帮我们完成了很多很重要,但是却通用的功能,例如:图片的加载压缩、展示、加载图片的内存管理等等。
对 Glide 还不熟悉的朋友,可以参考 《一篇好文,助你上手 Glide》
但是,在使用 Glide 的时候,有一些小技巧,可以让你的内存更优化,避免可能出现的 OOM。例如:虽然 Glide 会根据加载的控件大小,优化加载后的图片尺寸,可如果加载的是一张全屏的大图,依然会是一个占用内存空间非常大的操作。
具体一张 Bitmap 到底占用了多少内存空间,可以参考《Bitmap 比你想的更费内存 | 吊打 OOM》
本文有些建议来自 Android TV App,而 Android TV 众多的智能电视和智能盒子,实际上硬件条件非常的恶劣,而 Android TV 的 App ,为了美化,会用到大部分的图片,所以在图片使用方面,OOM 的问题就会被放大,而下面介绍的一些优化方案,在 Android 手机硬件条件非常好的环境下,不使用影响也不大。
二、开始优化
2.1 配置好 TrimMemory 和 LowMemory
Glide 帮我们做了大部分内存管理方面的事情,实际上它还支持做的更好。
对于一个 App 而言,在系统内存环境不足的情况下,会回调一些 onTrimMemory() 或者 onLowMemory() 等方法,这些都是在提醒开发者,当前设备的内存环境已经发生了变化,你最好调整你的内存使用策略,避免被系统清理掉或者出现 OOM 。
关于 onTrimMemroy() 相关内容,不了解的可以先参考《Android 开发,跳不过的内存管理》
而 Glide 也为我们提供了类似方法的接口,开发者只需要调用即可,它在内部会随着不同的内存情况,帮我们对缓存的图片进行优化。
在这里,你主要用到 Glide 的 trimMemory() 和 cleanMemroy() 方法,它们一个用来裁剪 Glide 缓存的图片内存空间,一个用来清理 Glide 缓存的内存空间。
在使用 onTrimMemory() 之前,一般是实现 ComponentCallbacks2 接口,然后在 Application 中,通过 registerComponentCallbacks() 方法进行注册。当然,如果你嫌麻烦,还可以直接在 Application 中,重写对应的方法。
了解了这些,就可以根据我们的需要来配置在何时调用 Glide 的对应方法,我推荐的配置:
- 在 lowMemory 的时候,调用
Glide.cleanMemroy()清理掉所有的内存缓存。 - 在 App 被置换到后台的时候,调用
Glide.cleanMemroy()清理掉所有的内存缓存。 - 在其它情况的
onTrimMemroy()回调中,直接调用Glide.trimMemory()方法来交给 Glide 处理内存情况。
那么对应的代码,如下:
既然知道需要调用 Glide 的这两个方法,我们还是需要了解到它内部到底帮我们做了什么。先来看看 Glide 对应的源码。
在 Glide 的这些方法内,可以看到,它们都会去操作 memoryCache 和 bitmapPool 这两个对象,实际上它们是两个接口,这里如果做特殊处理,操作的都是 Glide 对它们的默认实现,LruResourceCache 和 LruBitmapPool 。从名称上可以看出来,它们都是遵循 Lru 算法的。
就 Glide 而言,Memory Cache 是 Glide 用来在内存中缓存图片资源,使其在需要使用的时候立刻就可以使用,而不必执行磁盘的 I/O 操作,而 BitmatPool 则是 Glide 维护了一个图片复用池,LruBitmapPool 使用 Lru 算法保留最近使用的尺寸的 Bitmap,这不是本文的重点,大家了解一下即可。
其实 LruResourceCache 和 LruBitmapPool 中,对 clearMemory() 和 trimMemory() 的操作是类似的,这里就以 LruBitmapPool 举例。
在 LruBitmapPool 中,会根据回调的方法以及参数,调用 clearMemory() 或者 trimToSize(),其实最终都是调用的 trimToSize() 方法。它用于裁剪当前缓存资源的个数。
可以看到,根据裁剪的目标尺寸,会去回收多余的 Bitmap 到合适的目标大小,以达到清理内存的目的。
2.2 配置 GlideModule
GlideModule 是 Glide 提供的一个配置接口,它会在第一次使用 Glide 的时候被调用,用于进行 Glide 的一些初始的配置。
具体 GlideModule 的使用,可以参见官方文档:
https://github.com/bumptech/glide/wiki/Configuration
GlideModule 是一个接口,需要实现其对应的方法。
这里我们只需要使用 applyOptions() 这个方法,它用于在 Glide 的默认配置的基础上,追加一些我们需要的配置。
而在这里,我们可以根据当前设备的内存情况,对其进行一个设定,使用 ActivityManager 获取当前设备的内存情况,如果是处于 lowMemory 的时候,将图片的 DecodeFormat 设置为 RGB_565 , RGB_565 和默认的 ARGB_8888 比,每个像素会少 2 个byte,这样,等于一张同样的图片,加载到内存中会少一半内存的占用(ARGB_8888 每个像素占 4 byte)。
2.3 避免使用圆角的ImageView
在实际项目内,经常会用到一些带圆角的图片,或者直接就是圆形的图片。圆形的图片,多数用于一些用户的头像之类的显示效果。
而在 Android 下,也有大量的类似 XxxImageView 的开源控件,用于操作 Bitmap 以达到一个圆角图片的效果,例如 Github 上比较火的 RoundedImageView。
它们大部分的原理,是接收到你传递的 Bitmap ,然后再输出一个与原来 Bitmap 等大的新 Bitmap ,在此基础之上,进行圆角的一些处理,这就导致了,实际上会在内存中,多持有一个 Bitmap ,一下一张图片占用的内存就被加倍了。
所以既然已经选择使用 Glide ,推荐使用 glide-transformations 这个开源库配合使用,glide-transformations 利用 Glide 的 bitmapTransfrom() 接口,实现对加载的 Bitmap 的进行一些变换操作。
glide-transformations 的 Github 地址如下:
https://github.com/wasabeef/glide-transformations
glide-transformations 提供一系类对加载的图片的变换操作,从形状变换到色彩变换,全部支持,基本上满足大部分开发需要,并且它会复用 Glide 的 BitmapPool ,来达到节约内存的目的。
具体 glide-transformations 的使用,可以查看 Github 上的文档,下面是它的一个效果图。
2.4 根据内存情况,裁剪你的图片
前面的介绍的一些优化点,都是一些推荐的通用做法,基本上用了前面介绍的办法,图片导致的 OOM 应该会大幅度减少。
接下来介绍一个在 Android TV 上,加载全屏大图的时候,优化内存问题的一个解决办法。
首先要明确一点,国内 Android TV 的硬件环境非常的不好,二百三百的智能盒子到处都在卖,毕竟也是跑的 Android 系统,你想想你使用的是一款 299 的 Android 手机,你对它也不会有什么期待了。但是 Android TV 又是为了电视做的,所以大部分情况下,它都是需要支持 1920 * 1280 之类的屏幕尺寸,导致它如果加载一张全屏的大图,消耗的内存是不忍直视的,如果在内存环境不好的情况下,可能就直接 OOM 崩溃了。
所以,对于这种极端的情况,我想到了一个办法,根据当前的内存环境,按比例缩小需要显示的全屏图片,这样加载到内存中的图片,就是按比例缩小的。
在这里就需要用到 DrawableRequestBuilder 的 override() 这个 Api 了,它可以接受一个 width 和 height ,来重新指定加载图片的尺寸。
既然 Glide 已经提供了标准的 Api ,那么我们还需要获取到当前运行设备的宽高。
这里推荐使用 getRealSize() 的方式获取屏幕的宽高,它可以真实的拿到当前屏幕的尺寸。其它 Api 在部分智能电视和盒子上,拿到的尺寸会小,因为没有计算 StatusBar 或者 NavigationBar的高度,这些都是经验之谈。
同时,我们也需要用到 ComponentCallbacks2 这个接口,前面已经介绍过了,就不再赘述了。
在其中,记录 trim 的 level 这个值,反应当前的内存级别,在使用的时候,通过 getBitmapSize() 裁剪出一个符合当前内存环境的尺寸。
例子中只是对 TRIM_MENORY_RUNNING_LOW 进行了处理,会根据屏幕尺寸,缩放到 0.8f 倍的状态。如果要做的更多,可以将其它几个 level 也加上,调整不同的缩放倍数。
两个都输出一下,看看差别,同一张全屏的图片,不缩放和缩放 0.8f 的差别。
I/cxmyDev: bgImage byteCount : 8294400
I/cxmyDev: bgImage byteCount : 5308416
可以看到,优化的目的还是达到了。可以节约大概 3MB 左右的内存空间,而图片又不至于模糊到无法看的地步。
三、小结
优化是没有终点的,今天先聊到这里,之后有想到的再补充。如果你有什么更好的建议,可以在文末留言一起讨论一下。
点赞或者分享吧~
Glide 这样用,更省内存!!!的更多相关文章
- 这种实现方式比使用 += 要更节省内存和 CPU,尤其是要串联的字符串数目特别多的时候。
这种实现方式比使用 += 要更节省内存和 CPU,尤其是要串联的字符串数目特别多的时候. package main import ( "bytes" "fmt" ...
- Android Glide数据更新及内存缓存、硬盘缓存清理
[转] 原文 Android Glide数据更新及内存缓存.硬盘缓存清理 Android的Glide在加载图片时候内部默 ...
- 原来PHP对象比数组用更少的内存
一直以为php的数组更节省内存,从来没有测试过,今天因为要读取一个大配置文件作为pool.做了一次测试: 得出结论是 使用对象保存数据更好,花费的内存是数组array的1/4. 测试代码 class ...
- Android图片缓存之初识Glide
前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...
- Google推荐的图片加载库Glide介绍
英文原文 Introduction to Glide, Image Loader Library for Android, recommended by Google 译文首发 http://jco ...
- Glide的常用方法注释
Glide简单使用1.)添加引用 build.gradle 中添加配置 compile 'com.github.bumptech.glide:glide:3.7.0' 2.)设置绑定生命周期 我 ...
- 安卓高级 Android图片缓存之初识Glide
前言: 前面总结学习了图片的使用以及Lru算法,今天来学习一下比较优秀的图片缓存开源框架.技术本身就要不断的更迭,从最初的自己使用SoftReference实现自己的图片缓存,到后来做电商项目自己的实 ...
- Universal-Image-Loader,android-Volley,Picasso、Fresco和Glide图片缓存库的联系与区别
Universal-Image-Loader,android-Volley,Picasso.Fresco和Glide五大Android开源组件加载网络图片比较 在Android中的加载网络图片是一件十 ...
- android glide图片加载框架
项目地址: https://github.com/bumptech/glide Glide作为安卓开发常用的图片加载库,有许多实用而且强大的功能,那么,今天就来总结一番,这次把比较常见的都写出来,但并 ...
随机推荐
- C++内存布局详解
一个由C/C++编译的程序除了存放函数二进制代码的程序代码段(code段)外,数据占用的内存大致分为以下几个部分: 1.栈区(stack) 存放局部变量.函数参数.返回数据.返回地址等.系统自动分配释 ...
- pythonl练习
练习:用户输入姓名.年龄.工作.爱好 ,然后打印成以下格式 ------------ info of Egon ----------- Name : Egon Age : 22 Sex : male ...
- 【前端基础】动态脚本与JSONP
博主入职两个月了,越来越感受到打好基础对于前端工程师的重要性,在向着狂拽酷炫的框架&构建工具高速狂奔之前,必须有一个坚实的基础打底,才不至于轻易翻车.所以博主最近一直在恶补<JS高级程序 ...
- 九九乘法表实现---基于python
# coding:utf-8"""九九乘法表"""for k in range(1,10): for i in range(1,k+1 ...
- Tinyhttpd 代码学习
前阵子,参加了实习生面试,被面试官各种虐,问我说有没有读过一些开源的代码.对于只会用框架的我来说真的是硬伤啊,在知乎大神的推荐下在EZLippi-浮生志找了一些源代码来阅读,于是从小型入手,找了Tin ...
- LINUX服务器上新增用户名
最近所里的机群停了,需要用老板的服务器跑程序,这里首先得在老板的服务器上新增一些用户名.新增用户名方法如下: 1.利用useradd添加用户名,并指定用户名目录.脚本解释器.用户名 sudo user ...
- 交换机的Ethernet Channel
端口聚合也叫做以太通道(ethernet channel),主要用于交换机之间连接.由于两个交换机之间有多条冗余链路的时候,STP会将其中的几条链路关闭,只保留一条,这样可以避免二层的环 路产生.但是 ...
- MPLS LDP随堂笔记1
LDP 的使用原因(对于不同协议来说) LDP的四大功能 发现邻居 hello 5s 15s 224.0.0.2 发现邻居关系 R1 UDP 646端口 R2 UDP 646端口 此时形成邻居 建立邻 ...
- 转:【Java并发编程】之十一:线程间通信中notify通知的遗漏(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17228213 notify通知的遗漏很容易理解,即threadA还没开始wait的时候,t ...
- MongoDB学习之路(一)
NoSQL简介 NoSQL(Not Only SQL),意为"不仅仅是SQL" 关系型数据库遵循ACID规则 1. A(Atomicity)原子性 指的是事务里的所有操作要么全部做 ...