Android内存优化是我们性能优化工作中比较重要的一环,主要包括两方面的工作:
- 优化RAM,即降低运行时内存。目的是防止程序发生OOM异常,以及降低程序由于内存过大被LowMemoryKiller(LMK)机制杀死的概率。同时,不合理的内存使用会使GC次数大大增多,从而导致程序变卡。
- 优化ROM,即降低程序占ROM的体积,防止ROM空间不足导致程序无法安装等问题。
前言
Android中关于内存优化的问题主要包括三个方面:
- Memory Leaks 内存泄漏
- OutOfMemory 内存溢出
- Memory Churn 内存抖动
同时,和内存相关的三个主要数据为:
- 总内存
- 系统可用内存
- 当前App可用内存
扩大内存
- 申请更大内存
每一个Android设备都会有不同的RAM总大小与可用空间,因此不同设备为App提供了不同大小的heap限制,可以通过调用getMemoryClass(
)来获取当前App的可用heap大小。
在一些特殊情景下,可以通过在manifest的application标签下添加largeHeap=true
属性来声明一个更大的heap空间,之后可以通过getLargeMemoryClass()
来获取到一个更大的heap大小。但这不是该值得提倡的方法,因为使用额外的内存会影响系统整体的用户体验,并且会使得GC的运行时间更长。在任务切换时,系统的性能会变得大打折扣。
而且, large heap
并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap
的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()
来检查实际获取到的heap大小。
- 创建多进程
内存泄漏
对象由于编码错误或系统原因,仍然存在着对其直接或间接的引用,导致系统无法进行回收。
内存泄漏引发的常见问题有:
- 应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC);
- 应用被从后台进程处理为空进程;
- 应用莫名崩溃(引起OOM);
常见泄漏类型
单例模式导致的内存泄漏
使用单例持有Context,需要记得释放,或使用全局上下文。
静态变量导致的内存泄漏
除了避免显示地使用静态变量引用拥有自己生命周期的对象外,也需要注意一些隐式的使用,如下:
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
sBackground
是一个静态的变量,我们虽然没有显式地保存Context的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:Drawable->TextView->Context
。
所以,最终该Context也没有得到释放,发生了内存泄露。
解决方案
- 尽量避免静态变量引用资源耗费过多的对象,如Context,同时注意对静态对象的释放。
- Context尽量使用ApplicationContext。
- 使用W