【Android FrameWork】③UI体系相关

1,060 阅读7分钟

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写到屏幕的帧缓冲区
  • 屏幕以一定的帧率刷新,每次刷新就会把图像数据从缓冲区中取出显示在屏幕上,如果没有新的数据,屏幕就一直显示老的数据
  • 屏幕的图像缓存不只一个,加入只有一个缓存,如果屏幕正在读缓存,而系统服务又在写缓存,这会导致屏幕上的显示很奇怪,比如一半显示第一帧的内容,另一半显示第二帧的内容,所以其实有两个缓存,一个缓存拿来显示,另外一个缓存系统服务往里面写缓存,这样两个缓存互不干扰,当屏幕要显示下一帧时,交换两个缓存就可以了
    • 图63

关于屏幕刷新 屏幕是周期性刷新的,根据vsync信号,vsync信号是一个固定频率的脉冲信号,屏幕每次收到信号就会冲缓冲区里取一帧图像出来显示,绘制是由应用端发起的,可以随时发起,如图64

  • 第二帧图像是在屏幕第二个vsync信号后半段发过来的,其绘制时间并不长,但还是横跨了两个vsync信号周期,直到第四次vsync信号刷新时才显示出来,这种现象是影响用户体验的,如果频繁出现,会造成卡顿
  • 如何优化呢?如果绘制和vsync一致就解决了,只要保证每一次绘制小于16ms就可以了,那么安卓系统是如何做的呢
  • 关键类就是Choreographer(大名鼎鼎的编舞者),UI绘制的节奏完全由Choreographer来控制

Choreographer实现原理

  • Choreographer是与viewRootlmpl一起创建的,在每一个vsync周期内只会绘制一次(不论requestLayout多少次)
    • 如图65
  • 下面来看Choreographer中的callBack数组
    • 图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层的实现

  • 图68

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就能收到读事件,触发回调