Android性能优化典范总结(一)

2019-08-28  本文已影响0人  怡红快绿

原文地址:http://hukai.me/android-performance-patterns/

一、渲染机制

正常情况下,Android系统每隔16ms会对UI进行一次渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps(每秒传输帧数)。


正常渲染

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


丢帧

有很多原因会导致丢帧:

  1. layout布局太过复杂
  2. UI上有层叠太多的绘制单元
  3. 动画执行的次数过多

这些都会导致CPU或者GPU负载过重。

二、过渡绘制

Overdraw(过度绘制)描述的是屏幕上的某些像素在同一帧的时间内被绘制了多次。

在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。幸运的是,我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,可以观察UI上的Overdraw情况。


绘制图

例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。

三、VSYNC信号

首先了解两个概念:

图像显示到屏幕上一般分为两步:

  1. GPU对图片进行渲染
  2. 渲染后的内容呈现到屏幕上

理想的情况是帧率和刷新频率相等,每绘制一帧,屏幕显示一帧。而实际情况是,二者之间没有必然的大小关系,如果没有锁来控制同步,很容易出现问题。

当帧率大于刷新频率,当屏幕还没有刷新第 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操作速度更快的。

Generational Heap Memory模型

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

image.png

通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。

导致GC频繁执行有两个原因:

那么如何检测程序是否存在内存抖动现象,AndroidStudio给我们提供了分析工具Android Profiler。如果你在Memory Monitor里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动。

image.png

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


参考链接:https://blog.csdn.net/zhaizu/article/details/51882768

上一篇 下一篇

猜你喜欢

热点阅读