View渲染机制

写在开头

本文章学习了View的渲染机制,了解它对于布局优化会有很大的帮助,可以加深对过度渲染导致的UI卡顿的了解并提出更合适的解决方案。站在前人的肩膀上学习,在此表示感谢。

如何查看过度绘制

在学习渲染机制前先了解下如何查看过度绘制,其实手机上自带工具,在开发者工具中,如图:

这里写图片描述

它是以颜色来区分你屏幕上的某一点像素被渲染过几层的,蓝色,淡绿,淡红,深红。

蓝色: 意味着overdraw 1倍。像素绘制了两次。
绿色: 意味着overdraw 2倍。像素绘制了三次。
淡红: 意味着overdraw 3倍。像素绘制了四次,这事我们应该尝试优化、减少它们。
深红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们,很有可能造成UI卡顿现象。

在实际开发中发现如何您的界面有大片的深红色就有很大的几率发生UI的卡顿现象,那么为什么会发生卡顿呢?下面来介绍下。

为什么UI卡顿?

就时间而言,如果我们的界面在16ms内没完成绘制就会卡顿,给人不好的体验。为什么是16ms呢?说个题外话,如果你玩LOL,在右上角有个Fps的参数,如果低于60(16ms意味着1000/60hz,相当于60fps。)界面就会有明显的卡顿。这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。所以如果我们的应用没有在16ms内完成屏幕刷新的全部逻辑操作,就会发生卡顿。
这是因为Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,VSync是Vertical Synchronization(垂直同步)的缩写。CPU和GPU的处理时间都必须少于一个VSync的间隔,即16.6ms。如果每个间隔都有绘制的情况下,当前的FPS即为60帧。但是CPU和GPU的处理时间因为各种原因都大于一个VSync的间隔(16.6ms),所以在第二个VSync还在处理1区域的绘制时,不可能实现理论上的60FPS,同时也就出现了丢帧(SF: Skipped Frame)情况。用户都会发现画面不是很顺畅,造成了APP卡顿。

Android渲染机制基础认知

CPU: 中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能.CPU负责包括Measure,Layout,Record,Execute的计算操作,将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的纹理).。

GPU:一个类似于CPU的专门用来处理Graphics(图形)的处理器, 作用用来帮助加快栅格化操作,当然,也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。

OpenGL ES是手持嵌入式设备的3DAPI,跨平台的、功能完善的2D和3D图形应用程序接口API,有一套固定渲染管线流程。

DisplayList 在Android把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。

栅格化是将图片等矢量资源,转化为一格格像素点的像素图,显示到屏幕上,通俗的说所谓的栅格化就是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作。它把那些组件拆分到不同的像素上进行显示,用来解决那些复杂的XML布局文件和标记语言,使之转化成用户能看懂的图像,但是这不是直接转换的,XML布局文件需要在CPU中首先转换为多边形或者纹理,然后再传递给GPU进行格栅化,对于栅格化,跟OpenGL有关,格栅化是一个特别费时的操作。

垂直同步VSYNC:让显卡的运算和显示器刷新率一致以稳定输出的画面质量。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。

渲染机制分析

渲染流程线:UI对象—->CPU处理为多维图形,纹理 —–通过OpeGL ES接口调用GPU—-> GPU对图进行光栅化(Frame Rate ) —->硬件时钟(Refresh Rate)—-垂直同步—->投射到屏幕。如下图(大神的图):

这里写图片描述

Android系统每隔16ms发出VSYNC信号(1000ms/60=16.66ms),触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。如果渲染超时,计算渲染时间超过16ms。当这一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件 等待GPU完成栅格化渲染操作,这样会让这一帧画面,多停留了16ms,甚至更多.这样就这造成了 用户看起来 画面停顿。那么如何计算渲染时间呢?

计算View渲染时间

任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一 系列操作。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。

举个例子,当View的大小发生改变,DisplayList就会重新创建,然后再渲染,而当View发生位移,则DisplayList不会重新创建,而是执行重新渲染的操作.

当你的View过于复杂,操作又过于复杂,就会计算渲染时间超过16ms,产生卡顿问题.

所以View的渲染时间一般由Cpu测量Display List的时间+OpenGL渲染Display List所需要的时间+CPU等待GPU处理的时间共同组成。

View过于复杂,布局文件编写不规范则会导致绘制时间太长,就引起了过度绘制OverDraw的问题。那么我们应该怎么注意并解决这一问题呢?

过度绘制及解决

方案一:布局层级优化

层级的优化我们应该注意多方面:

Hierarchy Viewer工具使用,他是查看耗时情况,和布局树的深度的工具。

在开发中,ViewStub,Merge标签的使用,RelativeLayout的使用等等。其实这一部分就是考验你布局的根底了。如果可以的话,约束布局ConstraintLayout也是一个不错的选择。

方案二:

去掉window的默认背景

当我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,这个背景是被DecorView持有的。当我们的自定义布局时又添加了一张背景图或者设置背景色,那么DecorView的background此时对我们来说是无用的,但是它会产生一次Overdraw,带来绘制性能损耗。去掉window的背景可以在onCreate()中setContentView()之后调用getWindow().setBackgroundDrawable(null);或者在theme中添加android:windowbackground=”null”;

方案三:去掉其他不必要的背景

有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。再比如如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也同样可以解决问题。这里只简单举两个例子,我们在开发过程中的一些习惯性思维定式会带来不经意的Overdraw,所以开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上。

方案四:当背景无法避免,尽量用Color.TRANSPARENT

浅谈硬件加速

硬件加速,直观上说就是依赖GPU实现图形绘制加速,软硬件加速的区别主要是图形的绘制究竟是GPU来处理还是CPU,如果是GPU,就认为是硬件加速绘制,反之,软件绘制。大概从Android 4.+开始,默认情况下都是支持跟开启了硬件加速的,也存在手机支持硬件加速,但是部分API不支持硬件加速的情况,如果使用了这些API,就需要主关闭硬件加速,或者在View层,或者在Activity层,比如Canvas的clipPath等。但是,View的绘制是软件加速实现的还是硬件加速实现的,一般在开发的时候并不可见,那图形绘制的时候,软硬件的分歧点究竟在哪呢?

源码:

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        <!--关键点1 是否开启硬件加速-->
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
             ...
            dirty.setEmpty();
            mBlockResizeBuffer = false;
            <!--关键点2 硬件加速绘制-->
            mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
        } else {
          ...
           <!--关键点3 软件绘制-->
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        ...

关键点1是启用硬件加速的条件,必须支持硬件并且开启了硬件加速才可以,满足,就利用HardwareRenderer.draw,否则drawSoftware(软件绘制)。简答看一下这个条件,默认情况下,该条件是成立的,因为4.+之后的手机一般都支持硬件加速,而且在添加窗口的时候,ViewRootImpl会enableHardwareAcceleration开启硬件加速,new HardwareRenderer,并初始化硬件加速环境。

写在最后

https://www.jianshu.com/p/1ef2a9e5aa91
https://www.jianshu.com/p/9ac245657127
https://www.jianshu.com/p/40f660e17a73

猜你喜欢

转载自blog.csdn.net/say_from_wen/article/details/79093883