当前位置:   article > 正文

卡顿掉帧问题分析之原理篇_卡顿分析

卡顿分析

努比亚技术团队原创内容,转载请务必注明出处。
当用户抱怨手机在使用过程中存在卡顿问题的时候,会严重影响用户对手机品牌的好感和应用APP的体验,从而导致用户对手机品牌的忠诚度降低或应用APP的装机留存率下降。所以无论是手机设备厂商还是应用APP开发者,又或是Android系统的维护者Google都会对界面卡顿问题非常重视,会将界面的流畅度作为核心性能体验指标进行持续的优化。说到流畅度,本质上就是要解决用户操作手机过程中的界面丢帧问题,本来一秒钟屏幕上需要更新60帧画面,但是由于种种原因,这期间屏幕上只更新了55帧画面,这就是出现丢帧,在用户主观肉眼看来就是感知卡顿。那么当出现了丢帧卡顿的问题时,我们该如何着手去分析与优化解决呢?关于这块的内容,笔者将结合多年来的工作经历与理解,分三篇系列文章来讲解:

2 应用UI线程消息循环机制

App应用启动时,在Fork创建进程后会通过反射创建代表应用主线程的ActivityThread对象并执行其main函数,进行UI主线程的初始化工作:

  1. /*frameworks/base/core/java/android/app/ActivityThread.java*/
  2. public static void main(String[] args) {
  3. ...
  4. // 1.创建Looper、MessageQueue
  5. Looper.prepareMainLooper();
  6. ...
  7. // 2.启动loop消息循环,开始准备接收消息
  8. Looper.loop();
  9. ...
  10. }
  11. // 3.创建主线程Handler对象
  12. final H mH = new H();
  13. class H extends Handler {
  14. ...
  15. }
  16. /*frameworks/base/core/java/android/os/Looper.java*/
  17. public static void prepareMainLooper() {
  18. // 准备主线程的Looper
  19. prepare(false);
  20. synchronized (Looper.class) {
  21. if (sMainLooper != null) {
  22. throw new IllegalStateException("The main Looper has already been prepared.");
  23. }
  24. sMainLooper = myLooper();
  25. }
  26. }
  27. private static void prepare(boolean quitAllowed) {
  28. if (sThreadLocal.get() != null) {
  29. throw new RuntimeException("Only one Looper may be created per thread");
  30. }
  31. // 创建主线程的Looper对象,并通过ThreadLocal机制实现与主线程的一对一绑定
  32. sThreadLocal.set(new Looper(quitAllowed));
  33. }
  34. private Looper(boolean quitAllowed) {
  35. // 创建MessageQueue消息队列
  36. mQueue = new MessageQueue(quitAllowed);
  37. mThread = Thread.currentThread();
  38. }

主线程初始化完成后,主线程就有了完整的 LooperMessageQueueHandler,此时 ActivityThreadHandler 就可以开始处理 Message,包括 ApplicationActivityContentProviderServiceBroadcast 等组件的生命周期函数,都会以 Message 的形式,在主线程按照顺序处理,这就是 App 主线程的初始化和运行原理,部分处理的 Message 如下:

  1. /*frameworks/base/core/java/android/app/ActivityThread.java*/
  2. class H extends Handler {
  3. public static final int BIND_APPLICATION = 110;
  4. @UnsupportedAppUsage
  5. public static final int RECEIVER = 113;
  6. @UnsupportedAppUsage
  7. public static final int CREATE_SERVICE = 114;
  8. @UnsupportedAppUsage
  9. public static final int BIND_SERVICE = 121;
  10. public void handleMessage(Message msg) {
  11. switch (msg.what) {
  12. case BIND_APPLICATION:
  13. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
  14. AppBindData data = (AppBindData)msg.obj;
  15. handleBindApplication(data);
  16. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  17. break;
  18. ...
  19. }
  20. }
  21. ...
  22. }

主线程初始化完成后,主线程就进入阻塞状态(进入epoll_wait状态,并释放CPU运行资源),等待 Message,一旦有 Message 发过来,主线程就会被唤醒,处理 Message,处理完成之后,如果没有其他的 Message 需要处理,那么主线程就会进入休眠阻塞状态继续等待。可以说Android系统的运行是受消息机制驱动的,而整个消息机制是由上面所说的四个关键角色相互配合实现的(HandlerLooperMessageQueueMessage),其运行原理如下图所示:

3.3 Choreographer

上一节中讲到的,为了优化显示系统性能,GoogleAndroid 4.1系统中对Android Display系统进行了重构,引入了Project Butter(黄油计划),其中很重要的一点修改就是实现了:在系统收到VSync信号后,上层CPUGPU马上开始进行下一帧画面数据的处理,完成后及时将数据写入到Buffer。为了实现这个效果,控制上层CPUGPU在收到Vsync信号后马上开始一帧数据的处理,谷歌为此专门设计了一个名为Choreographer(中文翻译为“编舞者”)的类,来控制上层绘制的节奏。

