(三)如何避免OOM总结前面介绍了一些基础的内存管理机制以及OOM的基础知识,那么在实践操作当中,有哪些指导性的规则可以参考呢?归纳下来,可以从四个方面着手,首先是减小对象的内存占用,其次是内存对象的重复利用,然后是避免对象的内存泄露,最后是内存使用策略优化。 减小对象的内存占用避免OOM的第一步就是要尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。 1)使用更加轻量的数据结构例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。图8演示了HashMap的简要工作原理,相比起Android专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
图8 HashMap简要工作原理 关于更多ArrayMap/SparseArray的讨论,请参考《 Android性能优化典范(三)》的前三个段落。 2)避免在Android里面使用Enumandroid官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,所以请避免在Android里面使用到枚举。 3)减小Bitmap对象的内存占用Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施:
4)使用更小的图片在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。 内存对象的重复利用大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里创建对象池,然后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减少对象的重复创建,从而降低内存的分配与回收(如图9所示)。
图9 对象池技术 在Android上面最常用的一个缓存算法是LRU(Least Recently Use),简要操作原理如图10所示。
图10 LRU简要操作原理 1)复用系统自带的资源Android系统本身内置了很多的资源,比如字符串、颜色、图片、动画、样式以及简单布局等,这些资源都可以在应用程序中直接引用。这样做不仅能减少应用程序的自身负重,减小APK的大小,还可以在一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异、不符合需求的情况,还是需要应用程序自身内置进去。 2)注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用,如图11所示。
图11 3)Bitmap对象的复用在ListView与GridView等显示大量图片的控件里,需要使用LRU的机制来缓存处理好的Bitmap,如图12所示。
图12
图13 利用inBitmap的高级特性提高Android在Bitmap分配与释放执行效率 使用inBitmap需要注意几个限制条件:
图14 另外,在2.x的系统上,尽管Bitmap是分配在Native层,但还是无法避免被计算到OOM的引用计数器里。这里提示一下,不少应用会通过反射vBitmapFactory.Options里面的inNativeAlloc来达到扩大使用内存的目的,但是如果大家都这么做,对系统整体会造成一定的负面影响,建议谨慎采纳。 4)避免在onDraw方法里面执行对象的创建 类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。 5)StringBuilder 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。 |
|