Android 内存优化(一)
内存使用三大问题
Android中内存使用和优化问题一直是我们开发者关注的重点,随着系统版本和虚拟机的升级内存的限制和使用都有了变化,那么今天我就讲讲如何优化我们App的内存使用。
针对内存方面我们主要解决3大问题:内存溢出、内存泄漏和内存抖动,同时保持一个良好的内存使用习惯。接下来我们逐一讲解三个问题的出现原因和解决办法。
内存溢出:OutOfMemory
OOM的发生是开发者最痛苦的事情之一,其提供的crash log有时候让我们无从入手。为什么会出现OOM?这跟android系统判断规则有关系,在2.X和4.X系统上对于OOM的条件有所改变,但关键都是getMemoryClass()
。
发生OOM的条件
- Android 2.x系统 GC LOG中的dalvik allocated + external allocated + 新分配的大小>= getMemoryClass()值的时候就会发生OOM。 例如,假设有这么一段Dalvik输出的GC LOG:GC_FOR_MALLOC free 2K, 13% free 32586K/37455K, external 8989K/10356K, paused 20ms,那么32586+8989+(新分配23975)=65550>64M时,就会发生OOM。
- Android 4.x系统 Android 4.x的系统废除了external的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= getMemoryClass()的时候就会发生OOM。
如何避免OOM
Bitmap压缩、缓存与复用
Bitmap是app最需要注意的内存使用者,Bitmap在2.x系统上是被分配在native层heap中,而在3.0以后Bitmap被分配到了app的Dalvik heap中,减小其大小并重复利用它们是我们的最优选择。- 使用
inSampleSize
在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入 - 使用
decode format
设定不一样的编码格式会有很大的区别 - 使用LRUcache来缓存bitmap对象(尤其是在List & Grid)
- 充分使用BitmapFactory.Option inBitmap属性,利用这种特性即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大 小。在3.0-4.4系统中inBitmap要求新申请的Bitmap必须和可复用的Bitmap对象的大小完全相同,而在4.4系统以后要求有所放 松,只要新申请Bitmap对象小于或等于可复用的Bitmap对象即可复用
- 对Bitmap对象使用weakReference进行包装,这点在使用的时候需要分析应用场景是否适合
现在app中使用的大多数图片框架都实现上述这些功能,大家有兴趣的话可以去github上看看这些图片库的源码,学习一下他们的设计理念和实现方式。推荐glide和fresco~
- 使用
使用轻量级的数据结构
我们最常用的K-V形式的数据结构是HashMap,Android为我们提供了更轻量级数据结构ArrayMap/SparseArray,在效率和内存都比HashMap有明显的优势。因为HashMap需要一个额外的实例对象来记录Mapping操作,同时有大量的”自动装箱“操作。
我们在满足下述两个条件时就应该选择ArrayMap/SparseArray:对象个数的数量级最好是千以内,数据组织形式包含Map结构避免在Android里面使用Enum
Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
”
枚举通常情况下所占用的内存是静态常量的两倍甚至更多,有兴趣的同学可以建一个工程先打一个包看看dex有多大,然后分别加上这两段代码,再对比一下dex的大小。public static final int VALUE1 = 1; public static final int VALUE2 = 2; public static final int VALUE3 = 3; int func(int value) { switch(value) { case VALUE1: return -1; case VALUE2: return -2; case VALUE3: return -3; } return 0; }
public static enum Value{ VALUE1, VALUE2, VALUE3 } int func(Valuevalue) { switch(value) { case VALUE1: return -1; case VALUE2: return -2; case VALUE3: return -3; } return 0; }
内存监控 onLowMemory()与onTrimMemory()
- onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。
- onTrimMemory(int):Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。下图介绍了各种不同的回调参数:
- 其他优化点
- StringBuilder:需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”
- 对资源文件进行压缩:很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。我们可以对我们使用的资源文件进行压缩,例如使用TinyPng。
- Try catch某些大内存分配的操作