Choreographer 的引入,主要是为了配合系统Vsync垂直同步机制,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机。Choreographer 扮演 Android 渲染链路中承上启下的角色

  1. 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等;
  2. 启下:负责请求和接收 Vsync 信号。接收 Vsync 信号到来的事件后回调(通过 FrameDisplayEventReceiver.onVsync ),并请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) 。

一般应用App有界面UI的变化时,最终都会调用走到ViewRootImpl#scheduleTraversals()方法中,该方法中会往Choreographer中放入一个CALLBACK_TRAVERSAL类型的绘制任务,如下代码所示:

  1. /*frameworks/base/core/java/android/view/ViewRootImpl.java*/
  2. @UnsupportedAppUsage
  3. void scheduleTraversals() {
  4. if (!mTraversalScheduled) {
  5. ...
  6. // 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制类型的待执行消息,用于触发后续UI线程真正实现绘制动作
  7. mChoreographer.postCallback(
  8. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  9. ...
  10. }
  11. }

Choreographer在收到的绘制任务后,其内部的工作流程如下图所示:

从以上流程图可以看出上层一般App应用UIView的绘制流程(包含SurfaceView的游戏应用的绘制流程会有一些差异,篇幅有限此处不再展开分析)

  1. View#invalidate触发更新视图请求,此动作会调用ViewRootImpl#scheduleTraversals函数;
  2. ViewRootImpl#scheduleTraversals中会向ChoreographerpostCallback放入一个CALLBACK_TRAVERSAL类型绘制待执行任务;
  3. Choreographer通过DisplayEventReceiver向系统SurfaceFlinger注册下一个VSync信号;
  4. 当底层产生下一个VSync消息时,将该信号发送给DisplayEventReceiver,最后传递给Choreographer
  5. Choreographer收到VSync信号之后,向主线程MessageQueue发送了一个异步消息;
  6. 最后,异步消息的执行者是跑在主线程中的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方案,比如背景、标题、虚拟按键等,而PhoneWindowWindow的唯一实现类,在Activity创建后的attach流程中创建,应用启动显示的内容装载到其内部的mDecorDecorView);
  • DecorView是整个界面布局View控件树的根节点,通过它可以遍历访问到整个View控件树上的任意节点;
  • WindowManager是一个接口,继承自ViewManager接口,提供了View的基本操作方法;WindowManagerImp实现了WindowManager接口,内部通过组合方式持有WindowManagerGlobal,用来操作ViewWindowManagerGlobal是一个全局单例,内部通过ViewRootImplView添加至窗口中
  • ViewRootImpl是所有ViewParent,用来总体管理View的绘制以及与系统WMS窗口管理服务的IPC交互从而实现窗口的开辟ViewRootImpl是应用进程运转的发动机,可以看到ViewRootImpl内部包含mView(就是DecorView)、mSurfaceChoregraphermView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用请求vsync信号,接收信号后开始绘制流程。

