2.1 布局渲染流程与原理

2020-10-15  本文已影响0人  _大壮

GPU

GPU用途是将计算机系统所需要的显示信息进行转换驱动,并向显示器提供行扫描信号,控制显示器的正确显示,是连接显示器和个人电脑主板的重要元件,也是“人机对话”的重要设备之一。显卡作为电脑主机里的一个重要组成部分,承担输出显示图形的任务,对于从事专业图形设计的人来说显卡非常重要。

如果CPU想画一个二维图形,只需要发个指令给GPU,如“在坐标位置(x, y)处画个长和宽为a×b大小的长方形”,GPU就可以迅速计算出该图形的所有像素,并在显示器上指定位置画出相应的图形,画完后就通知CPU “我画完了”,然后等待CPU发出下一条图形指令。

 首先,我们要知道Android系统每隔16ms就重新绘制一次Activity,也就是说,我们的应用必须在16ms内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60帧,然而这个每秒帧数的参数由手机硬件所决定,现在大多数手机屏幕刷新率是60赫兹(赫兹是国际单位制中频率的单位,它是每秒中的周期性变动重复次数的计量),也就是说我们有16ms(1000ms/60次=16.66ms)的时间去完成每帧的绘制逻辑操作,如果错过了,比如说我们花费34ms才完成计算,那么就会出现我们称之为丢帧的情况。

在一个典型的显示系统中,

CPU、GPU、显示器三个部分,

CPU负责计算数据,把计算好数据交给GPU 负责Measure,Layout,Record,Execute等计算操作

GPU会对图形数据进行渲染, 负责Rasterization(栅格化)操作

然后显示器 负责把buffer里的数据呈现到屏幕上

image.png

SouthEast

Android VSYNC原理http://shangjin615.iteye.com/blog/1775684

以时间的顺序来看下将会发生的异常:

Step1. Display显示第0帧数据,此时CPU和GPU渲染第1帧画面,而且赶在Display显示下一帧前完成

Step2. 因为渲染及时,Display在第0帧显示完成后,也就是第1个VSync后,正常显示第1帧

Step3. 由于某些原因,比如CPU资源被占用,系统没有及时地开始处理第2帧,直到第2个VSync快来前才开始处理

Step4. 第2个VSync来时,由于第2帧数据还没有准备就绪,显示的还是第1帧。这种情况被Android开发组命名为“Jank”。

Step5. 当第2帧数据准备完成后,它并不会马上被显示,而是要等待下一个VSync。

所以总的来说,就是屏幕平白无故地多显示了一次第1帧。原因大家应该都看到了,就是CPU没有及时地开始着手处理第2帧的渲染工作,以致“延误军机”。

从上图可以看到,CPU和GPU的处理时间因为各种原因都大于一个VSync的间隔(16.6ms),所以在第二个VSync还在处理1区域的绘制时, 试想用户盯着同一张图看了32ms而不是16ms,当然很容易察觉出卡顿感,哪怕仅仅出现一次掉帧,用户都会发现动画不是很顺畅,大家在察觉到APP卡顿的时候

Android GPU 设计的缓冲机制

button转换成特定的向量图形是一个时间消耗过程,再由向量图形传递给GPU又是一个时间消耗过程,而由CPU传递给GPU同样是一个非常耗时的过程。这样就意味着,GPU中进行栅格化所节省下来的时间,可能在这里被消耗大半。

幸运的是Open GL考虑到了这一点,它提供了一个类似缓存到机制:CPU上传到GPU中的资源,可以作为缓冲保存在GPU当中,在下次再次利用的过程中,就省去了CPU的格式转换和CPU上传到GPU的过程消耗。

Android系统就灵活利用到了这一点,它在系统启动过程中,就将主题中的系统资源以一个单一向量图形的形式上传至GPU,以后在调用系统资源时,就可以直接在GPU中取到相应资源,而不需要转换和传递。这就是加载Android系统图片为啥这么快的原因。

然而,有了这个机制就可以万事大吉了?并不,随着UI画的图越来越诡异,产品设计的动画越来越彪悍,GPU的缓冲机制变得几乎形同虚设,因为每一个图片都是不同的,都无法服用,因此GPU中的缓存资源只能通过不断被覆盖来达到相应效果,

Android系统提供了差异化绘制机制,简单来说就是缓存的旧资源与即将写入的新资源进行对比,只对发生了改变的部分进行重新处理。以此缓解GPU的压力。

