Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2019-04-01:简述一下 Android 中 UI 的刷新机制? #17

Open
FeatherHunter opened this issue Mar 31, 2019 · 12 comments
Open

2019-04-01:简述一下 Android 中 UI 的刷新机制? #17

FeatherHunter opened this issue Mar 31, 2019 · 12 comments

Comments

@FeatherHunter
Copy link
Collaborator

FeatherHunter commented Mar 31, 2019

提示:

  • Handler中的异步消息和同步消息是什么?(我们通过Handler发出的Message只是同步消息,异步消息是系统级别的,以前可以反射调用,Android高版本已经被@hide,无法反射调用了)
  • 同步屏障又是什么?
  • Android页面刷新操作(performTraversals()-执行三大流程)本质是投递到主线程的消息队列中处理,为什么能保证界面刷新操作优先执行(即使有很多其他耗时的操作)?
@Moosphan Moosphan changed the title 2019-04-1:Handler中的异步消息和同步消息是什么?同步屏障又是什么?Android页面刷新操作(performTraversals()-执行三大流程)本质是投递到主线程的消息队列中处理,为什么能保证界面刷新操作优先执行(即使有很多其他耗时的操作)? 2019-04-1:简述一下 Android 中 UI 的刷新机制? Apr 1, 2019
@Moosphan Moosphan changed the title 2019-04-1:简述一下 Android 中 UI 的刷新机制? 2019-04-01:简述一下 Android 中 UI 的刷新机制? Apr 2, 2019
@FeatherHunter
Copy link
Collaborator Author

界面刷新的本质流程

  1. 通过ViewRootImplscheduleTraversals()进行界面的三大流程。
  2. 调用到scheduleTraversals()时不会立即执行,而是将该操作保存到待执行队列中。并给底层的刷新信号注册监听。
  3. VSYNC信号到来时,会从待执行队列中取出对应的scheduleTraversals()操作,并将其加入到主线程消息队列中
  4. 主线程消息队列中取出并执行三大流程: onMeasure()-onLayout()-onDraw()

同步屏障的作用

  1. 同步屏障用于阻塞住所有的同步消息(底层VSYNC的回调onVsync方法提交的消息是异步消息)
  2. 用于保证界面刷新功能的performTraversals()的优先执行。

同步屏障的原理?

  1. 主线程的Looper会一直循环调用MessageQueuenext方法并且取出队列头部的Message执行,遇到同步屏障(一种特殊消息)后会去寻找异步消息执行。如果没有找到异步消息就会一直阻塞下去,除非将同步屏障取出,否则永远不会执行同步消息
  2. 界面刷新操作是异步消息,具有最高优先级
  3. 我们发送的消息是同步消息,再多耗时操作也不会影响UI的刷新操作

@979451341
Copy link

楼上讲了 应用层,我就说一下 系统层的,
首先屏幕是 大约16.6ms刷新一次(固定的),当界面需要改变时, CPU开始计算,将计算结果 赋予 GPU 的buffer缓存起来,等待刷新时间的到来,然后根据buffer的数据刷新界面
如果当前界面没有变化,CPU不用计算,也不会给GPU的buffer赋值啥的,这个buffer也就没变化,等到刷新时间的到来,会依旧根据buffer刷新屏幕

结论是:界面改不改变都会刷新界面,只是在于CPU是否计算这点区别

UI刷新卡顿,基本都在于卡在CPU计算这一环节,对于根据GPU 的buffer刷新这一环节,在系统里有很高的优先级,楼上就说了同步屏障就是保护这一优先级的一个手段

@risechen
Copy link

risechen commented May 1, 2019

(1)简单概括:
Android应用程序把经过测量、布局、绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据。即应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过显示刷新机制把数据更新到屏幕
(2)应用层:相当于client,把计算好的图层数据通过共享内存shareclient传递给系统层
通过查看Activity的启动流程可以看出每个activity其实都attach了一个PhoneWindow,而PhoneWindow视图树的根节点是一个ViewRootImpl。而整体绘制其实就是在ViewRootImpl的
performTraversals方法调用后,采用深度优先挨个遍历视图中的子view,通过调用子view的onMesure,onLayout和draw方法,计算出它们的大小、位置。至于为何采用深度优先遍历,是因为子view的位置和大小依赖父view,就像我们使用match_parent属性就是依赖父view的宽高。
这里需要注意的是,draw绘制分为cpu和gpu绘制,cpuz绘制又叫软件绘制,gpu又叫硬件绘制。cpu绘制速度慢,兼容性好,gpu绘制速度快,但是兼容性差,占用内存高(8m以上),在android3.0之后才引入了gpu绘制,但是目前还是有很多Opengl接口不支持硬件加速

