一、内存优化概述:为什么我们需要关心这个
说实话,我刚开始做Android开发那会儿,对内存优化这事儿真没太当回事。总觉得手机内存越来越大,4G、8G、12G,够用了吧?直到有一次,我负责的一个社交应用在用户手机上频繁闪退,用户评分直接掉到3.2分……
那次经历让我彻底明白了:内存问题不是“够不够用”的问题,而是“能不能用好”的问题。你想想看,一个App占用了300MB内存,在2GB内存的老手机上,系统可能就要开始杀后台进程了。用户正聊着天呢,切出去回个微信,回来发现App重启了——这种体验,谁受得了?
1.1 为什么需要内存优化?
说白了,内存优化的核心目的就三个:
- 防止应用崩溃:OOM(Out Of Memory)是Android应用最常见的崩溃原因之一。我见过一个图片浏览应用,因为没做图片压缩,在低端机上打开10张照片就直接挂了。
- 提升用户体验:内存占用过高会导致卡顿、掉帧、应用被系统回收。用户不会管你是不是在做复杂计算,他们只关心“为什么我的手机这么卡”。
- 降低功耗:内存频繁分配和回收会消耗CPU资源,增加电量消耗。嗯,这一点很多人容易忽略。
核心观点:内存优化不是“省内存”,而是“合理用内存”。该用的地方不能省,不该用的地方一分都不能多。
1.2 常见内存问题:泄漏、抖动、溢出
我在项目中遇到过各种各样的内存问题,归纳起来就三大类:
1. 内存泄漏(Memory Leak)
内存泄漏,说白了就是对象已经不用了,但GC回收不了。为什么会这样?因为还有强引用在牵着它。
举个例子:
// 典型的匿名内部类导致的内存泄漏
public class MainActivity extends AppCompatActivity {
private static Handler sHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 这里持有外部Activity的引用
}
};
}
这段代码里,sHandler是静态的,它内部类隐式持有MainActivity的引用。Activity销毁了,但Handler还在,Activity就泄漏了。
避坑指南:我曾经在一个电商项目里,因为Fragment的嵌套使用不当,导致整个Activity链泄漏。用MAT一查,好家伙,泄漏了十几个Activity实例。从那以后,我养成了一个习惯——所有内部类都写成静态的,或者使用弱引用。
2. 内存抖动(Memory Churn)
内存抖动,就是短时间内频繁创建和销毁对象。这会导致GC频繁触发,引起卡顿。
典型的场景:
- 在onDraw()里创建Paint、Path对象
- 在循环里用“+”拼接字符串
- 频繁创建临时对象,比如在getView()里new ViewHolder
// 错误示范:每次绘制都创建新对象
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint(); // 每次绘制都new,内存抖动!
paint.setColor(Color.RED);
canvas.drawCircle(x, y, radius, paint);
}
注意:内存抖动不一定会导致OOM,但它会让你的应用卡成PPT。Perfetto的CPU跟踪里,如果看到GC频繁触发,十有八九就是内存抖动。
3. 内存溢出(OOM)
内存溢出,就是应用申请的内存超过了系统给的上限。Android系统给每个应用分配了堆内存上限,不同设备不一样,一般在64MB到512MB之间。
常见原因:
- 加载超大图片(比如5000x5000像素的图直接解码)
- 一次性加载大量数据(比如把整个数据库读到内存里)
- 内存泄漏累积到一定程度
我的经验:有一次线上反馈说用户打开某个页面就闪退。我查了Bugly日志,发现是OOM。后来定位到是Glide加载一张全景图时,没有做采样压缩。一张图解码出来直接占了30MB,加上其他内存,就爆了。
1.3 优化目标与衡量指标
做内存优化,不能凭感觉。你得有数据说话。我个人习惯关注这几个指标:
| 指标 | 说明 | 理想值 |
|---|---|---|
| PSS(比例共享内存) | 应用实际占用的物理内存,包含共享库按比例分摊的部分 | 根据设备而定,一般建议不超过总内存的20% |
| Java堆内存 | Dalvik/ART虚拟机分配的堆内存 | 不超过最大堆限制的80% |
| GC频率 | 每分钟GC触发的次数 | 低于5次/分钟 |
| GC暂停时间 | 每次GC导致应用暂停的时间 | 低于10ms |
| OOM率 | 应用因OOM崩溃的比率 | 低于0.01% |
怎么获取这些数据?
- PSS:用
adb shell dumpsys meminfo 包名查看 - 堆内存:用Android Studio的Profiler或者MAT
- GC频率:用Perfetto的CPU跟踪,或者开启
Debug.startMethodTracing() - OOM率:接入Bugly、Firebase等崩溃监控平台
小技巧:我一般会在应用的Debug版本里加一个内存监控面板,实时显示PSS、堆内存、GC次数。这样开发过程中就能发现问题,不用等到线上才暴露。
小结
这一章我们聊了内存优化的必要性、三大常见问题(泄漏、抖动、溢出),以及怎么衡量优化效果。说白了,内存优化就是一场“精打细算”的工程——你要知道内存花在哪了,花得值不值,有没有浪费。
下一章,我会带你深入MAT这个工具,看看怎么用它来定位内存泄漏。嗯,MAT是我用得最多的内存分析工具,没有之一。