在上面,我们有提到,有DisplayList对CPU处理好的格式资源以及需要进行的相应的绘制指令,进行接收。这里的DispalyList在特殊情况下,可以对其接收的信息进行复用,举例来说:

如果一个button改变了其位置:GPU可以将DisplayList中的信息可以进行复用。

如果一个button改变了其大小或者其形状,表面颜色发生改变(视觉上的形体,色彩改变 ):GPU就无法使用之前CPU传递来的DisplayList,需要通过CPU进行重新格式转换,然后将命令和转换好的资源存入一个新的DisplayList当中。

过度绘制 案例

<item name="android:windowBackground">@null</item>

或者 getWindow.setBackgroundDrawable(null)

或者将布局设置的白色去掉

布局优化

UI是用户感知与交互的第一也是唯一的途径,是影响用户体验最关键的一部分。每一个写在布局中的组件都需要初始化,进行布局,绘制的过程。如果在ViewGroup里面有很深的子控件层级, 或者有不必要的子控件,这样的布局文件会使得程序变卡,性能降低。

首先布局文件我们需要牢记:

1、布局层级越少越好

2、布局中控件数越少越好

能肯定的是需要测量与绘制的控件越少,耗时越低,性能越好。而层级越少意味着在绘制控件的时候需要参考的父/子控件越少。

Hierarchy Viewer

我们可以借助一些工具来查看我们的布局层级。Hv工具,现在HV在SDK里找不到了,google已经把它隐藏起来。并且hv有只能在开发版手机或模拟器运行的限制。

   目前的HV工具已经处于废弃状态,试了很多次用不了。我们可以前往

http://mirrors.zzu.edu.cn/android/repository/
下载旧版本的tools

image.png

下载完后解压,双击打开HV工具


image.png

能够看到hv被设置为不赞成使用的了。

我们这里使用模拟器来检查我们的布局:

   由于hv的本身的问题,对于一些的布局不能加载,并且它也不再维护更新了。我们使用另外一个app来观察。打开开源中国
image.png

然后我们能在hv上看到我们app的进程:

image.png

然后加载布局结构图

image.png

我们来观察下这个布局,

image.png

我们找到

image.png

id为content的节点。对于我们开发者来说,我们的布局加载在系统id为content的布局中。我们看到在这个content的viewgroup中有215个child view。

我们可以对照到我们的布局当中,能够看到我们布局的根结点和hv中content的根结点匹配,都是FrameLayout

image.png 现在我们在hv中点击 image.png

获得view tree的加载时间等信息。然后我们看到

image.png

在这个节点上有三个内容:Measure, Layout和Draw。这对照了我们视图的绘制流程,onMeasure测量计算、onLayout布局位置以及onDraw 绘制。

   我们把视图缩小。
image.png

看到这个简直崩溃了,太多了。

我们看到视图中几乎所有节点都有三个点,颜色也各不相同

这三个点也是代表着View的Measure, Layout和Draw。

不同颜色意味着不同的速度:

绿: 表示该View的此项性能比该View Tree中超过50%的View都要快;例如,代表Measure的是绿点,意味着这个视图的测量时间快于树中的视图对象的50%。

黄: 表示该View的此项性能比该View Tree中超过50%的View都要慢;

红: 表示该View的此项性能是View Tree中最慢的;。

我们从最右侧开始观察。

image.png

然后放大视图。

image.png

我们看大最右侧的一排view id都一样。这里肯定是个类似reclerview的视图。

我们找到左边的父节点

image.png

果然是个recyclerview。而去能看到id是view_recycler_inactive。我们可以在工程中搜索一下这个id


image.png

这个布局我们看到微博的这么多相同的节点就需要有警惕,会不会有无意义的嵌套,或者能不能减少?

我们观察下:

先来看看

image.png

嗯~~~,这里有id。

这个id我们要确保代码中是否会通过这个id拿到这个视图进行一些操作。


image.png

代码中会拿这个id,但是我们搜索没有地方再去操作他。

所以这里就算我们不优化布局,也存在一个问题,不需要使用它为什么要去find?撩完就不要了。

image.png

我们放过人家。

相应的这个viewgroup也尝试去掉

image.png

去掉后我们的布局没有变化,表示这确实是多此一举的一个LinearLayout。

继续:

image.png

再往下是个scroolview,看来这个不能去掉了。

不过里面的一个LinearLayout好像可以试试:

image.png

也有id,看看代码中有没有不负责任。

image.png

看来不能去掉。

  Ok,我们回归来分析hv给我们的信息,
image.png

这个RecyclerView的item一片红点,那我们看看他的item布局:

image.png

顺便也可以看看另一个RecyclerView

image.png

两个都是****view_tab_item.

image.png

这里看着没什么可以优化的。也没看到多余的层级,每一个****TextView****也都有自己的作用。

如果我们根据****HV****提示一个个节点看下去能优化的地方还有很多,但是由于不是作者,也不是工程参与者,我们实在没办法结合需求来观察。

(看情况是否写例子)

我们刚刚说到****HV****现在被置为****deprecated****的了,那么现在我们如何来检测布局的层级?

Layout Inspactor

   LI可以帮助我们在程序运行的时候分析布局文件(ui绘制时间与嵌套层级),从而找到性能瓶颈。

Hierarchy Viewer已经被置为deprecated的了,取而代之的是Layout Inspactor,也可以说LI就是新版的HV。并且Li没有hv只能在开发版手机或模拟器运行的限制。

现在我们使用真机与li来检测我们的布局

   那么要在AS使用LI非常方便

在AS中打开方式:

image.png image.png

等待一段时间后我们能看到

image.png

这个文件中右侧是我们选中view的属性,左侧则是布局层级显示

image.png

我们看到使用hv可能会忽略的一些东西,能在li看的更清楚。

比如这里,我们的content下面是一个FrameLayout,我们的content本身就已经是一个v7包下面的ContentFrameLayout extend FrameLayout。那我们还有写framelayout的必要吗?

   打开布局,把节点换成
image.png

我们再运行看看。。

   没有任何变化,但是我们再使用li查看
image.png

看到确实少了一个FrameLayout,虽然效果并不明显,但是我们优化的道路又前进了一小步。

刚刚我们使用了一个标签:merge。

类似的标签还有include和viewstub

Include****、****merge****与****viewStub

在开发中我们经常会需要在不同地方放入相同的布局,有些布局可以被复用。这种情况可以创建一份单独的xml来定义这个可被复用的layout。比如需要自己定义ToolBar/ActionBar。那么需要设置theme为:

image.png

然后将布局设置为

image.png

最后在Activity中

setSupportActionBar((Toolbar) findViewById(R.id.toolBar));

基本上如果使用了Toolbar那么我们app的每个界面都会需要,这里可以根据情况来,将Toolbar整体写入到一个单独的xml中(Toolbar中内容不会改变)或者将Toolbar内容写到单独xml中(Toolbar中内容会改变)。为了能够重用代码

image.png

include标签能够引入另外一个xml布局到当前布局中。

现在我们这样写能够轻松的在需要的地方使用include引入一个通用的布局,但是这在一些情况下会造成多了一个层级标签LinearLayout。

比如上面这种情况,我们可以把LinearLayout标签换成merge

image.png

merge标签的作用在于不会增加层级会被系统自动忽略。

当然Toolbar中的内容经常会改变,在同一个页面中我们也会经常需要根据不同的状态来对他进行改变。比如

image.png

点击搜索图标,toolbar的视图变成一个输入框一个按钮。很明显实现这个功能非常简单。

image.png

然后布局里一个输入框+按钮

image.png

一个textview+图片

image.png

默认将edit_layer设置为GONE不可见。

INVISIBLE

view在layout布局文件中会占用位置,但是view为不可见,该view还是会创建对象,会被初始化,会占用资源。

GONE

view在layout布局文件中不占用位置,但是该view还是会创建对象,会被初始化,会占用资源。

ViewStub

不可见,不用占用资源,只有设置viewstub为visible、invisible或者调用其inflater()方法时,其对应的布局文件才会被初始化。局限是viewstub的引用对象需要是一个布局layout文件,如果要是单个的view的话,viewstub就不合适了。

那么类似上面这种情况,我们可以将edit_layer使用ViewStub来引入

image.png

其中layout和include一样都表示需要加载的xml布局文件。

inflatedId是设置---在这个布局被加载后的id。

也就是说在ViewStub被加载后,我们再使用edit_stub这个id就不能找到ViewStub了。而应该使用edit_layer来查找这一个视图。

Lint****建议

我们看到优化布局是一项非常繁琐复杂的工作,所以我们平时在开发中一定要注意。

出了使用hv和li两种工具之外,我们还可以使用Lint检查,让Lint给出存在问题的地方来修改(activity_main4)。

image.png

然后弹出框

image.png

选择Lint检查的范围

image.png

按照Lint的提示修改布局即可。

上一篇下一篇

猜你喜欢

热点阅读