(3)系统层:service
Android是通过系统层进程SurfaceFlinger服务来把收到的应用层图层数据渲染到屏幕上
主要工作有:
①响应客户端事件,创建Layer与客户端的surface进行连接
②接收客户端数据及属性,修改layer属性,如颜色、尺寸、透明度
③将创建的layer内容刷新到屏幕上
④维护layer的序列(缓冲),并对layer的最终输出做裁剪计算
(4)应用层和系统层通信:简单说就是应用层绘制到缓冲区,SurfaceFlinger把缓存数据渲染到屏幕,两个进程使用安卓匿名共享内存SharedClient缓存需要显示的数据。
由于应用层和系统层分别是两个不同的进程,需要使用跨进程通信来实现数据传输,在Android显示系统中,使用了内部匿名共享内存ShareClient。每一个应用和SurfaceFlinger都会创建一个SharedClient。每个SharedClient中最多可以创建31个SharedBufferStack(window),也就是是说每个应用理论上最多支持31个window,同时每个SharedBufferStack包含2个(<4.1)缓冲区到3个(>=4.1)缓冲区
(5)显示刷新机制:简单说4.1以前无vsync同步机制,绘制时间分散,很可能绘制任务在,vsync信号后16ms末尾才开始,这样就导致帧率很低,4.0以后采用vsync机制,Choreographer强制vsync执行绘制任务,绘制时间得到保证
Android绘制UI采用双缓冲机制,就是SharedBufferStack中的缓冲区,UI总是先在Back Buffer计算完毕后,再交换到Front Buffer渲染到屏幕上。理想情况下一个刷新在16ms内完成(60fps).
4.1之前是双缓冲,4.1之后是3缓冲Tripple Buffer。并不是缓冲区越多越好,只有绘制占用的cpu和gpu时间片都小的时候3缓冲的性能大概率高于双缓冲。
(6)总结
①影响绘制的根本原因:
<1>绘制任务太重,绘制一帧内容耗时太长;
<2>主线程太忙了,导致vsync信号来的时候还没计算好数据导致丢帧;
(7)开发中优化建议:
①主线程不要做耗时操作,如大量的cursor操作和ams操作,执行复杂算法;
②降低gc几率,图片加载优化,避免创建大量对象;
③io密集型任务放在子线程执行
④cpu密集型任务在子线程中串行执行

@MoJieBlog
Copy link
Collaborator

我来说下为什么是16.6毫秒刷新一次UI。经常玩游戏的人肯定知道60fps的时候基本上就感觉不到卡顿。这里的60fps就是每秒60帧。1000/60 = 16.666666...所以当手机刷新频率16.6时,用户就不会感到卡顿

@Saturdaycong
Copy link

都会
但UI刷新并不一定是CPU问题,刷新这个动作本身就是CPU和GPU合作完成的,但是如果刷新的数据特别多,GPU需要绘制的内容过多,如果GPU性能跟不上也会造成界面卡顿

@siren4
Copy link

siren4 commented Aug 25, 2019

1.界面上的任何一个view的刷新请求最终都是调用ViewRootImpl的scheduleTraversals()来实现的。
2.scheduleTraversals() 会先过滤掉同一帧内的重复调用,确保同一帧内只需要安排一次遍历绘制 View 树的任务.
3.scheduleTraversals() 会往主线程的消息队列中发送一个同步屏障,发完同步屏障后 scheduleTraversals() 将 doTraversal() 封装到 Runnable 里面,然后将这个 Runnable 任务以当前时间戳放进一个待执行的队列里,并且向底层订阅下一个屏幕刷新信号Vsync.
4.当下一个屏幕刷新信号发出时,底层就会回调取出之前放进待执行队列里的任务来执行,也就是ViewRootImpl的doTraversal() 操作。
5.doTraversal()中首先移除同步屏障,再会调用performTraversals() 方法根据当前状态判断是否需要执行performMeasure() 测量、perfromLayout() 布局、performDraw() 绘制流程,在这几个流程中都会去遍历 View 树来刷新需要更新的View。
6.等到下一个Vsync信号到达,将上面计算好的数据渲染到屏幕上,同时如果有必要开始下一帧的数据处理。

@nealkafuly
Copy link

为什么要问这些题目,对项目的优化有作用吗,艹,别乱加面试题好吗,

@nealkafuly
Copy link

这些题目一般不该系统代码,app需要知道jb

@nealkafuly
Copy link

哦哦,原来可以根据系统做了什么,然后就可以避免一些搞耗时的动作,比如布局层数减少,这个题目的目的就在这里,会优化就完事啦,还搞这些,浪费时间。

@zhouyueyuedsf
Copy link

几个点可以说下:

  1. 屏幕刷新的双缓冲机制(本质上是生产者和消费者关系)
  2. requestLayout/invalidate与onVsync的关系
  3. android中优化相关(键盘弹出对动画的影响等)

@mlinqirong
Copy link

mlinqirong commented Dec 16, 2021

16毫秒1帧 1秒60/帧 刷新一次

@AndroidJiang
Copy link

为什么要问这些题目,对项目的优化有作用吗,艹,别乱加面试题好吗,

当然有作用,你优化项目卡顿时,这个就是必须了解的,如果这都不知道,那请问你是如何定位卡顿问题呢?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants