1. 说说Android的UI刷新机制
考察点
- 丢帧一般是什么原因引起的?
- 主线程有耗时操作,耽误了view的绘制
- Android刷新频率60帧/秒,每隔16ms调onDraw绘制一次?
- 不是,16ms是指vsync信号间隔,只有应用端的view主动发起重绘,这样才会向surfaceFlinger请求接收vsync信号,这样在下次vsync信号到来时才会发起绘制,onMeasure->onLayout->onDraw
- onDraw完之后屏幕会马上刷新么?
- 不会立即刷新,需要等到下一次vsync信号来之后才会
- 如果界面没有重绘,还会每隔16ms刷新屏幕么?
- 如果没有重绘就不会受到vsync信号,屏幕依然回以60帧刷新,只不过屏幕上依然用的是旧的数据
- 如果在屏幕快要刷新的时候才去onDraw绘制会丢帧么?
- 与vsync信号有关,跟什么时候onDraw没有关系,要等到下一次vsync信号来的时候
先来看一张基本显示原理图
- 首先应用会向系统服务申请buffer
- 然后系统服务会返回buffer
- 应用绘制后提交buffer到系统服务
- 系统服务把buffer写到屏幕的帧缓冲区
- 屏幕以一定的帧率刷新,每次刷新就会把图像数据从缓冲区中取出显示在屏幕上,如果没有新的数据,屏幕就一直显示老的数据
- 屏幕的图像缓存不只一个,加入只有一个缓存,如果屏幕正在读缓存,而系统服务又在写缓存,这会导致屏幕上的显示很奇怪,比如一半显示第一帧的内容,另一半显示第二帧的内容,所以其实有两个缓存,一个缓存拿来显示,另外一个缓存系统服务往里面写缓存,这样两个缓存互不干扰,当屏幕要显示下一帧时,交换两个缓存就可以了
关于屏幕刷新
屏幕是周期性刷新的,根据vsync信号,vsync信号是一个固定频率的脉冲信号,屏幕每次收到信号就会冲缓冲区里取一帧图像出来显示,绘制是由应用端发起的,可以随时发起,如图64
- 第二帧图像是在屏幕第二个vsync信号后半段发过来的,其绘制时间并不长,但还是横跨了两个vsync信号周期,直到第四次vsync信号刷新时才显示出来,这种现象是影响用户体验的,如果频繁出现,会造成卡顿
- 如何优化呢?如果绘制和vsync一致就解决了,只要保证每一次绘制小于16ms就可以了,那么安卓系统是如何做的呢
- 关键类就是Choreographer(大名鼎鼎的编舞者),UI绘制的节奏完全由Choreographer来控制
Choreographer实现原理
- Choreographer是与viewRootlmpl一起创建的,在每一个vsync周期内只会绘制一次(不论requestLayout多少次)
- 如图65
- 如图65
- 下面来看Choreographer中的callBack数组
- 图66
- 图66
答题要点
- Vsync的原理是怎样的?
- Choreographer的原理是怎样的?
- UI刷新的大致流程,应用和SurfaceFlinger的通信过程?
整体流程总结
- 首先应用层的view调用了requestLayout重绘,其实就是new了一个runnable到Choreographer消息队列中
- Choreographer没有马上处理消息,而是先向surfaceFlinger请求下一个vsync信号,调用方法为requestNextVsync
- 然后surfaceFlinger就会在下一个vsync信号来的时候通过调用postSyncEvent向choreographer发送了一个通知
- choreographer收到通知之后就会处理消息队列中的消息
- 之前的requestLayout对应的runnable中执行的就是performTraversal函数去真正的执行绘制
2. surface跨进程通信原理
考察点
- 怎么理解surface,它是一块buffer吗?
- 不是,surface只是一个壳子,里面包含了一个能生产buffer的对象GraphicBufferProducer
- 如果是, surface跨进程传递怎么带上这个buffer?
- 如果不是,surface又是怎么跨进程传递的?
- 传的是GraphicBufferProducer(GBP)对象
- Activity的surface在系统中创建后,是怎么跨进程传回应用的?
- 系统中创建的是surfaceControl对象,不是surface对象,sufaceControl中包含GPP,跨进程返回到应用
先来看下surface在java层和native层的实现
Activity的surface是怎么跨进程传递的? Activity有自己的decorView,decorView需要绘制,需要一个surface,surface需要向系统申请,系统生成好了之后就会返回给应用,这里涉及surface的传递,来看一下performTraversals函数
- relayoutWindow向系统申请surface
- mWindowSession可以看作是应用和WMS打开的一个通信通道
- 如图69
结论
- surface本质是GraphicBufferProducer, 而不是buffer
- surface跨进程传递,本质就是GraphicBufferProducer的传递
3.surface的绘制原理
考察点
- surface绘制的buffer是怎么来的?
- 通过GBP(GraphicBufferProducer)向SurfaceFlinger的BufferQueue申请的
- buffer绘制完了又是怎么提交的?
- 通过GBP向BufferQueue提交的
- (canvas绘制的底层用的是skia引擎)
先看下java层的代码
- 图70
总结
- 画面的左边是应用,画面的右边是SurfaceFlinger
- 应用中要绘制图像的话,需要创建一个surface,在surface上绘制需要buffer
- buffer哪里来呢,需要在SurfaceFlinger进程中创建一个BufferQueue,一个surface对应一个BufferQueue
- BufferQueue是一个双端队列,有两端,一端是producer端,另一端是consumer端,
- producer端需要跨进程传回到应用进程,交给surface保管,surface要绘制的时候,就用producer端从BufferQueue通过binder调用申请一块buffer,这块buffer是作为canvas的bitmap(这里的bitmap是一种数据结构)的缓冲区,canvas绘制完成之后,再将buffer返回给BufferQueue,BufferQueue就会通知consumer端,回调onFrameAvailable,回调就表示又有一帧数据可用了
- consumer可以在SurfaceFlinger进程,也可以传给别的应用进程,也就是说,consumer就是用来消费这一帧数据的
4. vsync机制
考察点
- VSync信号的生成机制 ->硬件生成与软件生成
- VSync在SurfaceFlinger中的分发流程
- VSync信号的分发原理
先从宏观上了解下分发流程
- HWComposer是从硬件生vsync信号
- 如果硬件模块没有加载成功,还可以在VSyncThread线程中通过软件模拟生成Vsync信号
- vsync信号分发给了一个工作线程,叫做DispSyncThread
- 工作线程收到vsync信号之后,将信号一分为二,两路分发,分发给两个线程,app-EventThread和sf-EventThread
- app-EventThread是将vsync信号分发给APP进程
- sf-EventThread是将信号分发给SurfaceFlinger进程
- vsync为什么要一分为二呢
-
vsync信号发生的时候,一方面要通知应用绘制UI,另一方面通知SurfaceFlinger对绘制完的图像进行合成渲染
-
如果都在vsync信号来的时候一起“干活”,就会发生抢占cpu资源的情况,所以安卓FrameWork将vsync信号一分为二,并且各自加上了偏移量,而且它们的偏移量也不一致,这样就可以错开分发互不影响
-
下面看下vsync信号的分发流程
- vsync信号首先会唤起EventThread线程
- EventThread线程唤醒之后,就会把vsync信号分发出去,通过注册到EventThread中的connection分发出去,如何分发呢,每个connection中都有一堆描述符
- 一个描述符用于发送数据mSenderFd
- 一个描述符用于接收数据mReceiverFd
- EventThread发送事件的时候向发送描述符mSenderFd写了一个数据
- 接收描述符mReceiverFd接收到了数据,
- 对于APP进程来说,mReceiverFd就会在线程消息队列中,looper会监听它
- 对于SurfaceFlinger来说,mReceiverFd也要被监听,同样要在消息队列中被监听
- 所以当EventThread写数据的时候SF/APP就能收到读事件,触发回调