likes
comments
collection
share

Framework源码面试六部曲:6.Android屏幕刷新机制

作者站长头像
站长
· 阅读数 19

前言

今天在电脑上翻出了很久之前整理笔记Framework源码面试,Flutter,以及一部分面试专题。拿出来温习一下。

关注公众号:Android苦做舟

今天先讲Framework源码篇

1.Framework源码面试:Activity启动流程

2.Framework源码面试:Binder面试

3.Framework源码面试:Handler面试

4.Framework源码面试:事件分发机制

5.Framework源码面试:onMeasure测量原理

6.Framework源码面试:Android屏幕刷新机制

众所周知,理解刷新机制对于Android程序员来说至关重要

讲解面试之前,先理解下什么是刷新机制,他的原理是什么

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

  1. CPU
  2. GPU
  3. display

两个部分,

计算过程: CPU负责计算数据,把计算好数据交给GPU,GPU会对图形数据进行渲染,渲染好后放到buffer里存起来,然后display(有的文章也叫屏幕或者显示器)负责把buffer里的数据呈现到屏幕上。

显示过程: 简单的说就是CPU/GPU准备好数据,存入buffer,display每隔一段时间去buffer里取数据,然后显示出来。

display读取的频率是固定的,比如每个16ms读一次,但是CPU/GPU写数据是完全无规律的。

​ 上述内容概括一下就是说,屏幕的刷新包括以下步骤:CPU 计算屏幕数据、GPU 进一步处理和缓存、最后 display 再将缓存中(buffer)的屏幕数据显示出来。

友情提示:在咱们的开发过程中应该接触不到 GPU、display 这些层面的东西,所以我把这部分工作都称作底层的工作了,下文出现的底层指的就是除了 CPU 计算屏幕数据之外的工作。

​ 对于 Android 而言,第一个步骤:CPU 计算屏幕数据指的也就是 View 树的绘制过程,也就是 Activity 对应的视图树从根布局 DecorView 开始层层遍历每个 View,分别执行测量、布局、绘制三个操作的过程。

​ 也就是说,我们常说的 Android 每隔 16.6ms 刷新一次屏幕其实是指:底层以固定的频率,比如每 16.6ms 将 buffer 里的屏幕数据显示出来。

面试问题1:16.6ms 刷新一次屏幕到底是什么意思呢?是指每隔 16.6ms 调用 onDraw() 绘制一次么?

我们常说的 Android 每隔 16.6 ms 刷新一次屏幕其实是指底层会以这个固定频率来切换每一帧的画面。

面试问题2:如果界面一直保持没变的话,那么还会每隔 16.6ms 刷新一次屏幕么?

这个每一帧的画面也就是我们的 app 绘制视图树(View 树)计算而来的,这个工作是交由 CPU 处理,耗时的长短取决于我们写的代码:布局复不复杂,层次深不深,同一帧内刷新的 View 的数量多不多。

面试问题3:界面的显示其实就是一个 Activity 的 View 树里所有的 View 都进行测量、布局、绘制操作之后的结果呈现,那么如果这部分工作都完成后,屏幕会马上就刷新么?

CPU 绘制视图树来计算下一帧画面数据的工作是在屏幕刷新信号来的时候才开始工作的,而当这个工作处理完毕后,也就是下一帧的画面数据已经全部计算完毕,也不会马上显示到屏幕上,而是会等下一个屏幕刷新信号来的时候再交由底层将计算完毕的屏幕画面数据显示出来。

面试问题4:网上都说避免丢帧的方法之一是保证每次绘制界面的操作要在 16.6ms 内完成,但如果这个 16.6ms 是一个固定的频率的话,请求绘制的操作在代码里被调用的时机是不确定的啊,那么如果某次用户点击屏幕导致的界面刷新操作是在某一个 16.6ms 帧快结束的时候,那么即使这次绘制操作小于 16.6 ms,按道理不也会造成丢帧么?这又该如何理解?

当我们的 app 界面不需要刷新时(用户无操作,界面无动画),app 就接收不到屏幕刷新信号所以也就不会让 CPU 再去绘制视图树计算画面数据工作,但是底层仍然会每隔 16.6 ms 切换下一帧的画面,只是这个下一帧画面一直是相同的内容。

面试问题5 :Android 卡顿原因,为什么Android会发生卡顿

​ Android系统每隔16ms会发出VSYNC信号重绘我们的界面(Activity)。

​ **为什么是16ms:**因为Android设定的刷新率是60FPS(Frame Per Second),也就是每秒60帧的刷新率, 约16ms刷新一次。这就意味着,,我们需要在16ms内完成下一次要刷新的界面的相关运算,,以便界面刷新更新。

​ 举个例子,当运算需要24ms完成时,16ms时就无法正常刷新了,而需要等到32ms时刷新,这就是丢帧了。丢帧越多,给用户的感觉就越卡顿。

Framework源码面试六部曲:6.Android屏幕刷新机制

卡顿的原因分析

​ 我们知道,对于APP的每一个View,Android系统都会通过三个步骤来渲染:Measure(测量)、Layout(布局)和Draw(绘制)。measure从最顶部的节点开始,顺着layout树形结构依次往下测量每个view需要在屏幕中展示的尺寸大小。每个子节点都需要向父节点提供自己的尺寸来决定展示的位置,遇到冲突时,父节点可以强制子节点重新measure(可能导致时间消耗为原来的2-3倍)。因此扁平化的view结构会性能更好。

RelativeLayouts经常需要measure所有子节点两次才能把子节点合理的布局。如果子节点设置了weights属性,LinearLayouts也需要measure这些节点两次,才能获得精确的展示尺寸。如果LinearLayouts或者RelativeLayouts被套嵌使用,measure所费时间可能会呈指数级增长。

一旦view开始被measure,该view所有的子view都会被重新layout,再把该view传递给它的父view,如此重复一直到最顶部的根view。layout完成之后,所有的view都被渲染到屏幕上。需要特别注意到是,并不是只有用户看得见的view才会被渲染,所有的view都会。

**面试问题6 :**我们怎么解决Android卡顿的问题

APP拥有的views越多,measure,layout,draw所花费的时间就越久。要缩短这个时间,关键是保持view的树形结构尽量扁平,而且要移除所有不需要渲染的view。移除这些view会对加速屏幕渲染产生明显的效果。理想情况下,总共的measure,layout,draw时间应该被很好的控制在16ms以内,以保证滑动屏幕时UI的流畅。

对于布局的检查,我们可以通过Hierarchy Viewer来检查。Hierarchy Viewer不仅可以很方便可视化的查看屏幕上套嵌的view结构,还可以点击任何一个view来展示子view的数量,和measure, layout, draw的耗时。Hierarchy Viewer还可以帮助发现overdraw(重复的绘制)。

关注公众号:Android苦做舟