UI卡顿分析之内存抖动和计算性能优化

渲染机制以及优化

一、了解渲染刷新机制
VSYNC(垂直刷新/绘制)

60HZ是屏幕刷新理想的频率。60fps---一秒内绘制的帧数。
24帧/秒 电源胶卷时代

在60fps内,系统会得到发送的VSYNC(垂直刷新)信号qu去进行渲染,就会正常地绘制。
60fps要求:每一帧只能停留16ms.

为什么用户会感觉到卡顿

首先,我们要知道Android系统每隔16ms就重新绘制一次Activity,也就是说,我们的应用必须在16ms内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60帧,然而这个每秒帧数的参数由手机硬件所决定,现在大多数手机屏幕刷新率是60赫兹(赫兹是国际单位制中频率的单位,它是每秒中的周期性变动重复次数的计量),也就是说我们有16ms(1000ms/60次=16.66ms)的时间去完成每帧的绘制逻辑操作,如果错过了,比如说我们花费34ms才完成计算,那么就会出现我们称之为丢帧的情况,安卓系统尝试在屏幕上绘制新的一帧,但是这一帧还没准备好,所以画面就不会刷新。如果用户盯着同一张图看了32ms而不是16ms,用户会很容易察觉出卡顿感,哪怕仅仅出现一次掉帧,用户都会发现动画不是很顺畅,如果出现多次掉帧,用户就会开始抱怨卡顿

VSYNC:有两个概念

1)Refresh Rate:屏幕在一秒时间内刷新屏幕的次数----有硬件的参数决定,比如60HZ.
2)Frame Rate:GPU在一秒内绘制操作的帧数,比如:60fps。

GPU刷新:GPU帮助我们将UI组件等计算成纹理Texture和三维图形Polygons
同时会使用OpenGL---会将纹理和Polygons缓存在GPU内存里面。
View Tree。

卡顿是如何造成的

卡主线程了!(卡UI线程)
1.外部引起的
比如:Activity里面直接进行网络访问/大文件的IO操作
内存这一块有些什么要注意的。

1)内存抖动的问题,不断地创建和回收对象。
    new Object
    obj = null 
     解决方法:1.减少对象的创建,如:字符串拼接优先使用是StringBuff或者StringBuilde
              2.使用profiler的memory模块查看程序是否有大量的垃圾回收情况
2)  一个方法太复杂,执行太耗时了。
     解决方法:1.使用批处理和缓存技术
              2.减少递归调用方法,用循环替代递归,kotlin使用尾递归进行优化
              3.使用异步执行耗时算法
              4.使用合适的容器和数据结构及算法

2.View本身的卡顿
自定义View要注意的,能否优化、
Android系统的渲染管线分为两个关键组件:CPU和GPU,它们共同工作,在屏幕上绘制图片

在CPU方面,最常见的性能问题是不必要的布局和失效,这些内容必须在视图层次结构中进行测量、清除并重新创建,引发这种问题通常有两个原因:一是重建显示列表的次数太多,二是花费太多时间作废视图层次并进行不必要的重绘,这两个原因在更新显示列表或者其他缓存GPU资源时导致CPU工作过度。
在GPU方面,最常见的问题是我们所说的过度绘制(overdraw),通常是在像素着色过程中,通过其他工具进行后期着色时浪费了GPU处理时间。

UI卡顿分析之内存抖动和计算性能优化_第1张图片
image.png

想要开发一款性能优越的应用,我们必须了解底层是如何运行的。有一个主要问题就是,Activity是如何绘制到屏幕上的?那些复杂的XML布局文件和标记语言,是如何转化成用户能看懂的图像的?
实际上,这是由格栅化操作来完成的,格栅化就是将例如字符串、按钮、路径或者形状的一些高级对象,拆分到不同的像素上在屏幕上进行显示, 格栅化是一个非常费时的操作。 GPU的目的就是加快格栅化的操作,GPU在上个世纪90年代被引入用来帮助加快格栅化操作

UI卡顿分析之内存抖动和计算性能优化_第2张图片
image.png

GPU使用一些指定的基础指令集,主要是多边形和纹理,也就是图片,CPU在屏幕上绘制图像前会向GPU输入这些指令,这一过程通常使用的API就是Android的OpenGL ES,这就是说,在屏幕上绘制UI对象时无论是按钮、路径或者复选框,都需要在CPU中首先转换为多边形或者纹理,然后再传递给GPU进行格栅化

UI卡顿分析之内存抖动和计算性能优化_第3张图片
image.png

我们要知道,一个UI对象转换为一系列多边形和纹理的过程肯定相当耗时,从CPU上传处理数据到GPU同样也很耗时。所以很明显,我们需要尽量减少对象转换的次数,以及上传数据的次数,幸亏,OpenGL ES API允许数据上传到GPU后可以对数据进行保存,当我们下次绘制一个按钮时,只需要在GPU存储器里引用它,然后告诉OpenGL如何绘制就可以了,一条经验之谈:渲染性能的优化就是尽可能地上传数据到GPU,然后尽可能长地在不修改的情况下保存数据,因为每次上传资源到GPU时,我们都会浪费宝贵的处理时间,Android系统的Honeycomb版本发布之后,整个UI渲染系统就在GPU中运行,之后各个版本都在渲染系统性能方面有更多改进。
Android系统在降低、重新利用GPU资源方面做了很多工作,这方面完全不用担心,举例说,任何我们的主题所提供的资源,例如Bitmaps、Drawables等都是一起打包到统一的纹理当中,然后使用网格工具上传到GPU,例如Nine Patches等,这样每次我需要绘制这些资源时,我们就不用做任何转换,他们已经存储在GPU中了,大大加快了这些视图类型的显示。然而随着UI对象的不断升级,渲染流程也变得越来越复杂,例如说绘制图像,就是把图片上传到CPU存储器,然后传递到GPU中进行渲染。路径使用时完全另外一码事,我们需要在CPU中创建一系列的多边形,甚至在GPU中创建掩蔽纹理来定义路径。绘制字符就更加复杂一些,首先我们需要在CPU中把字符绘制制成图像,然后把图像上传到GPU进行渲染再返回到CPU,在屏幕上为字符串的每个字符绘制一个正方形。


UI卡顿分析之内存抖动和计算性能优化_第4张图片
image.png

现在Android系统已经解决了大多数性能问题,除非我们还有更高要求,我们基本不会发现与GPU相关的问题,然后还有一个GPU性能问题瓶颈,这个问题困扰着每个程序员,这就是过度绘制。

过度绘制

如果我们曾经粉刷过房子,我们应该知道,给墙壁粉刷工作量非常大,如果我们需要重新粉刷,第一次的粉刷就白干了。同样的道理,我们的应用程序会因为过度绘制,从而导致性能问题,如果我们想兼顾高性能和完美的设计,往往会碰到一种性能问题,即过度绘制。过度绘制是一个术语,指的是屏幕上的某个像素点在同一帧的时间内被绘制了多次。假如我们有一堆重叠的UI卡片,最接近用户的卡片在最上面,其余卡片都藏在下面,也就是说我们花大力气绘制的那些下面的卡片基本都是不可见的。

UI卡顿分析之内存抖动和计算性能优化_第5张图片
image.png

问题就在于此,因为每次像素经过渲染后,并不是用户最后看到的部分,这就是在浪费GPU的时间。目前流行的一些布局是一把双刃剑,带给我们漂亮视觉感受的同时,也造成过度绘制的问题,为了最大限度地提高应用程序的性能,我们必须尽量减少过度绘制。幸运的是,Android手机提供了查看过度绘制情况的工具,在开发者选项中打开“Show GPU overdraw”

选项,手机屏幕显示会出现一些异常不用过于惊慌,Android在屏幕上使用不同颜色,标记过度绘制的区域,如果某个像素点只渲染了一次,我们看到的是它原来的颜色,随着过度绘制的增多,标记颜色也会逐渐加深,例如1倍过度绘制会被标记为蓝色,2倍、3倍、4倍过度绘制遵循同样的模式。所以当我们调试应用程序的用户界面时,目标就是尽可能的减少过度绘制,将红色区块转变成蓝色区块,为了完成目标有两种清楚过度绘制的方法,首先要从视图中清楚那些,不必要的背景和图片,他们不会在最终渲染图像中显示,记住,这些都会影响性能。其次,对视图中重叠的屏幕区域进行定义,从而降低CPU和GPU的消耗,接下来我们深入了解过度绘制


UI卡顿分析之内存抖动和计算性能优化_第6张图片
image.png

常见的过度绘制:

1.清除不必要的背景和图片,主题背景,跟布局背景......
2.图片没加载的时候设置背景图片,加载出来之后背景颜色设置为透明
3.重叠的view只绘制可以看见的部分

布局优化

是时候来了解一下渲染管道中的CPU部分,为了在屏幕上绘制某个东西,Android通常将高级XML文件转换为GPU能够识别的对象,然后显示在屏幕上,这个操作是在DisplayList的帮助下完成的,DisplayList持有所有要交给GPU绘制到屏幕上的数据信息,包含GPU要绘制的全部对象的信息列表,还有执行绘制操作的OpenGL命令列表,在某个View第一次需要被渲染时,DisplayList会因此被创建,当这个View要显示到屏幕上时,我们将绘制指令提交给GPU来执行DisplayList,我们下次渲染这个View时,比如说位置发生了变化,我们仅仅需要执行DisplayList就够了,但是如果我们修改了View的某些可见组件的内容,那么之前的DisplayList就无法继续使用了,这时我们要重新创建一个DisplayList,重新执行渲染指令并更新到屏幕上,请注意,任何时候View的绘制内容发生变化,都需要重新创建DisplayList并重新执行指令更新到屏幕,这个流程的表现性能,取决于我们的View的复杂程度,取决于视觉变化的类型,同时对渲染管道也会产生一些影响。举例说,假如某个文本框尺寸突然变成当前的两倍,在改变尺寸前,需要通过父View重新计算,并摆放其他子View的位置,在这种情况下我们改变了某个View,后面就会有很多工作要做,这些类型的视觉变化需要渲染管道的额外工作,当我们的View的尺寸变化时,触发了测量操作,会经过整个View Hierarchy,询问各个View的新尺寸,我们一旦改变了View的大小就会触发上述过程,无论是填充或者图片尺寸、设置文本大小、宽度、高度等等,如果我们是改变对象位置或者询问布局,或者某个View重新摆放子View都会触发布局操作,会触发整个Hierarchy重新计算对象在屏幕上的新位置,现在Android运行系统已经非常善于处理记录并执行渲染管道,除非我们要处理自定义View或者同时需要绘制太多View,其他情况下一般不会耗费太多时间,测量和布局操作性能也很好,但是当我们的View Hierarchy失控时也更容易出现问题,执行这些功能的时间是和我们的View Hierarchy中需要处理的节点数成正比的,系统需要处理的View越多处理时间就越长。某些View可能比其他View要耗费更多时间,造成这种浪费的首要原因是,View Hierarchy中包含太多的无用View,这些View根本不会显示在屏幕上,一旦触发测量操作和布局操作只会拖累应用程序的性能表现,幸好有一款叫做Hierarchy Viewer工具,它可以帮助我们查找并修复这些流氓View,我们来看看。
方案:用Layout Inspector查看布局深度

你可能感兴趣的:(UI卡顿分析之内存抖动和计算性能优化)