文章目录
Android性能优化——内存
1. 内存、GC和性能
C/C++需要通过手动编码来申请及释放内容,而Java则拥有GC机制。
Android 系统里面有一个Generational Heap Memeory 的模型,系统会根据内存中不同的内存数据分别执行不同的GC操作。例如,最近分配的对象会放在Young Generation区域,这个区域的对象通常都是会快速被创建并且很快被摧毁回收的,同时这个区域的GC操作速度也是比Old Generation区的GC操作更快的。
GC是停止世界事件,意思是很对指令的运行直到操作完成才会停止。也就是说执行GC的时候,所有线程的任何操作都会需要暂停,等待GC操作完成后,其他操作才能继续进行。
单次GC并不会占用太多的时间,但是大量不停的GC操作会占用帧间隔时间(16ms)。如何在帧间隔时间做了过多的GC操作,那么其它计算、渲染的可用时间就变少了。
2. Memory Monitor
AndroidStudio 中的Memory Monitor 可以帮助我们查看内存使用情况
当然如今AndroidStudio已经使用Memory Profiler替代Memory Monitor
3. 内存泄漏
内存泄漏表示的是不再用到的对象因为错误引用而无法进行回收。
发生内存泄漏会导致Memory Generation 中的剩余Heap Size越来越小,这样会导致频繁GC,从而引起性能问题。
例如下面的代码,init()
方法来自某个自定义View
private void init() {
ListenerCollector collector = new ListenerCollector();
collector.setListener(this, mListener);
}
上面的代码很容易出校内存泄漏,如果activity因为设备翻转而重新创建,自定义的View会自动重新把新创建的mListener给绑定到ListenerCollector中,但是当activity被摧毁的时候,mListener却无法被回收了。
4. Heap Viewer
Heap Viewer 位于Android Device Monitor 的DDMS里面
下图可以看到当前进程中HeapSize的情况,分别有哪些类型的数据,占比是多少。
现在也已经被替换为 Memory Profiler
5. 内存抖动
内存抖动(Memory Churn)是因为在短时间内大量的对象被创建又马上被释放。大量的对象会严重占用Young Generation ,当达到阀值,剩余控件不够的时候,会触发GC从而导致刚产生的对象有很快被回收,即使每次分配的对象占了很少内存,但是他们叠加在一起会增加heap的压力,从而触发更多其他类型的GC。
这个操作很有可能影响到帧率,产生性能问题。
如果在Memory Monitor里面观察到,短时间发生了多次内存涨跌,这意味着很有可能发生了内存抖动。
可以通过,Allocation Tracker 查看短时间内用一个栈不断进出的相同对象。这是内存抖动的典型信号。
我们需要避免在for循环里面分配对象占用内存,需要把对象的创建移到循环体之外。
在自定义View的onDraw方法也需要注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。
6. Allocation Tracker
Android Studio 3.x 之后已经被 Android Profiler 替代
https://developer.android.com/studio/profile/monitor#alloc
7. 内存抖动的例子
下面演示了一个通过修改代码避免内存抖动的例子。
优化之前的检测图
定位代码之后,修复了String拼接的问题。
优化之后
8. Recap
后面总结了一下提到的三个工具
- Memory Monitor:跟踪整个app的内存变化情况。
- Heap Viewer:查看当前内存快照,便于对比分析哪些对象有可能发生了泄漏。
- Allocation Tracker:追踪内存对象的来源。