我们从ViewRootImplinvalidate流程继续往下分析:

  1. /*frameworks/base/core/java/android/view/ViewRootImpl.java*/
  2. @UnsupportedAppUsage
  3. void invalidate() {
  4. mDirty.set(0,0,mWidth,mHeight);
  5. if (!mWillDrawSoon) {
  6. // 调用scheduleTraversals函数触发绘制操作
  7. scheduleTraversals();
  8. }
  9. }
  10. @UnsupportedAppUsage
  11. void scheduleTraversals() {
  12. if (!mTraversalScheduled) {
  13. ...
  14. // 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制类型的待执行消息,用于触发后续UI线程真正实现绘制动作
  15. mChoreographer.postCallback(
  16. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  17. ...
  18. }
  19. }

从以上分析可以看出,应用UI线程的绘制最终是通过往Choreographer中放入一个CALLBACK_TRAVERSAL类型的绘制任务而触发,下面的流程就和3.3.3小节中的分析的一致,Choreographer会先向系统申请Vsync信号,待Vsync信号到来后,向应用主线程MessageQueue发送一个异步消息,触发在主线程中执行ViewRootImpl#doTraversal绘制任务动作。我们接着看看ViewRootImpldoTraversal函数执行绘制流程的简化代码流程:

  1. /*frameworks/base/core/java/android/view/ViewRootImpl.java*/
  2. void doTraversal() {
  3. if (mTraversalScheduled) {
  4. mTraversalScheduled = false;
  5. // 调用removeSyncBarrier及时移除主线程MessageQueue中的Barrier同步栏删,以避免主线程发生“假死”
  6. mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
  7. ...
  8. // 执行具体的绘制任务
  9. performTraversals();
  10. ...
  11. }
  12. }
  13. private void performTraversals() {
  14. ...
  15. // 1.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的measure测量操作
  16. windowSizeMayChange |= measureHierarchy(...);
  17. ...
  18. if (mFirst...) {
  19. // 2.第一次执行traversals绘制任务时,Binder调用访问系统窗口管理服务WMS的relayoutWindow接口,实现WMS计算应用窗口尺寸并向系统surfaceflinger正式申请Surface“画布”操作
  20. relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  21. }
  22. ...
  23. // 3.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的layout测量操作
  24. performLayout(lp, mWidth, mHeight);
  25. ...
  26. // 4.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的draw测量操作
  27. performDraw();
  28. ...
  29. }
  30. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  31. ...
  32. // 原生标识View树的measure测量过程的trace tag
  33. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
  34. try {
  35. // 从mView指向的View控件树的根节点DecorView出发,遍历访问整个View树,并完成整个布局View树的测量工作
  36. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  37. } finally {
  38. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  39. }
  40. }
  41. private void performDraw() {
  42. ...
  43. boolean canUseAsync = draw(fullRedrawNeeded);
  44. ...
  45. }
  46. private boolean draw(boolean fullRedrawNeeded) {
  47. ...
  48. if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
  49. ...
  50. // 如果开启并支持硬件绘制加速,则走硬件绘制的流程(从Android 4.+开始,默认情况下都是支持跟开启了硬件加速的)
  51. mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
  52. } else {
  53. // 否则走drawSoftware软件绘制的流程
  54. if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
  55. scalingRequired, dirty, surfaceInsets)) {
  56. return false;
  57. }
  58. }
  59. }

从上面的代码流程可以看出,ViewRootImpl中负责的整个应用界面绘制的主要流程如下

  1. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的measure测量操作,由于篇幅所限,本文就不展开分析这块的详细流程;
  2. 界面第一次执行绘制任务时,会通过Binder IPC访问系统窗口管理服务WMS的relayout接口,实现窗口尺寸的计算并向系统申请用于本地绘制渲染的Surface“画布”的操作(具体由SurfaceFlinger负责创建应用界面对应的Layer对象,并通过内存共享的方式通过Binder将地址引用透过WMS回传给应用进程这边);
  3. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的layout布局操作
  4. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的draw绘制操作,如果开启并支持硬件绘制加速(从Android 4.X开始谷歌已经默认开启硬件加速),则走GPU硬件绘制的流程,否则走CPU软件绘制的流程;

以上绘制过程从systrace上看如下图所示:

5 RenderThread 线程渲染流程

