在实践操作当中,可以从四个方面着手减小内存使用,首先是减小对象的内存占用,其次是内存对象的重复利用,然后是避免对象的内存泄露,最后是内存使用策略优化。
减小对象的内存占用
-
使用更加轻量级的数据结构
:例如,我们可以考虑使用ArrayMap
/SparseArray
而不是HashMap
等传统数据结构,相比起Android系统专门为移动操作系统编写的ArrayMap
容器,在大多数情况下,HashMap
都显示效率低下,更占内存。另外,SparseArray
更加高效在于,避免了对key与value的自动装箱,并且避免了装箱后的解箱。 -
避免使用Enum
:在Android中应该尽量使用int
来代替Enum
,因为使用Enum
会导致编译后的dex文件大小增大,并且使用Enum
时,其运行时还会产生额外的内存占用。 -
减小
Bitmap对象的内存占用
:inSampleSize
:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。decode format
:解码格式,选择ARGB_8888
RBG_565
ARGB_4444
ALPHA_8
,存在很大差异。 > ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 > ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位 > RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位 > ALPHA_8:每个像素占四位,只有透明度,没有颜色。
-
使用更小的图片
:在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
内存对象的重复使用
大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码的时候显式的在程序里面去创建对象池,然后处理好复用的实现逻辑,要么就是利用系统框架既有的某些复用特性达到减少对象的重复创建,从而减少内存的分配与回收。
-
复用系统自带资源
:Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。 -
ListView ViewHodler
-
Bitmap对象的复用
:在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。 -
inBitmap
:使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data
内存区域,而不是去问内存重新申请一块区域来存放bitmap。- 使用inBitmap,在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap,后面来了新的bitmap,可以从cache中按照api版本找到最适合重用的bitmap,来重用它的内存区域。
- 新申请的bitmap与旧的bitmap必须有相同的解码格式
-
避免在onDraw方法里面执行对象的创建:类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
-
StringBuilder
:在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
避免内存泄漏
内部类引用导致Activity的泄漏
:最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏
。- 考虑使用Application Context而不是Activity Context
- 注意临时Bitmap对象的及时回收
- 注意监听器的注销
- 注意缓存容器中的对象泄漏:不使用的对象要将引用置空。
- 注意Cursor对象是否及时关闭
内存优化策略
-
综合考虑设备内存阈值与其他因素设计合适的缓存大小
-
onLowMemory()
:Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。 -
onTrimMemory()
:Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用 -
资源文件需要选择合适的文件夹进行存放:例如我们只在
hdpi
的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi
的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。 -
谨慎使用static对象
-
优化布局层次,减少内存消耗
-
使用FlatBuffer等工具序列化数据
-
谨慎使用依赖注入框架
-
使用ProGuard来剔除不需要的代码