努比亚技能团队原创内容,转载请务必注明出处。
当用户抱怨手机在利用过程中存在卡顿标题标时间,会严峻影响用户对手机品牌的好感和应用APP的体验,从而导致用户对手机品牌的忠诚度低落或应用APP的装机留存率降落。所以无论是手机装备厂商照旧应用APP开辟者,又或是Android体系的维护者Google都会对界面卡顿标题非常器重,会将界面的流通度作为焦点性能体验指标进行一连的优化。说到流通度,本质上就是要办理用户操纵手机过程中的界面丢帧标题,原来一秒钟屏幕上须要更新60帧画面,但是由于种种缘故原由,这期间屏幕上只更新了55帧画面,这就是出现丢帧,在用户主观肉眼看来就是感知卡顿。那么当出现了丢帧卡顿的标题时,我们该怎样动手去分析与优化办理呢?关于这块的内容,笔者将联合多年来的工作履历与明白,分三篇系列文章来解说:
- Android卡顿掉帧标题分析之原理篇
- Android卡顿掉帧标题分析之工具篇
- Android卡顿掉帧标题分析之实战篇
在分析任何标题之前,我们都须要先弄清晰其根本原理,也就是要把握了这个“道”,才华真正动手去分析标题,否者只能是弄得一头雾水,也没法真正的明白息争决标题。所以要想分析并办理界面掉帧卡顿标题,我们就先须要知道在Android体系上应用UI线程到底是怎样完成一帧画面的上帧显示动作的(本文解说的内容重要基于Android原生应用的绘制渲染流程,对于游戏应用和Flutter开辟的应用流程会不太一样,由于篇幅所限,本文暂不涉及,可以关注团队后续别的文章的内容)。由于大部门应用界面的上帧更新画面动作都是由用户手指触摸屏幕而触发,所以本文以手指上下滑动应用界面的操纵场景为例,联合Systrace分析一下Android应用上帧显示的原理。
1 Input事故处理处罚机制
1.1 体系机制分析
Android 体系是由事故驱动的,而 Input 是最常见的事故之一,用户的点击、滑动、长按等操纵,都属于 Input 事故驱动,此中的焦点就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 system_server历程中的两个 Native循环线程,负责读取和分发 Input 事故。整个处理处罚过程大致流程如下:
- 触摸屏会按照屏幕硬件的触控采样率周期,每隔几毫秒扫描一次,如果有触控事故就会上报到对应的装备驱动;体系封装了一个叫EventHub的对象,它利用inotify和epoll机制监听/dev/input目次下的input装备驱动节点,通过EventHub的getEvents接口就可以监听并获取到Input事故;
- InputReader负责从EventHub内里把Input事故读取出来,然后交给 InputDispatcher 进行事故分发;
- InputDispatcher在拿到 InputReader获取的事故之后,对事故进行包装后,寻找并分发到目的窗口;
- InboundQueue队列(“iq”)中放着InputDispatcher从InputReader中拿到的input事故;
- OutboundQueue(“oq”)队列内里放的是即将要被派发给各个目的窗口App的事故;
- WaitQueue队列内里记载的是已经派发给 App(“wq”),但是 App还在处理处罚没有返回处理处罚乐成的事故;
- PendingInputEventQueue队列(“aq”)中记载的是应用须要处理处罚的Input事故,这里可以看到input事故已经通报到了应用历程;
- deliverInputEvent 标识 App UI Thread 被 Input 事故唤醒;
- InputResponse 标识 Input 事故地区,这里可以看到一个 Input_Down 事故 + 多少个 Input_Move 事故 + 一个 Input_Up 事故的处理处罚阶段都被算到了这里;
- App 相应处理处罚Input 事故,内部会在其界面View树中逐层分发和处理处罚。
用一张图形貌整个过程大致如下(关于这部门详细的Android体系源码实现流程可以参考这篇文章https://juejin.cn/post/6956500920108580878):
1.2 联合Systrace分析
从上面的体系机制的分析可以看出,整个Input触控事故的分发与处理处罚重要涉及到两个历程:一个是system_server体系历程,另一个是当前焦点窗口所属的Setting应用历程。
一、system_server历程的处理处罚过程:
- 当用户手指在Setting应用界面滑动时,体系system_server历程中的native线程InputReader会从EventHub中读取其利用linux的epoll机制监听到的屏幕驱动上报的Input触控事故,然后唤醒别的一条native线程InputDispatcher负责进行事故的进一步分发处理处罚。
- InputDispatcher被唤醒后会先将事故放到InboundQueue队列(也就是Systrace上看到的“iq”队列)中,然后找到详细处理处罚此input事故的应用目的窗口,并将Input事故放入对应的应用目的窗口的OutboundQueue队列(也就是Systrace上看到的“oq”队列)中,等候进一步通过SocketPair双工信道发送input事故到应用目的窗口中;
- 末了当事故发送给详细的应用目的窗口后,会将事故移动到WaitQueue队列中(也就是Systrace上看到的“wq”队列)并不停等候收到到目的应用处理处罚Input事故完成后的反馈后再从队列中移除,如果5秒内没有收到目的应用窗口处理处罚完成此次Input事故的反馈,就会报该应用ANR非常事故。以上整个过程在Android体系AOSP源码中都加有相应的Systrace tag,如下Systrace截图所示:
二、应用历程的处理处罚过程:当Input触控事故通过socket通报到Settings应用历程这边后,会唤醒应用的UI线程在ViewRootImpl#deliverInputEvent的流程中进行Input事故的详细分发与处理处罚。详细的处理处罚流程:
- 先交给之前在添加应用PhoneWindow窗口时的ViewRootImpl#setView流程中创建的多个差异范例的InputUsage中依次进行处理处罚(好比对输入法处理处罚逻辑的封装ImeInputUsage,某些key范例的Input事故会由它先交给输入法历程处理处罚完后再交给应用窗口的InputUsage处理处罚),整个处理处罚流程是按照责任链的筹划模式进行;
- 末了会交给负责应用窗口Input事故分发处理处罚的ViewPostImeInputUsage中详细处理处罚,这内里会从View结构树的根节点DecorView开始遍历整个View树上的每一个子View或ViewGroup控件实行事故的分发、拦截、处理处罚的逻辑;
- 末了触控事故处理处罚完成后会调用finishInputEvent竣事应用对触控事故处理处罚逻辑,这内里会通过JNI调用到native层InputConsumer的sendFinishedSignal函数中通过socket消息关照体系框架中的InputDispatcher该Input事故处理处罚完成,触发从"wq"队列中实时移除待处理处罚事故以免报ANR非常。
- 一次滑动过程的触控交互的InputResponse地区中一样寻常会包罗一个Input的ACTION_DOWN事故+多个ACTION_MOVE事故+一个ACTION_UP事故,Settings应用界面中的干系View控件在收到多个ACTION_MOVE触控事故后,颠末判定为用户手指滑动活动,一样寻常会调用View#invalidate等干系接口触发UI线程的绘制上帧更新画面的操纵,详细流程后文会继续详细分析。以上过程如下Systrace截图所示:
2 应用UI线程消息循环机制
App应用启动时,在Fork创建历程后会通过反射创建代表应用主线程的ActivityThread对象并实行其main函数,进行UI主线程的初始化工作:
/*frameworks/base/core/java/android/app/ActivityThread.java*/public static void main(String[] args) { ... // 1.创建Looper、MessageQueue Looper.prepareMainLooper(); ... // 2.启动loop消息循环,开始预备吸收消息 Looper.loop(); ...}// 3.创建主线程Handler对象final H mH = new H();class H extends Handler { ...}/*frameworks/base/core/java/android/os/Looper.java*/public static void prepareMainLooper() { // 预备主线程的Looper prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // 创建主线程的Looper对象,并通过ThreadLocal机制实现与主线程的一对一绑定 sThreadLocal.set(new Looper(quitAllowed));}private Looper(boolean quitAllowed) { // 创建MessageQueue消息队列 mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}主线程初始化完成后,主线程就有了完备的 Looper、MessageQueue、Handler,此时 ActivityThread 的 Handler 就可以开始处理处罚 Message,包罗 Application、Activity、ContentProvider、Service、Broadcast 等组件的生命周期函数,都会以 Message 的情势,在主线程按照次序处理处罚,这就是 App 主线程的初始化和运行原理,部门处理处罚的 Message 如下:
/*frameworks/base/core/java/android/app/ActivityThread.java*/class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage public static final int RECEIVER = 113; @UnsupportedAppUsage public static final int CREATE_SERVICE = 114; @UnsupportedAppUsage public static final int BIND_SERVICE = 121; public void handleMessage(Message msg) { switch (msg.what) { case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; ... } } ...}主线程初始化完成后,主线程就进入壅闭状态(进入epoll_wait状态,并开释CPU运行资源),等候 Message,一旦有 Message 发过来,主线程就会被唤醒,处理处罚 Message,处理处罚完成之后,如果没有其他的 Message 须要处理处罚,那么主线程就会进入休眠壅闭状态继续等候。可以说Android体系的运行是受消息机制驱动的,而整个消息机制是由上面所说的四个关键脚色相互共同实现的(Handler、Looper、MessageQueue、Message),其运行原理如下图所示:
3 Android屏幕刷新机制
3.1 双缓存+Vsync
在一个典范的显示体系中,一样寻常包罗CPU、GPU、Display三个部门:CPU负责计算帧数据,把计算好的数据交给GPU,GPU会对图形数据进行渲染,渲染好后放到buffer(图像缓冲区)里存起来,然后Display(屏幕或显示器)负责把Buffer里的数据出现到屏幕上。屏幕上显示的内容,是从Buffer图像帧缓冲区中读取的,大致读取过程为:从Buffer的起始地点开始,从上往下,从左往右扫描整个Buffer,将内容映射到显示屏上。如下图所示:
当然,屏幕上显示的内容须要不绝的更新,如果在同一个Buffer进行读取和写入操纵,将会导致屏幕显示多帧内容而出现显示庞杂。所以硬件层除了提供一个Buffer用于屏幕显示,还会提供了一个Buffer用于背景的CPU/GPU图形绘制与合成,也就是我们常说的双缓冲:让绘制和显示器拥有各自的Buffer:CPU/GPU 始终将完成的一帧图像数据写入到 后缓存区(Back Buffer),而显示器利用前缓存区( Front Buffer),当屏幕刷新时,Front Buffer 并不会发生变革,当Back Buffer预备停当后,它们才进行互换。如下图所示:
抱负情况下假设前一帧显示完成,后一帧数据就预备好了,屏幕开始读取下一帧内容进行显示,也就是开始读取上图中的后缓冲区的内容:
此时,前后缓冲区进行一次脚色互换,之前的后缓冲区变为前缓冲区,进行图形的显示,之前的前缓冲区则变为后缓冲区,进行图形的绘制合成。然而,抱负很丰满,实际很骨感,上面假设“当前一帧显示完毕,后一帧预备好了”的情况,在实际中这两个事故并非同时完成。那么,屏幕读取缓冲区的速率和体系绘制合成帧的速率之间有什么关系呢,带着这个迷惑我们看看下面两个根本概念:
- 屏幕刷新率(Screen Refresh Rate): 屏幕刷新率是一个硬件的概念,单元是Hz(赫兹),是说屏幕这个硬件刷新画面的频率:举例来说,60Hz 刷新率意思是:这个屏幕在 1 秒内,会刷新显示内容60 次;那么对应的,90Hz 是说在 1 秒内刷新显示内容 90 次。
- 帧率(Frame Rate): 与屏幕刷新率对应的,帧率是一个软件的概念,单元是FPS(Frame Per Second ),体现 CPU/GPU 在一秒内绘制合成产生的帧数,意思是每秒产生画面的个数,FPS 的值是由软件体系决定的。举例来说,60FPS 指的是每秒产生 60 个画面;90FPS 指的是每秒产生 90 个画面。
我们用以下两个假设来分析两者的关系:
- 屏幕刷新率比体系帧率快:
此时,在前缓冲区内容全部映射到屏幕上之后,后缓冲区尚未预备好下一帧,屏幕将无法读取下一帧,所以只能继续显示当前一帧的图形,造成一帧显示多次,也就是卡顿。
- 体系帧率比屏幕刷新率快:
此时,屏幕未完全把前缓冲区的一帧映射到屏幕,而体系已经在后缓冲区预备好了下一帧,并要求读取下一帧到屏幕,将会导致屏幕上半部门是上一帧的图形,而下半部门是下一帧的图形,造成屏幕上显示多帧,也就是屏幕扯破现象,如下图所示:
所以上面两种情况,都会导致标题,根本缘故原由就是两个缓冲区的操纵速率不同等。办理办法就是:让屏幕控制前后缓冲区的切换机遇,让体系帧速率共同屏幕刷新率的节奏。那么屏幕是怎样控制这个节奏的呢?
答案就是垂直同步(VSync):当屏幕从缓冲区扫描完一帧到屏幕上之后,开始扫描下一帧之前,中心会有一个时间间隙,称为Vetrical Blanking Interval (VBI),这个时间点实在就是进行前后缓存区互换的最佳机遇,此时屏幕并没有在刷新,也就克制了屏幕扯破现象的产生,所以在此时发出的一个同步Vsync信号,该信号用来切换前缓冲区和后缓冲区(本质就是内存地点的互换,刹时即可完成),即可到达最佳结果。
通过上面的分析可以看出:屏幕的显示节奏是由屏幕刷新率的硬件参数决定且固定的,软件操纵体系须要共同屏幕的显示,在固定的时间内预备好下一帧,以供屏幕进行显示,两者通过VSync信号来实现同步(VSync这个概念并不是Google首创的,它在从前的PC机范畴就已经出现了)。
3.2 Drawing with Vsync
在Android 4.1之前,屏幕刷新也依照上面先容的 双缓存+VSync 机制,整个流程与架构借用2012年Google I/O大会上展示的一张图如下所示:
上图中:
一、纵轴体现Buffer的利用者,由如下三个脚色构成:
- CPU : 代表利用CPU对界面View的Measure尺寸测量、Layout位置结构、Draw绘制并终极天生纹理的操纵;
- GPU:代表利用OpenGl库指令操纵GPU硬件对CPU天生的纹理数据进行渲染和栅格化以及合成等操纵;
- Display:代表底层的显示屏幕;
二、横轴体现时间,每个长方形体现Buffer的利用,长方形的宽度代表利用时长,VSync代表垂直同步信号。
我们以时间为次序来看看这种筹划存在的潜伏缺陷:
- Display上显示第0帧数据,此时CPU和GPU正在处理处罚预备第1帧的画面,且在Display显示下一帧前完成;
- 因为CPU和GPU的处理处罚实时,Display在第0帧显示完成后,也就是第1个VSync后,缓存进行互换,然后正常显示第1帧;
- 接着第2帧开始处理处罚,但是CPU并没有立即开始预备第2帧的数据,而是直到第2个VSync快来前才开始处理处罚的;
- 第2个VSync来时,由于第2帧数据还没有预备停当,缓存没有互换,屏幕显示的照旧第1帧画面,即产生了丢帧卡顿标题;
- 当第2帧数据预备完成后,它并不能立马被显示,而是要等到下一个VSync 带来后,进行前后缓存互换才华显示到屏幕上。
出现此掉帧卡顿标题标根本缘故原由是:上层的CPU和GPU并不知道Vsync信号的到来,所以在底层屏幕的Vsync信号发出后并没有实时收到并开始下一帧画面的操纵处理处罚。根据前面的分析我们知道:双缓存的互换是在Vsyn信号到来时进行,互换后屏幕会读取Front Buffer内的新数据更新显示到屏幕上,而此时的Back Buffer 就可以供GPU预备下一帧数据了。如果 Vsyn到来时 CPU/GPU就开始操纵的话,是有完备的Vsync周期时长来处理处罚一帧数据,以克制卡顿的出现。那怎样让 CPU/GPU的处理处罚在 Vsyn信号到来时就开始进行呢?
为了优化体系显示性能,Google在Android 4.1体系中对Android Display体系进行了重构,引入了Project Butter(黄油筹划),此中很重要的一点修改就是实现了:在体系收到VSync信号后,上层CPU和GPU立即开始进行下一帧画面数据的处理处罚,完成后实时将数据写入到Buffer中,Google称之为Drawing with Vsync。如下图所示:
3.3 Choreographer
上一节中讲到的,为了优化显示体系性能,Google在Android 4.1体系中对Android Display体系进行了重构,引入了Project Butter(黄油筹划),此中很重要的一点修改就是实现了:在体系收到VSync信号后,上层CPU和GPU立即开始进行下一帧画面数据的处理处罚,完成后实时将数据写入到Buffer中。为了实现这个结果,控制上层CPU和GPU在收到Vsync信号后立即开始一帧数据的处理处罚,谷歌为此专门筹划了一个名为Choreographer(中文翻译为“编舞者”)的类,来控制上层绘制的节奏。
Choreographer 的引入,重要是为了共同体系Vsync垂直同步机制,给上层 App 的渲染提供一个稳固的 Message 处理处罚的机遇,也就是 Vsync 到来的时间 ,体系通过对 Vsync 信号周期的调解,来控制每一帧绘制操纵的机遇。Choreographer 饰演 Android 渲染链路中承上启下的脚色:
- 承上:负责吸收和处理处罚 App 的各种更新消息和回调,等到 Vsync 到来的时间同一处理处罚。好比会集处理处罚 Input(重要是 Input 事故的处理处罚) 、Animation(动画干系)、Traversal(包罗 measure、layout、draw 等操纵) ,判定卡顿掉帧情况,记载 CallBack 耗时等;
- 启下:负责哀求和吸收 Vsync 信号。吸收 Vsync 信号到来的事故后回调(通过 FrameDisplayEventReceiver.onVsync ),并哀求 Vsync(FrameDisplayEventReceiver.scheduleVsync) 。
一样寻常应用App有界面UI的变革时,终极都会调用走到ViewRootImpl#scheduleTraversals()方法中,该方法中会往Choreographer中放入一个CALLBACK_TRAVERSAL范例的绘制任务,如下代码所示:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/@UnsupportedAppUsagevoid scheduleTraversals() { if (!mTraversalScheduled) { ... // 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制范例的待实行消息,用于触发后续UI线程真正实现绘制动作 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... }}Choreographer在收到的绘制任务后,其内部的工作流程如下图所示:
从以上流程图可以看出上层一样寻常App应用UI中View的绘制流程(包罗SurfaceView的游戏应用的绘制流程会有一些差异,篇幅有限此处不再睁开分析):
- View#invalidate触发更新视图哀求,此动作会调用ViewRootImpl#scheduleTraversals函数;
- ViewRootImpl#scheduleTraversals中会向Choreographer中postCallback放入一个CALLBACK_TRAVERSAL范例绘制待实行任务;
- Choreographer通过DisplayEventReceiver向体系SurfaceFlinger注册下一个VSync信号;
- 当底层产生下一个VSync消息时,将该信号发送给DisplayEventReceiver,末了通报给Choreographer;
- Choreographer收到VSync信号之后,向主线程MessageQueue发送了一个异步消息;
- 末了,异步消息的实行者是跑在主线程中的ViewRootImpl#doTraversal,也就是真正开始绘制一帧的操纵(包罗measure、layout、draw三个过程);
至此,底层的VSync控制上层绘制的逻辑就表明完了。
4 UI 线程绘制流程
在前几节中分析了应用UI线程的消息循环机制和Android屏幕刷新机制之后,我们接着1末节中关于Input触控事故的处理处罚流程继续往下分析。在1末节的分析中我们相识到:用户手指在应用界面上下滑动时,应用的UI线程中会收到system_server体系历程发送来的一系列Input事故(包罗一个ACTION_DOWN、多个ACTION_MOVE和一个ACTION_UP事故),应用界面结构中的干系View控件在收到多个ACTION_MOVE触控事故后,判定为用户手指的滑动活动后,一样寻常会调用View#invalidate等接口触发UI线程的绘制上帧更新画面的操纵。
在开始分析之前,我们先来看看Android体系的GUI显示体系在APP应用历程侧的焦点架构,其整体架构如下图所示:
- Window是一个抽象类,通过控制DecorView提供了一些标准的UI方案,好比配景、标题、虚拟按键等,而PhoneWindow是Window的唯一实现类,在Activity创建后的attach流程中创建,应用启动显示的内容装载到其内部的mDecor(DecorView);
- DecorView是整个界面结构View控件树的根节点,通过它可以遍历访问到整个View控件树上的恣意节点;
- WindowManager是一个接口,继续自ViewManager接口,提供了View的根本操纵方法;WindowManagerImp实现了WindowManager接口,内部通过组合方式持有WindowManagerGlobal,用来操纵View;WindowManagerGlobal是一个全局单例,内部通过ViewRootImpl将View添加至窗口中;
- ViewRootImpl是全部View的Parent,用来总体管理View的绘制以及与体系WMS窗口管理服务的IPC交互从而实现窗口的开辟;ViewRootImpl是应用历程运转的发动机,可以看到ViewRootImpl内部包罗mView(就是DecorView)、mSurface、Choregrapher:mView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用哀求vsync信号,吸收信号后开始绘制流程。
我们从ViewRootImpl的invalidate流程继续往下分析:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/@UnsupportedAppUsagevoid invalidate() { mDirty.set(0,0,mWidth,mHeight); if (!mWillDrawSoon) { // 调用scheduleTraversals函数触发绘制操纵 scheduleTraversals(); }}@UnsupportedAppUsagevoid scheduleTraversals() { if (!mTraversalScheduled) { ... // 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制范例的待实行消息,用于触发后续UI线程真正实现绘制动作 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... }}从以上分析可以看出,应用UI线程的绘制终极是通过往Choreographer中放入一个CALLBACK_TRAVERSAL范例的绘制任务而触发,下面的流程就和3.3.3末节中的分析的同等,Choreographer会先向体系申请Vsync信号,待Vsync信号到来后,向应用主线程MessageQueue发送一个异步消息,触发在主线程中实行ViewRootImpl#doTraversal绘制任务动作。我们接着看看ViewRootImpl的doTraversal函数实行绘制流程的简化代码流程:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 调用removeSyncBarrier实时移除主线程MessageQueue中的Barrier同步栏删,以克制主线程发生“假死” mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); ... // 实行详细的绘制任务 performTraversals(); ... }}private void performTraversals() { ... // 1.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的measure测量操纵 windowSizeMayChange |= measureHierarchy(...); ... if (mFirst...) { // 2.第一次实行traversals绘制任务时,Binder调用访问体系窗口管理服务WMS的relayoutWindow接口,实现WMS计算应用窗口尺寸并向体系surfaceflinger正式申请Surface“画布”操纵 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); } ... // 3.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的layout测量操纵 performLayout(lp, mWidth, mHeight); ... // 4.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的draw测量操纵 performDraw(); ...}private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { ... // 原生标识View树的measure测量过程的trace tag Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { // 从mView指向的View控件树的根节点DecorView出发,遍历访问整个View树,并完成整个结构View树的测量工作 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}private void performDraw() { ... boolean canUseAsync = draw(fullRedrawNeeded); ...}private boolean draw(boolean fullRedrawNeeded) { ... if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { ... // 如果开启并支持硬件绘制加速,则走硬件绘制的流程(从Android 4.+开始,默认情况下都是支持跟开启了硬件加速的) mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { // 否则走drawSoftware软件绘制的流程 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } }}从上面的代码流程可以看出,ViewRootImpl中负责的整个应用界面绘制的重要流程如下:
- 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的measure测量操纵,由于篇幅所限,本文就不睁开分析这块的详细流程;
- 界面第一次实行绘制任务时,会通过Binder IPC访问体系窗口管理服务WMS的relayout接口,实现窗口尺寸的计算并向体系申请用于当地绘制渲染的Surface“画布”的操纵(详细由SurfaceFlinger负责创建应用界面对应的Layer对象,并通过内存共享的方式通过Binder将地点引用透过WMS回传给应用历程这边);
- 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的layout结构操纵;
- 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的draw绘制操纵,如果开启并支持硬件绘制加速(从Android 4.X开始谷歌已经默认开启硬件加速),则走GPU硬件绘制的流程,否则走CPU软件绘制的流程;
以上绘制过程从systrace上看如下图所示:
5 RenderThread 线程渲染流程
停止到如今,在ViewRootImpl中完成了对界面的measure、layout和draw等绘制流程后,用户依然照旧看不到屏幕上显示的应用界面内容,因为整个Android体系的显示流程除了前面讲到的UI线程的绘制外,界面还须要颠末RenderThread线程的渲染处理处罚,渲染完成后,还须要通过Binder调用“上帧”交给surfaceflinger历程中进行合成后送显才华终极显示到屏幕上。本末节中,我们将接上一节中ViewRootImpl中末了draw的流程继续往下分析开启硬件加速情况下,RenderThread渲染线程的工作流程。由于如今Android 4.X之后体系默认界面是开启硬件加速的,所以本文我们重点分析硬件加速条件下的界面渲染流程,我们先分析一下简化的代码流程:
/*frameworks/base/core/java/android/view/ViewRootImpl.java*/private boolean draw(boolean fullRedrawNeeded) { ... if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { ... // 硬件加速条件下的界面渲染流程 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { ... }}/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) { ... // 1.从DecorView根节点出发,递归遍历View控件树,记载每个View节点的绘制操纵命令,完成绘制操纵命令树的构建 updateRootDisplayList(view, callbacks); ... // 2.JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL实行渲染任务; int syncResult = syncAndDrawFrame(choreographer.mFrameInfo); ...}从上面的代码可以看出,硬件加速绘制重要包罗两个阶段:
- 从DecorView根节点出发,递归遍历View控件树,记载每个View节点的drawOp绘制操纵命令,完成绘制操纵命令树的构建;
- JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL实行渲染任务;
5.1 构建绘制命令树
我们先来看看第一阶段构建绘制命令树的代码简化流程:
/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/private void updateRootDisplayList(View view, DrawCallbacks callbacks) { // 原生标志构建View绘制操纵命令树过程的systrace tag Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()"); // 递归子View的updateDisplayListIfDirty实现构建DisplayListOp updateViewTreeDisplayList(view); ... if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) { // 获取根View的SkiaRecordingCanvas RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight); try { ... // 利用canvas缓存DisplayListOp绘制命令 canvas.drawRenderNode(view.updateDisplayListIfDirty()); ... } finally { // 将全部DisplayListOp绘制命令添补到RootRenderNode中 mRootNode.endRecording(); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW);}private void updateViewTreeDisplayList(View view) { ... // 从DecorView根节点出发,开始递归调用每个View树节点的updateDisplayListIfDirty函数 view.updateDisplayListIfDirty(); ...}/*frameworks/base/core/java/android/view/View.java*/public RenderNode updateDisplayListIfDirty() { ... // 1.利用`View`对象构造时创建的`RenderNode`获取一个`SkiaRecordingCanvas`“画布”; final RecordingCanvas canvas = renderNode.beginRecording(width, height); try { ... if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // 如果仅仅是ViewGroup,而且自身不消绘制,直接递归子View dispatchDraw(canvas); ... } else { // 2.利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操纵时,创建对应的DisplayListOp绘制命令,并缓存记载到其内部的SkiaDisplayList持有的DisplayListData中; draw(canvas); } } finally { // 3.将包罗有`DisplayListOp`绘制命令缓存的`SkiaDisplayList`对象设置添补到`RenderNode`中; renderNode.endRecording(); ... } ...}public void draw(Canvas canvas) { ... // draw the content(View自己实现的onDraw绘制,由应用开辟者自己实现) onDraw(canvas); ... // draw the children dispatchDraw(canvas); ...}/*frameworks/base/graphics/java/android/graphics/RenderNode.java*/public void endRecording() { ... // 从SkiaRecordingCanvas中获取SkiaDisplayList对象 long displayList = canvas.finishRecording(); // 将SkiaDisplayList对象添补到RenderNode中 nSetDisplayList(mNativeRenderNode, displayList); canvas.recycle();}从以上代码可以看出,构建绘制命令树的过程是从View控件树的根节点DecorView触发,递归调用每个子View节点的updateDisplayListIfDirty函数,终极完成绘制树的创建,简述流程如下:
- 利用View对象构造时创建的RenderNode获取一个SkiaRecordingCanvas“画布”;
- 利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操纵时,创建对应的DisplayListOp绘制命令,并缓存记载到其内部的SkiaDisplayList持有的DisplayListData中;
- 将包罗有DisplayListOp绘制命令缓存的SkiaDisplayList对象设置添补到RenderNode中;
- 末了将根View的缓存DisplayListOp设置到RootRenderNode中,完成构建。
以上整个构建绘制命令树的过程可以用如下游程图体现:
硬件加速下的整个界面的View树的结构如下图所示:
末了从Systrace上看这个过程如下图所示:
5.2 实行渲染绘制任务
颠末上一末节中的分析,应用在UI线程中从根节点DecorView出发,递归遍历每个子View节点,搜集其drawXXX绘制动作并转换成DisplayListOp命令,将其记载到DisplayListData并添补到RenderNode中,终极完成整个View绘制命令树的构建。今后UI线程的绘制任务就完成了。下一步UI线程将唤醒RenderThread渲染线程,触发其利用OpenGL实行界面的渲染任务,本末节中我们将重点分析这个流程。我们照旧先看看这块代码的简化流程:
/*frameworks/base/graphics/java/android/graphics/HardwareRenderer.java*/public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) { // JNI调用native层的干系函数 return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);}/*frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp*/static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) { ... RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo()); return proxy->syncAndDrawFrame();}/*frameworks/base/libs/hwui/renderthread/RenderProxy.cpp*/int RenderProxy::syncAndDrawFrame() { // 唤醒RenderThread渲染线程,实行DrawFrame绘制任务 return mDrawFrameTask.drawFrame();}/*frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp*/int DrawFrameTask::drawFrame() { ... postAndWait(); ...}void DrawFrameTask::postAndWait() { AutoMutex _lock(mLock); // 向RenderThread渲染线程的MessageQueue消息队列放入一个待实行任务,以将其唤醒实行run函数 mRenderThread->queue().post([this]() { run(); }); // UI线程临时进入wait等候状态 mSignal.wait(mLock);}void DrawFrameTask::run() { // 原生标识一帧渲染绘制任务的systrace tag ATRACE_NAME("DrawFrame"); ... { TreeInfo info(TreeInfo::MODE_FULL, *mContext); //1.将UI线程构建的DisplayListOp绘制命令树同步到RenderThread渲染线程 canUnblockUiThread = syncFrameState(info); ... } ... // 同步完成后则可以唤醒UI线程 if (canUnblockUiThread) { unblockUiThread(); } ... if (CC_LIKELY(canDrawThisFrame)) { // 2.实行draw渲染绘制动作 context->draw(); } else { ... } ...}bool DrawFrameTask::syncFrameState(TreeInfo& info) { ATRACE_CALL(); ... // 调用CanvasContext的prepareTree函数实现绘制命令树同步的流程 mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode); ...}/*frameworks/base/libs/hwui/renderthread/CanvasContext.cpp*/void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target) { ... for (const sp<RenderNode>& node : mRenderNodes) { ... // 递归调用各个子View对应的RenderNode实行prepareTree动作 node->prepareTree(info); ... } ...}/*frameworks/base/libs/hwui/RenderNode.cpp*/void RenderNode::prepareTree(TreeInfo& info) { ATRACE_CALL(); ... prepareTreeImpl(observer, info, false); ...}void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) { ... if (info.mode == TreeInfo::MODE_FULL) { // 同步绘制命令树 pushStagingDisplayListChanges(observer, info); } if (mDisplayList) { // 遍历调用各个子View对应的RenderNode的prepareTreeImpl bool isDirty = mDisplayList->prepareListAndChildren( observer, info, childFunctorsNeedLayer, [](RenderNode* child, TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) { child->prepareTreeImpl(observer, info, functorsNeedLayer); }); ... } ...}void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) { ... syncDisplayList(observer, &info); ...}void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { ... // 完成赋值同步DisplayList对象 mDisplayList = mStagingDisplayList; mStagingDisplayList = nullptr; ...}void CanvasContext::draw() { ... // 1.调用OpenGL库利用GPU,按照构建好的绘制命令完成界面的渲染 bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler())); ... // 2.将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示 bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap); ...}从以上代码可以看出:UI线程利用RenderProxy向RenderThread线程发送一个DrawFrameTask任务哀求,RenderThread被唤醒,开始渲染,大致流程如下:
- syncFrameState中遍历View树上每一个RenderNode,实行prepareTreeImpl函数,实现同步绘制命令树的操纵;
- 调用OpenGL库API利用GPU硬件,按照构建好的绘制命令完成界面的渲染(详细过程,由于本文篇幅所限,暂不睁开分析);
- 将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示;
整个过程可以用如下游程图体现:
从Systrace上这个过程如下图所示:
6 SurfaceFlinger图形合成
SurfaceFlinger合成显示部门属于Android体系GUI中图形显示的内容,简单的说SurfaceFlinger作为体系中独立运行的一个Native历程,借用Android官网的形貌,其职责就是负责担当来自多个泉源的数据缓冲区,对它们进行合成,然后发送到显示装备。如下图所示:
从上图可以看出,实在SurfaceFlinger在Android体系的整个图形显示体系中是起到一个承上启下的作用:
- 对上:通过Surface与差异的应用历程建立接洽,吸收它们写入Surface中的绘制缓冲数据,对它们进行同一合成。
- 对下:通过屏幕的后缓存区与屏幕建立接洽,发送合成好的数据到屏幕显示装备。
图形的通报是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层利用,怎样管理这些Buffer呢?答案就是BufferQueue ,下面我们来看看BufferQueue的工作原理:
6.1 BufferQueue机制
借用一张经典的图来形貌BufferQueue的工作原理:
BufferQueue是一个典范的生产者-消耗者模子中的数据结构。在Android应用的渲染流程中,应用饰演的就是“生产者”的脚色,而SurfaceFlinger饰演的则是“消耗者”的脚色,其共同工作的流程如下:
- 应用历程中在开始界面的绘制渲染之前,须要通过Binder调用dequeueBuffer接口从SurfaceFlinger历程中管理的BufferQueue 中申请一张处于free状态的可用Buffer,假云云时没有可用Buffer则壅闭等候;
- 应用历程中拿到这张可用的Buffer之后,选择利用CPU软件绘制渲染或GPU硬件加速绘制渲染,渲染完成后再通过Binder调用queueBuffer接口将缓存数据返回给应用历程对应的BufferQueue(如果是 GPU 渲染的话,这里尚有个 GPU处理处罚的过程,所以这个 Buffer 不会立即可用,须要等 GPU 渲染完成的Fence信号),并申请sf范例的Vsync以便唤醒“消耗者”SurfaceFlinger进行消耗;
- SurfaceFlinger 在收到 Vsync 信号之后,开始预备合成,利用 acquireBuffer获取应用对应的 BufferQueue 中的 Buffer 并进行合成操纵;
- 合成竣事后,SurfaceFlinger 将通过调用 releaseBuffer将 Buffer 置为可用的free状态,返回到应用对应的 BufferQueue中。
6.2 Vsync同步机制
在之前3.3末节关于Android体系屏幕刷新机制中我们分析了Vsync机制的来龙去脉。实在Android体系中的Vsync信号的产生与管理都是由SurfaceFlinger模块同一负责的,Vysnc信号一样寻常分为两种范例:
- app范例的Vsync:app范例的Vysnc信号由上层应用中的Choreographer根据绘制需求进行注册和吸收,用于控制应用UI绘制上帧的生产节奏。根据3.4小结中的分析:应用在UI线程中调用invalidate刷新界面绘制时,须要先透过Choreographer向体系申请注册app范例的Vsync信号,待Vsync信号到来后,才华往主线程的消息队列放入待绘制任务进行真正UI的绘制动作;
- sf范例的Vsync:sf范例的Vsync是用于控制SurfaceFlinger的合成消耗节奏。应用完成界面的绘制渲染后,通过Binder调用queueBuffer接口将缓存数据返还给应用对应的BufferQueue时,会申请sf范例的Vsync,待SurfaceFlinger 在其UI线程中收到 Vsync 信号之后,便开始进行界面的合成操纵。
Vsync信号的天生是参考屏幕硬件的刷新周期的,其架构如下图所示:
6.3 帧数据的提交消耗过程
我们接着3.5.2末节中的分析,应用历程的RenderThread渲染线程在实行完一帧画面的渲染操纵的末了,会通过Binder调用queueBuffer接口将一帧数据提交给SurfaceFlinger历程进行消耗合成显示。我们联合干系简化的源码流程(这里基于Android 11源代码分析)来看看SurfaceFlinger中是如那边理应用的哀求的。
/*frameworks/native/libs/gui/BufferQueueProducer.cpp*/status_t BufferQueueProducer::queueBuffer(int slot, const QueueBufferInput &input, QueueBufferOutput *output) { ATRACE_CALL(); ...... if (frameAvailableListener != nullptr) { frameAvailableListener->onFrameAvailable(item); } ......}上面的frameAvailableListener是BufferQueueLayer:
/*frameworks/native/services/surfaceflinger/BufferQueueLayer.cpp*/void BufferQueueLayer:nFrameAvailable(const BufferItem& item) { ...... mFlinger->signalLayerUpdate();//这里触发申请一下个Vsync-sf信号 ......}/*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/void SurfaceFlinger::signalLayerUpdate() { ...... mEventQueue->invalidate(); ......}/*frameworks/native/services/surfaceflinger/Scheduler/MessageQueue.cpp*/void MessageQueue::invalidate() { ...... mEvents->requestNextVsync();// 申请一下个Vsync-sf信号 ......}以上过程从Systrace上看如下图所示:
由上面分析可知,只要有layer上帧,那么就会申请下一次的Vsync-sf信号, 当Vsync-sf信号来时会调用onMessageReceived函数来处理处罚帧数据:
/*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/void SurfaceFlinger:nMessageInvalidate(nsecs_t expectedVSyncTime) { ATRACE_CALL(); ...... refreshNeeded |= handleMessageInvalidate(); ...... signalRefresh();//再次向消息队列发送一个消息,消息到达时会调用onMessageRefresh ......}bool SurfaceFlinger::handleMessageInvalidate() { ATRACE_CALL(); bool refreshNeeded = handlePageFlip(); ......}在handleMessageInvalidate里一个比较重要的函数是handlePageFlip():
/*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/bool SurfaceFlinger::handlePageFlip(){ ATRACE_CALL(); ...... mDrawingState.traverse([&](Layer* layer) { if (layer->hasReadyFrame()) { frameQueued = true; if (layer->shouldPresentNow(expectedPresentTime)) { mLayersWithQueuedFrames.push_back(layer); } ....... } ...... }); ...... for (auto& layer : mLayersWithQueuedFrames) { if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) { mLayersPendingRefresh.push_back(layer); } ....... } ......}这里可以看出来,handlePageFlip里一个重要的工作是检查全部的Layer是否有新Buffer提交,如果有则调用其latchBuffer来处理处罚:
/*frameworks/native/services/surfaceflinger/BufferLayer.cpp*/bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime, nsecs_t expectedPresentTime) { ATRACE_CALL(); ...... status_t err = updateTexImage(recomputeVisibleRegions, latchTime, expectedPresentTime); ......}/*frameworks/native/services/surfaceflinger/BufferQueuedLayer.cpp*/status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime, nsecs_t expectedPresentTime) { ...... status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &mAutoRefresh, &queuedBuffer, maxFrameNumberToAcquire); ......}/*frameworks/native/services/surfaceflinger/BufferLayerConsumer.cpp*/status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime, bool* autoRefresh, bool* queuedBuffer, uint64_t maxFrameNumber) { ATRACE_CALL(); ...... status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber); ......}status_t ConsumerBase::acquireBufferLocked(BufferItem *item, nsecs_t presentWhen, uint64_t maxFrameNumber) { ...... status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber); ......}这里调用到了BufferLayerConsumer的基类ConsumerBase里:
/*frameworks/native/libs/gui/ConsumerBase.cpp*/status_t ConsumerBase::acquireBufferLocked(BufferItem *item, nsecs_t presentWhen, uint64_t maxFrameNumber) { ...... status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber); ......}到这里onMessageInvalidate中的重要工作竣事,在这个函数的处理处罚中:SurfaceFlinger重要是检查每个Layer是否有新提交的Buffer, 如果有则调用latchBuffer将每个BufferQueue中的Slot 通过acquireBuffer拿走。此过程从Systrace上看如下图有所示:
之后acquireBuffer拿走的Buffer(Slot对应的状态是ACQUIRED状态)会被交由HWC Service处理处罚,这部门是在onMessageRefresh中处理处罚的:
/*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/void SurfaceFlinger:nMessageRefresh() { ATRACE_CALL(); ...... mCompositionEngine->present(refreshArgs); ......}/*frameworks/native/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp*/void CompositionEngine::present(CompositionRefreshArgs& args) { ATRACE_CALL(); ...... for (const auto& output : args.outputs) { output->present(args); } ......}/*frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp*/void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) { ATRACE_CALL(); ...... updateAndWriteCompositionState(refreshArgs);//告知HWC service有哪些layer要加入所成 ...... beginFrame(); prepareFrame(); ...... finishFrame(refreshArgs); postFramebuffer();//这里会调用到HWC service的接口去present display合成画面}void Output::postFramebuffer() { ...... auto frame = presentAndGetFrameFences(); ......}/*frameworks/native/services/surfaceflinger/displayhardware/HWComposer.cpp*/status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) { ATRACE_CALL(); ...... auto error = hwcDisplay->present(&displayData.lastPresentFence);//送去HWC service合成 ...... std::unordered_map<HWC2:ayer*, sp<Fence>> releaseFences; error = hwcDisplay->getReleaseFences(&releaseFences); RETURN_IF_HWC_ERROR_FOR("getReleaseFences", error, displayId, UNKNOWN_ERROR); displayData.releaseFences = std::move(releaseFences);//获取releaseFence, 以便关照到各个Slot, buffer被release后会通过dequeueBuffer给到应用,应用在画图前会等候releaseFence ......}以上过程从systrace上看如下图所示:
末了总结一下应用调用queueBuffer将一帧Buffer数据提到SurfaceFlinger后SurfaceFlinger的重要处理处罚流程,:
- 起首Binder线程会通过BufferQueue机制把应用上帧的Slot状态改为QUEUED, 然后把这个Slot放入mQueue队列, 然后通过onFrameAvailable回调关照到BufferQueueLayer, 在处理处罚函数里会哀求下一次的Vsync-sf信号;
- 在Vsync-sf信号到来后,SurfaceFlinger主线程要实行两次onMessageReceived, 第一次要检查全部的Layer看是否有上帧, 如果有Layer上帧就调用它的latchBuffer把它的Buffer acquireBuffer取走;并发送一个消息到主消息队列,让主线程再次走进onMessageReceived,;
- 第二次走进来时,重要实行present方法,在这些方法里会和HWC service沟通,调用它的跨历程接口关照它去做图层的合成后送显示器显示。
后续HWC service的合成以及屏幕的详细显示原来由于篇幅有限就不睁开阐明,感兴趣的读者可以参考系列文章https://www.jianshu.com/p/df46e4b39428。
7 流程总结与卡顿界说
7.1 应用绘制上帧流程总结
在本节中我们以用户手指上下滑动应用界面的操纵场景为例,联合体系源码和Systrace工具,按照实行次序分析了Android应用绘制上帧显示的体系运行机制与总体流程,我们以一张图形貌如下:
末了总结整个流程大致如下:
- 用户手指触摸屏幕后,屏幕驱动产生Input触控事故;框架system_server历程中的EventHub通过epoll机制监听到驱动产生的Input触控事故上报,由InputReader读取到Input事故后,唤醒InputDispatcher找到当前触控焦点应用窗口,并通过事先建立的socket通道发送Input事故到对应的应用历程;
- 应用历程收到Input触控事故后UI线程被唤醒进行事故的分发,干系View控件中根据多个ACTION_MOVE范例的Input事故判定为用户手指滑动活动后,通过Choreographer向体系注册申请app范例的Vsync信号,并等候Vsync信号到来后触发绘制操纵;
- app范例的Vsync信号到来后,唤醒应用UI线程并向其消息队列中放入一个待实行的绘制任务,在UI线程中先后遍历实行View控件树的测量、结构和绘制(硬件加速默认开启的状态下会遍历并记载每个View的draw操纵天生对应的绘制命令树)操纵;
- View控件树的绘制任务实行完成后会唤醒应用的RenderThread渲染线程实行界面渲染任务;整个渲染任务中会先同步UI线程中构建好的绘制命令树,然后通过dequeueBuffer申请一张处于free状态的可用Buffer,然后调用SkiaOpenGLPipeline渲染管道中利用GPU进行渲染操纵,渲染完成后swapBuffer触发queueBuffer动作进行上帧;
- 应用渲染线程末了的queueBuffer上帧动作,会唤醒对端SurfaceFlinger历程中的Binder处理处罚线程,此中将对应用BufferQuque中的Buffer标志为Queued状态,然后注册申请sf范例的Vsync信号;
- 待sf范例的Vsync信号到来后会唤醒SurfaceFlinger的主线程实行一帧的合成任务,此中会先通过handlePageFlip操纵遍历全部的应用Layer找到有上帧操纵的处于Queued状态的Buffer进行AcquireBuffer获取标志锁定,然后实行persent动作调用唤醒HWC service历程的工作线程实行详细的图层的合成送显操纵;
- HWC service中终极会收到SurfaceFlinger的哀求后,进行图层合成操纵,终极通过调用libDrm库干系接口Commit提交Buffer数据到Kernel内核中的屏幕驱动,并终极送到屏幕硬件上显示。
7.2 卡顿的界说
根据本节中我们对Android应用上帧显示的原理分析,我们开端可以判定:如果在一个Vsync周期内(60HZ的屏幕上就是16.6ms),按照整个上帧显示的实行的次序来看,应用UI线程的绘制、RenderThread线程的渲染、SurfaceFlinger/HWC的图层合成以及终极屏幕上的显示这些动作没有全部都实行完成的话,屏幕上就会显示上一帧画面的内容,也就是掉帧,而人的肉眼就大概会感觉到画面卡顿(由于 Triple Buffer 的存在,这里也有大概不掉帧)。
这里借用高爷的一段经典形貌从三个方面界说卡顿:
- 从现象上来说,在 App 一连的动画播放大概手指滑动列表时(关键是一连),如果一连 2 帧大概 2 帧以上,应用的画面都没有变革,那么我们以为这里发生了卡顿;
- 从 SurfaceFlinger 的角度来说,在 App 一连的动画播放大概手指滑动列表时(关键是一连),如果有一个 Vsync 到来的时间 ,App 没有可以用来合成的 Buffer,那么这个 Vsync 周期 SurfaceFlinger 就不会走合成的逻辑(大概是去合成其他的 Layer),那么这一帧就会显示 App 的上一帧的画面,我们以为这里发生了卡顿;
- 从 App 的角度来看,如果渲染线程在一个 Vsync 周期内没有 queueBuffer 到 SurfaceFlinger 中 App 对应的 BufferQueue 中,那么我们以为这里发生了卡顿。
8 参考
“终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面明白 https://juejin.cn/post/6863756420380196877
Android Systrace 流通性实战 1 :相识卡顿原理 https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/#/%E4%BA%86%E8%A7%A3%E5%8D%A1%E9%A1%BF%E5%8E%9F%E7%90%86
Android图形显示体系汇总 https://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&mid=2247493101&idx=1&sn=9c4f6a09408e3f4ee6e9acdac47a28d3&chksm=97f55b59a082d24fead3bfe40d2ecb89f1d6991d0d198879f4abcbd08f8c48182676f7566c6f&mpshare=1&scene=1&srcid=0518K88i53u0Zt0JOSvdStY3&sharer_sharetime=1621300130624&sharer_shareid=2d76fc4769fc55b6ca84ec3820ba5821&version=3.1.7.3005&platform=win#rd |