截止到目前,在ViewRootImpl中完成了对界面的measurelayoutdraw等绘制流程后,用户依然还是看不到屏幕上显示的应用界面内容,因为整个Android系统的显示流程除了前面讲到的UI线程的绘制外,界面还需要经过RenderThread线程的渲染处理,渲染完成后,还需要通过Binder调用“上帧”交给surfaceflinger进程中进行合成后送显才能最终显示到屏幕上。本小节中,我们将接上一节中ViewRootImpl中最后draw的流程继续往下分析开启硬件加速情况下,RenderThread渲染线程的工作流程。由于目前Android 4.X之后系统默认界面是开启硬件加速的,所以本文我们重点分析硬件加速条件下的界面渲染流程,我们先分析一下简化的代码流程:

  1. /*frameworks/base/core/java/android/view/ViewRootImpl.java*/
  2. private boolean draw(boolean fullRedrawNeeded) {
  3. ...
  4. if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
  5. ...
  6. // 硬件加速条件下的界面渲染流程
  7. mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
  8. } else {
  9. ...
  10. }
  11. }
  12. /*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
  13. void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
  14. ...
  15. // 1.从DecorView根节点出发,递归遍历View控件树,记录每个View节点的绘制操作命令,完成绘制操作命令树的构建
  16. updateRootDisplayList(view, callbacks);
  17. ...
  18. // 2.JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;
  19. int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
  20. ...
  21. }

从上面的代码可以看出,硬件加速绘制主要包括两个阶段

  1. DecorView根节点出发,递归遍历View控件树,记录每个View节点的drawOp绘制操作命令,完成绘制操作命令树的构建;
  2. JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;

5.1 构建绘制命令树

我们先来看看第一阶段构建绘制命令树的代码简化流程:

  1. /*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
  2. private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
  3. // 原生标记构建View绘制操作命令树过程的systrace tag
  4. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
  5. // 递归子View的updateDisplayListIfDirty实现构建DisplayListOp
  6. updateViewTreeDisplayList(view);
  7. ...
  8. if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
  9. // 获取根View的SkiaRecordingCanvas
  10. RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
  11. try {
  12. ...
  13. // 利用canvas缓存DisplayListOp绘制命令
  14. canvas.drawRenderNode(view.updateDisplayListIfDirty());
  15. ...
  16. } finally {
  17. // 将所有DisplayListOp绘制命令填充到RootRenderNode中
  18. mRootNode.endRecording();
  19. }
  20. }
  21. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  22. }
  23. private void updateViewTreeDisplayList(View view) {
  24. ...
  25. // 从DecorView根节点出发,开始递归调用每个View树节点的updateDisplayListIfDirty函数
  26. view.updateDisplayListIfDirty();
  27. ...
  28. }
  29. /*frameworks/base/core/java/android/view/View.java*/
  30. public RenderNode updateDisplayListIfDirty() {
  31. ...
  32. // 1.利用`View`对象构造时创建的`RenderNode`获取一个`SkiaRecordingCanvas`“画布”;
  33. final RecordingCanvas canvas = renderNode.beginRecording(width, height);
  34. try {
  35. ...
  36. if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
  37. // 如果仅仅是ViewGroup,并且自身不用绘制,直接递归子View
  38. dispatchDraw(canvas);
  39. ...
  40. } else {
  41. // 2.利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
  42. draw(canvas);
  43. }
  44. } finally {
  45. // 3.将包含有`DisplayListOp`绘制命令缓存的`SkiaDisplayList`对象设置填充到`RenderNode`中;
  46. renderNode.endRecording();
  47. ...
  48. }
  49. ...
  50. }
  51. public void draw(Canvas canvas) {
  52. ...
  53. // draw the content(View自己实现的onDraw绘制,由应用开发者自己实现)
  54. onDraw(canvas);
  55. ...
  56. // draw the children
  57. dispatchDraw(canvas);
  58. ...
  59. }
  60. /*frameworks/base/graphics/java/android/graphics/RenderNode.java*/
  61. public void endRecording() {
  62. ...
  63. // 从SkiaRecordingCanvas中获取SkiaDisplayList对象
  64. long displayList = canvas.finishRecording();
  65. // 将SkiaDisplayList对象填充到RenderNode中
  66. nSetDisplayList(mNativeRenderNode, displayList);
  67. canvas.recycle();
  68. }

从以上代码可以看出,构建绘制命令树的过程是从View控件树的根节点DecorView触发,递归调用每个子View节点的updateDisplayListIfDirty函数,最终完成绘制树的创建,简述流程如下

  1. 利用View对象构造时创建的RenderNode获取一个SkiaRecordingCanvas“画布”;
  2. 利用SkiaRecordingCanvas在每个子View控件的onDraw绘制函数中调用drawLinedrawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData
  3. 将包含有DisplayListOp绘制命令缓存的SkiaDisplayList对象设置填充到RenderNode中;
  4. 最后将根View的缓存DisplayListOp设置到RootRenderNode中,完成构建。

以上整个构建绘制命令树的过程可以用如下流程图表示:

硬件加速下的整个界面的View树的结构如下图所示:

 最后从Systrace上看这个过程如下图所示:

 

 

5.2 执行渲染绘制任务

