原文地址:http://hukai.me/android-performance-patterns/
一、渲染机制
正常情况下,Android系统每隔16ms会对UI进行一次渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps(每秒传输帧数)。

如果某个操作耗时24ms,也就意味着将会错过一次渲染UI的机会,这样就发生了丢帧现象。用户在32ms内看到的会是同一帧画面。

有很多原因会导致丢帧:
- layout布局太过复杂
- UI上有层叠太多的绘制单元
- 动画执行的次数过多
这些都会导致CPU或者GPU负载过重。
二、过渡绘制
Overdraw(过度绘制)描述的是屏幕上的某些像素在同一帧的时间内被绘制了多次。
在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。幸运的是,我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,可以观察UI上的Overdraw情况。

例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。
三、VSYNC信号
首先了解两个概念:
- 帧率:Frame Rate,单位 fps(每秒传输帧数),是指GPU生成帧的速率,一般来说越高越好。
- 屏幕刷新频率:Refresh Rate,单位赫兹/Hz,是指设备刷新屏幕的频率,该值对于特定的设备来说是个常量,如 60Hz。
图像显示到屏幕上一般分为两步:
- GPU对图片进行渲染
- 渲染后的内容呈现到屏幕上

理想的情况是帧率和刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有锁来控制同步,很容易出现问题。
当帧率大于刷新频率,当屏幕还没有刷新第 n-1 帧的时候,GPU 已经在生成第 n 帧了,从上往下开始覆盖第 n-1 帧的数据,当屏幕开始刷新第 n-1 帧的时候,Buffer 中的数据上半部分是第 n 帧数据,而下半部分是第 n-1 帧的数据,显示出来的图像就会出现上半部分和下半部分明显偏差的现象,我们称之为 “tearing”


当帧率小于刷新频率,某些帧显示的画面内容就会与上一帧的画面相同。

四、UI渲染过程
栅格化:栅格即像素,栅格化即将矢量图形转化为位图(栅格图像)。它把UI上的组件拆分到不同的像素上进行显示。这是一个很费时的操作,GPU的引入就是为了加快栅格化的操作。

CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染。
然而每次从CPU转移到GPU是一件很麻烦的事情,所幸的是OpenGL ES可以把那些需要渲染的纹理Hold在GPU Memory里面,在下次需要渲染的时候直接进行操作。所以如果你更新了GPU所hold住的纹理内容,那么之前保存的状态就丢失了。
Android里的Drawable、Bitmap都是直接被打包到统一的Texture纹理中,然后再传递到GPU Memory里面,这意味着每次你需要使用这些资源的时候,都是直接从纹理里面进行获取渲染的。
五、内存抖动(Memory Churn)
虽然Android有自动管理内存的机制,但是对内存的不恰当使用仍然容易引起严重的性能问题。在同一帧里面创建过多的对象是件需要特别引起注意的事情。
Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不同的内存数据类型分别执行不同的GC操作。例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象通常都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的。

GC操作最大的特点就是Stop-The-World
,也就是说执行GC操作的时候,所有线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。

通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。
导致GC频繁执行有两个原因:
- 频繁创建和销毁大量对象,从而导致内存抖动
-
瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。
那么如何检测程序是否存在内存抖动现象,AndroidStudio给我们提供了分析工具Android Profiler。如果你在Memory Monitor里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动。

当你大致定位问题之后,接下去的问题修复也就显得相对直接简单了。例如,你需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。
网友评论