经过上一小节中的分析,应用在UI线程中从根节点DecorView出发,递归遍历每个子View节点,搜集其drawXXX绘制动作并转换成DisplayListOp命令,将其记录到DisplayListData并填充到RenderNode中,最终完成整个View绘制命令树的构建。从此UI线程的绘制任务就完成了。下一步UI线程将唤醒RenderThread渲染线程,触发其利用OpenGL执行界面的渲染任务,本小节中我们将重点分析这个流程。我们还是先看看这块代码的简化流程:

  1. /*frameworks/base/graphics/java/android/graphics/HardwareRenderer.java*/
  2. public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
  3. // JNI调用native层的相关函数
  4. return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
  5. }
  6. /*frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp*/
  7. static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
  8. jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
  9. ...
  10. RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
  11. env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
  12. return proxy->syncAndDrawFrame();
  13. }
  14. /*frameworks/base/libs/hwui/renderthread/RenderProxy.cpp*/
  15. int RenderProxy::syncAndDrawFrame() {
  16. // 唤醒RenderThread渲染线程,执行DrawFrame绘制任务
  17. return mDrawFrameTask.drawFrame();
  18. }
  19. /*frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp*/
  20. int DrawFrameTask::drawFrame() {
  21. ...
  22. postAndWait();
  23. ...
  24. }
  25. void DrawFrameTask::postAndWait() {
  26. AutoMutex _lock(mLock);
  27. // 向RenderThread渲染线程的MessageQueue消息队列放入一个待执行任务,以将其唤醒执行run函数
  28. mRenderThread->queue().post([this]() { run(); });
  29. // UI线程暂时进入wait等待状态
  30. mSignal.wait(mLock);
  31. }
  32. void DrawFrameTask::run() {
  33. // 原生标识一帧渲染绘制任务的systrace tag
  34. ATRACE_NAME("DrawFrame");
  35. ...
  36. {
  37. TreeInfo info(TreeInfo::MODE_FULL, *mContext);
  38. //1.将UI线程构建的DisplayListOp绘制命令树同步到RenderThread渲染线程
  39. canUnblockUiThread = syncFrameState(info);
  40. ...
  41. }
  42. ...
  43. // 同步完成后则可以唤醒UI线程
  44. if (canUnblockUiThread) {
  45. unblockUiThread();
  46. }
  47. ...
  48. if (CC_LIKELY(canDrawThisFrame)) {
  49. // 2.执行draw渲染绘制动作
  50. context->draw();
  51. } else {
  52. ...
  53. }
  54. ...
  55. }
  56. bool DrawFrameTask::syncFrameState(TreeInfo& info) {
  57. ATRACE_CALL();
  58. ...
  59. // 调用CanvasContext的prepareTree函数实现绘制命令树同步的流程
  60. mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
  61. ...
  62. }
  63. /*frameworks/base/libs/hwui/renderthread/CanvasContext.cpp*/
  64. void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
  65. RenderNode* target) {
  66. ...
  67. for (const sp<RenderNode>& node : mRenderNodes) {
  68. ...
  69. // 递归调用各个子View对应的RenderNode执行prepareTree动作
  70. node->prepareTree(info);
  71. ...
  72. }
  73. ...
  74. }
  75. /*frameworks/base/libs/hwui/RenderNode.cpp*/
  76. void RenderNode::prepareTree(TreeInfo& info) {
  77. ATRACE_CALL();
  78. ...
  79. prepareTreeImpl(observer, info, false);
  80. ...
  81. }
  82. void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
  83. ...
  84. if (info.mode == TreeInfo::MODE_FULL) {
  85. // 同步绘制命令树
  86. pushStagingDisplayListChanges(observer, info);
  87. }
  88. if (mDisplayList) {
  89. // 遍历调用各个子View对应的RenderNode的prepareTreeImpl
  90. bool isDirty = mDisplayList->prepareListAndChildren(
  91. observer, info, childFunctorsNeedLayer,
  92. [](RenderNode* child, TreeObserver& observer, TreeInfo& info,
  93. bool functorsNeedLayer) {
  94. child->prepareTreeImpl(observer, info, functorsNeedLayer);
  95. });
  96. ...
  97. }
  98. ...
  99. }
  100. void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
  101. ...
  102. syncDisplayList(observer, &info);
  103. ...
  104. }
  105. void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
  106. ...
  107. // 完成赋值同步DisplayList对象
  108. mDisplayList = mStagingDisplayList;
  109. mStagingDisplayList = nullptr;
  110. ...
  111. }
  112. void CanvasContext::draw() {
  113. ...
  114. // 1.调用OpenGL库使用GPU,按照构建好的绘制命令完成界面的渲染
  115. bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
  116. mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
  117. &(profiler()));
  118. ...
  119. // 2.将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示
  120. bool didSwap =
  121. mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
  122. ...
  123. }

从以上代码可以看出:UI线程利用RenderProxyRenderThread线程发送一个DrawFrameTask任务请求,RenderThread被唤醒,开始渲染,大致流程如下

  1. syncFrameState中遍历View树上每一个RenderNode,执行prepareTreeImpl函数,实现同步绘制命令树的操作;
  2. 调用OpenGLAPI使用GPU硬件,按照构建好的绘制命令完成界面的渲染(具体过程,由于本文篇幅所限,暂不展开分析);
  3. 将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示;

整个过程可以用如下流程图表示:

Systrace上这个过程如下图所示:

6 SurfaceFlinger图形合成

SurfaceFlinger合成显示部分属于Android系统GUI中图形显示的内容,简单的说SurfaceFlinger作为系统中独立运行的一个Native进程,借用Android官网的描述,其职责就是负责接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。如下图所示:

从上图可以看出,其实SurfaceFlingerAndroid系统的整个图形显示系统中是起到一个承上启下的作用

  • 对上:通过Surface与不同的应用进程建立联系,接收它们写入Surface中的绘制缓冲数据,对它们进行统一合成。
  • 对下:通过屏幕的后缓存区与屏幕建立联系,发送合成好的数据到屏幕显示设备。

图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?答案就是BufferQueue ,下面我们来看看BufferQueue的工作原理:

6.1 BufferQueue机制

借用一张经典的图来描述BufferQueue的工作原理:

BufferQueue是一个典型的生产者-消费者模型中的数据结构。在Android应用的渲染流程中,应用扮演的就是“生产者”的角色,而SurfaceFlinger扮演的则是“消费者”的角色,其配合工作的流程如下

  1. 应用进程中在开始界面的绘制渲染之前,需要通过Binder调用dequeueBuffer接口从SurfaceFlinger进程中管理的BufferQueue 中申请一张处于free状态的可用Buffer,如果此时没有可用Buffer则阻塞等待;
  2. 应用进程中拿到这张可用的Buffer之后,选择使用CPU软件绘制渲染或GPU硬件加速绘制渲染,渲染完成后再通过Binder调用queueBuffer接口将缓存数据返回给应用进程对应的BufferQueue(如果是 GPU 渲染的话,这里还有个 GPU处理的过程,所以这个 Buffer 不会马上可用,需要等 GPU 渲染完成的Fence信号),并申请sf类型的Vsync以便唤醒“消费者”SurfaceFlinger进行消费;
  3. SurfaceFlinger 在收到 Vsync 信号之后,开始准备合成,使用 acquireBuffer获取应用对应的 BufferQueue 中的 Buffer 并进行合成操作;
  4. 合成结束后,SurfaceFlinger 将通过调用 releaseBufferBuffer 置为可用的free状态,返回到应用对应的 BufferQueue中。

6.2 Vsync同步机制

在之前3.3小节关于Android系统屏幕刷新机制中我们分析了Vsync机制的来龙去脉。其实Android系统中的Vsync信号的产生与管理都是由SurfaceFlinger模块统一负责的,Vysnc信号一般分为两种类型:

  1. app类型的Vsyncapp类型的Vysnc信号由上层应用中的Choreographer根据绘制需求进行注册和接收,用于控制应用UI绘制上帧的生产节奏。根据3.4小结中的分析:应用在UI线程中调用invalidate刷新界面绘制时,需要先透过Choreographer向系统申请注册app类型的Vsync信号,待Vsync信号到来后,才能往主线程的消息队列放入待绘制任务进行真正UI的绘制动作;
  2. 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中是如何处理应用的请求的。

  1. /*frameworks/native/libs/gui/BufferQueueProducer.cpp*/
  2. status_t BufferQueueProducer::queueBuffer(int slot,
  3. const QueueBufferInput &input, QueueBufferOutput *output) {
  4. ATRACE_CALL();
  5. ......
  6. if (frameAvailableListener != nullptr) {
  7. frameAvailableListener->onFrameAvailable(item);
  8. }
  9. ......
  10. }

上面的frameAvailableListenerBufferQueueLayer

  1. /*frameworks/native/services/surfaceflinger/BufferQueueLayer.cpp*/
  2. void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {
  3. ......
  4. mFlinger->signalLayerUpdate();//这里触发申请一下个Vsync-sf信号
  5. ......
  6. }
  7. /*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/
  8. void SurfaceFlinger::signalLayerUpdate() {
  9. ......
  10. mEventQueue->invalidate();
  11. ......
  12. }
  13. /*frameworks/native/services/surfaceflinger/Scheduler/MessageQueue.cpp*/
  14. void MessageQueue::invalidate() {
  15. ......
  16. mEvents->requestNextVsync();// 申请一下个Vsync-sf信号
  17. ......
  18. }

以上过程从Systrace上看如下图所示:

由上面分析可知,只要有layer上帧,那么就会申请下一次的Vsync-sf信号, 当Vsync-sf信号来时会调用onMessageReceived函数来处理帧数据:

  1. /*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/
  2. void SurfaceFlinger::onMessageInvalidate(nsecs_t expectedVSyncTime) {
  3. ATRACE_CALL();
  4. ......
  5. refreshNeeded |= handleMessageInvalidate();
  6. ......
  7. signalRefresh();//再次向消息队列发送一个消息,消息到达时会调用onMessageRefresh
  8. ......
  9. }
  10. bool SurfaceFlinger::handleMessageInvalidate() {
  11. ATRACE_CALL();
  12. bool refreshNeeded = handlePageFlip();
  13. ......
  14. }

handleMessageInvalidate里一个比较重要的函数是handlePageFlip():

  1. /*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/
  2. bool SurfaceFlinger::handlePageFlip()
  3. {
  4. ATRACE_CALL();
  5. ......
  6. mDrawingState.traverse([&](Layer* layer) {
  7. if (layer->hasReadyFrame()) {
  8. frameQueued = true;
  9. if (layer->shouldPresentNow(expectedPresentTime)) {
  10. mLayersWithQueuedFrames.push_back(layer);
  11. }
  12. .......
  13. }
  14. ......
  15. });
  16. ......
  17. for (auto& layer : mLayersWithQueuedFrames) {
  18. if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
  19. mLayersPendingRefresh.push_back(layer);
  20. }
  21. .......
  22. }
  23. ......
  24. }

这里可以看出来,handlePageFlip里一个重要的工作是检查所有的Layer是否有新Buffer提交,如果有则调用其latchBuffer来处理:

  1. /*frameworks/native/services/surfaceflinger/BufferLayer.cpp*/
  2. bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
  3. nsecs_t expectedPresentTime) {
  4. ATRACE_CALL();
  5. ......
  6. status_t err = updateTexImage(recomputeVisibleRegions, latchTime, expectedPresentTime);
  7. ......
  8. }
  9. /*frameworks/native/services/surfaceflinger/BufferQueuedLayer.cpp*/
  10. status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
  11. nsecs_t expectedPresentTime) {
  12. ......
  13. status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &mAutoRefresh,
  14. &queuedBuffer, maxFrameNumberToAcquire);
  15. ......
  16. }
  17. /*frameworks/native/services/surfaceflinger/BufferLayerConsumer.cpp*/
  18. status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime,
  19. bool* autoRefresh, bool* queuedBuffer,
  20. uint64_t maxFrameNumber) {
  21. ATRACE_CALL();
  22. ......
  23. status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber);
  24. ......
  25. }
  26. status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
  27. nsecs_t presentWhen, uint64_t maxFrameNumber) {
  28. ......
  29. status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);
  30. ......
  31. }

这里调用到了BufferLayerConsumer的基类ConsumerBase里:

  1. /*frameworks/native/libs/gui/ConsumerBase.cpp*/
  2. status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
  3. nsecs_t presentWhen, uint64_t maxFrameNumber) {
  4. ......
  5. status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);
  6. ......
  7. }

到这里onMessageInvalidate中的主要工作结束,在这个函数的处理中:SurfaceFlinger主要是检查每个Layer是否有新提交的Buffer, 如果有则调用latchBuffer将每个BufferQueue中的Slot 通过acquireBuffer拿走。此过程从Systrace上看如下图有所示:

之后acquireBuffer拿走的Buffer(Slot对应的状态是ACQUIRED状态)会被交由HWC Service处理,这部分是在onMessageRefresh中处理的:

  1. /*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/
  2. void SurfaceFlinger::onMessageRefresh() {
  3. ATRACE_CALL();
  4. ......
  5. mCompositionEngine->present(refreshArgs);
  6. ......
  7. /*frameworks/native/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp*/
  8. void CompositionEngine::present(CompositionRefreshArgs& args) {
  9. ATRACE_CALL();
  10. ......
  11. for (const auto& output : args.outputs) {
  12. output->present(args);
  13. }
  14. ......
  15. }
  16. /*frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp*/
  17. void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
  18. ATRACE_CALL();
  19. ......
  20. updateAndWriteCompositionState(refreshArgs);//告知HWC service有哪些layer要参与合成
  21. ......
  22. beginFrame();
  23. prepareFrame();
  24. ......
  25. finishFrame(refreshArgs);
  26. postFramebuffer();//这里会调用到HWC service的接口去present display合成画面
  27. }
  28. void Output::postFramebuffer() {
  29. ......
  30. auto frame = presentAndGetFrameFences();
  31. ......
  32. }
  33. /*frameworks/native/services/surfaceflinger/displayhardware/HWComposer.cpp*/
  34. status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) {
  35. ATRACE_CALL();
  36. ......
  37. auto error = hwcDisplay->present(&displayData.lastPresentFence);//送去HWC service合成
  38. ......
  39. std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
  40. error = hwcDisplay->getReleaseFences(&releaseFences);
  41. RETURN_IF_HWC_ERROR_FOR("getReleaseFences", error, displayId, UNKNOWN_ERROR);
  42. displayData.releaseFences = std::move(releaseFences);//获取releaseFence, 以便通知到各个Slot, buffer被release后会通过dequeueBuffer给到应用,应用在绘图前会等待releaseFence
  43. ......
  44. }

以上过程从systrace上看如下图所示:

最后总结一下应用调用queueBuffer将一帧Buffer数据提到SurfaceFlingerSurfaceFlinger的主要处理流程,:

  1. 首先Binder线程会通过BufferQueue机制把应用上帧的Slot状态改为QUEUED, 然后把这个Slot放入mQueue队列, 然后通过onFrameAvailable回调通知到BufferQueueLayer, 在处理函数里会请求下一次的Vsync-sf信号;

  2. Vsync-sf信号到来后,SurfaceFlinger主线程要执行两次onMessageReceived, 第一次要检查所有的Layer看是否有上帧, 如果有Layer上帧就调用它的latchBuffer把它的Buffer acquireBuffer取走;并发送一个消息到主消息队列,让主线程再次走进onMessageReceived,;

  3. 第二次走进来时,主要执行present方法,在这些方法里会和HWC service沟通,调用它的跨进程接口通知它去做图层的合成后送显示器显示。

后续HWC service的合成以及屏幕的详细显示原理由于篇幅有限就不展开说明,感兴趣的读者可以参考系列文章Android画面显示流程分析(1) - 简书

7 流程总结与卡顿定义

7.1 应用绘制上帧流程总结

在本节中我们以用户手指上下滑动应用界面的操作场景为例,结合系统源码和Systrace工具,按照执行顺序分析了Android应用绘制上帧显示的系统运行机制与总体流程,我们以一张图描述如下:

最后总结整个流程大致如下

  1. 用户手指触摸屏幕后,屏幕驱动产生Input触控事件;框架system_server进程中的EventHub通过epoll机制监听到驱动产生的Input触控事件上报,由InputReader读取到Input事件后,唤醒InputDispatcher找到当前触控焦点应用窗口,并通过事先建立的socket通道发送Input事件到对应的应用进程;

  2. 应用进程收到Input触控事件后UI线程被唤醒进行事件的分发,相关View控件中根据多个ACTION_MOVE类型的Input事件判断为用户手指滑动行为后,通过Choreographer向系统注册申请app类型的Vsync信号,并等待Vsync信号到来后触发绘制操作;

  3. app类型的Vsync信号到来后,唤醒应用UI线程并向其消息队列中放入一个待执行的绘制任务,在UI线程中先后遍历执行View控件树的测量、布局和绘制(硬件加速默认开启的状态下会遍历并记录每个Viewdraw操作生成对应的绘制命令树)操作;

  4. View控件树的绘制任务执行完成后会唤醒应用的RenderThread渲染线程执行界面渲染任务;整个渲染任务中会先同步UI线程中构建好的绘制命令树,然后通过dequeueBuffer申请一张处于free状态的可用Buffer,然后调用SkiaOpenGLPipeline渲染管道中使用GPU进行渲染操作,渲染完成后swapBuffer触发queueBuffer动作进行上帧;

  5. 应用渲染线程最后的queueBuffer上帧动作,会唤醒对端SurfaceFlinger进程中的Binder处理线程,其中将对应用BufferQuque中的Buffer标记为Queued状态,然后注册申请sf类型的Vsync信号;

  6. sf类型的Vsync信号到来后会唤醒SurfaceFlinger的主线程执行一帧的合成任务,其中会先通过handlePageFlip操作遍历所有的应用Layer找到有上帧操作的处于Queued状态的Buffer进行AcquireBuffer获取标记锁定,然后执行persent动作调用唤醒HWC service进程的工作线程执行具体的图层的合成送显操作;

  7. HWC service中最终会收到SurfaceFlinger的请求后,进行图层合成操作,最终通过调用libDrm库相关接口Commit提交Buffer数据到Kernel内核中的屏幕驱动,并最终送到屏幕硬件上显示。

7.2 卡顿的定义

根据本节中我们对Android应用上帧显示的原理分析,我们初步可以判断:如果在一个Vsync周期内(60HZ的屏幕上就是16.6ms),按照整个上帧显示的执行的顺序来看,应用UI线程的绘制、RenderThread线程的渲染、SurfaceFlinger/HWC的图层合成以及最终屏幕上的显示这些动作没有全部都执行完成的话,屏幕上就会显示上一帧画面的内容,也就是掉帧,而人的肉眼就可能会感觉到画面卡顿(由于 Triple Buffer 的存在,这里也有可能不掉帧)。

这里借用高爷的一段经典描述从三个方面定义卡顿:

  1. 从现象上来说,在 App 连续的动画播放或者手指滑动列表时(关键是连续),如果连续 2 帧或者 2 帧以上,应用的画面都没有变化,那么我们认为这里发生了卡顿;

  2. SurfaceFlinger 的角度来说,在 App 连续的动画播放或者手指滑动列表时(关键是连续),如果有一个 Vsync 到来的时候 ,App 没有可以用来合成的 Buffer,那么这个 Vsync 周期 SurfaceFlinger 就不会走合成的逻辑(或者是去合成其他的 Layer),那么这一帧就会显示 App 的上一帧的画面,我们认为这里发生了卡顿;

  3. App 的角度来看,如果渲染线程在一个 Vsync 周期内没有 queueBufferSurfaceFlingerApp 对应的 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



作者:努比亚技术团队
链接:https://www.jianshu.com/p/386bbb5fa29a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/342926
推荐阅读
相关标签
  

闽ICP备14008679号