Android-显示流程

从 onResume 到像素上屏:用日志逐行走读 Android 显示全流程

网上讲 Android 渲染原理的文章很多,但大多数停在"概念图 + 源码跳转"层面------你看完知道有 SurfaceFlinger,知道有 vsync,却始终回答不了一个问题:我写完 onResume() 之后,屏幕上的像素到底是怎么一步步出现的?中间每个环节发生在哪个进程、哪一行代码、哪个时刻?

本文换一种学法:先讲清前置知识,然后直接在 AOSP 三个进程的关键函数里埋日志(26 个点位),冷启动一个真实 App,把 logcat 的时间轴拿出来,一行一行对着源码讲。

环境说明:Android 14 车机平台(分屏多窗口),测试 App 为 com.hmi.bev.carapp/.MainActivity,屏幕 2560x1440。文中所有日志均为真实抓取,所有代码行号均可在对应文件中对照。


一、前置知识

不讲清这些概念,后面的日志一行都读不懂。每一节都尽量用一句话 + 一张图说透。

1.1 参与显示的五个角色

Android 显示不是"App 画到屏幕上"这么简单,至少五个角色分处四个进程:

角色 所在进程 职责 本文日志中的 pid
App(UI线程/RenderThread) App 进程 构建 UI、录制绘制命令、驱动 GPU 渲染、提交 buffer 5689
WMS(WindowManagerService) system_server 窗口的"户籍管理":登记窗口、分配尺寸/Z序、编排窗口动画 1215
AMS(ActivityManagerService) system_server Activity 生命周期调度,负责喊 App 去 onResume 1215
SurfaceFlinger(SF) 独立进程 把所有窗口的 layer 合成成一帧,交给显示硬件 308
HWC(Hardware Composer) vendor 进程 显示硬件抽象层:高效合成(overlay)、送显(page flip)、上报硬件 vsync ---
GPU 硬件 光栅化、合成执行者 ---

一个关键认知:App 永远画不到屏幕上,App 只能画到自己的一块内存(buffer)里。 屏幕上某一时刻的内容,是 SF 把所有窗口的 buffer 按 Z 序叠出来的。这就是"合成"的含义------就像 Photoshop 的图层。

1.2 屏幕是怎么"亮"的:扫描、VBI、VSync

显示器(无论 LCD/OLED)刷新是逐行扫描 :从左上角第一个像素开始,一行一行写到右下角,写完一帧回到起点。写完一帧、准备写下一帧之间的空隙叫 VBI(Vertical Blanking Interval,垂直消隐期)

由此引出三个基础问题:

① 画面撕裂 :如果扫描到一半时 App 把 FrameBuffer 改了,上半屏是旧帧、下半屏是新帧------撕裂。解法是双缓冲:App 画到"后缓冲区",扫描读"前缓冲区",只在 VBI 里交换两块缓冲区(page flip)。

② Jank(掉帧) :双缓冲解决了撕裂,但 App 不知道何时该开始画。如果画得太晚,VBI 到了后缓冲区还没好,只能继续显示旧帧------连续两帧显示同一画面就是 jank。解法是 VSync 同步:硬件在每个 VBI 发出一个信号(VSync),通知上游"该开始画下一帧了"。

③ 流水线阻塞 :双缓冲下,如果 GPU 画得慢,CPU 想画下一帧时唯一的空闲 buffer 都没有,只能干等。解法是三缓冲(多缓冲) :多一块 buffer 让 CPU/GPU/显示器三者流水线并行。

Android 的答案就是把这三层方案打包:VSync 信号 + BufferQueue(默认 3 块 GraphicBuffer)

1.3 VSync 不是广播,是"请求-响应";而且分两路

这是初学者最容易误解的两点:

第一,vsync 是 App 主动请求的。 Choreographer 不会被动收到每秒钟 60 个信号------只有当 App 有绘制需求(你调了 requestLayout()/invalidate())时,它才向 SF 的 EventThread 请求"下一个 vsync 叫我"。没人画图的进程就静静睡着,省电。

第二,vsync 分 App 相位和 SF 相位两路。 如果 App 和 SF 在同一个信号时刻惊醒,SF 合成时 App 的帧八成还没画完。所以 SF 内部会把硬件 vsync 建模后错开分发

arduino 复制代码
vsync-app  ──────► App 开始画第 N 帧
(App 画...约一个周期)
vsync-sf   ──────► SF 醒来,latch 第 N 帧、合成、送显

本文日志里 FF17 onVsync(App 收到)和 FF42 commit(SF 醒来)会以约 16.7ms 的节奏交错出现,就是这个双相位机制在呼吸。

1.4 GPU 渲染管线:为什么"画"要分两步

CPU 擅长逻辑控制,GPU 擅长海量并行计算。把 200 万个像素算出颜色是典型的"无逻辑、纯计算",所以交给 GPU。一帧从数据到像素经历四个阶段:

arduino 复制代码
①Application   CPU:准备好"图元"(三角形/矩形/文字)和它们的顶点、颜色、纹理
②Geometry      GPU:顶点着色器变换坐标 → 装配出三角形
③Rasterization GPU:算出三角形覆盖了哪些像素
④Pixel         GPU:片段着色器给每个像素上色 → 测试混合 → 写入 buffer

重要推论:CPU 发出的 drawRect() 只是"命令",像素是 GPU 稍后算出来的。 CPU 提交命令后可以继续干别的(异步),GPU 算完了用 fence(栅栏) 通知下游"这块 buffer 可以用了"。后面会看到 SF latch buffer 前要先等 fence,就是在这里衔接的。

1.5 Skia / OpenGL ES / EGL:三个名词各站在哪

  • OpenGL ES:GPU 的通用驱动接口。你的代码不直接碰 GPU,通过它发命令。
  • Skia :2D 绘图库,Android UI 的画笔。它底层可以走 CPU(软件绘制,画到 Bitmap),也可以走 OpenGL ES/Vulkan(硬件加速)。你在 onDraw() 里写的 canvas.drawRect() 最终进的是 Skia。
  • EGL :OpenGL ES 与"窗口系统"之间的胶水。它负责三件事:eglCreateWindowSurface(把 GL 的输出绑定到一块可显示的 buffer 上)、管理 GL 上下文(eglCreateContext)、eglSwapBuffers(一帧画完,提交)。后面 RenderThread 初始化时会遇到它。

1.6 HWC:最后一棒

SF 合成完(或决定不合)后要交给显示硬件。理论上 SF 可以用 GPU 把所有 layer 叠成一张图再给显示器,但现代显示控制器(DPU)自带多个 overlay plane,能硬件层

面叠加多张图------零 GPU 开销、零内存拷贝。HWC 就是这个能力的 HAL 封装:

vbnet 复制代码
SF: "这几层你能直接叠吗?"(prepare)
HWC: "状态栏和导航栏我叠(DEVICE),中间那层有半透明混合,你自己用GPU合(CLIENT)"
SF: 用 RenderEngine 把 CLIENT 层合成一张 framebuffer
SF: "拿去"(presentDisplay)
HWC: 驱动 DPU 在下个 VBI page flip → 面板扫出新帧 👁

1.7 核心对象关系图

arduino 复制代码
【App 进程】                          【system_server】              【SurfaceFlinger】
​
Activity                                              WindowState  ←─ 窗口的"户口档案"
 └ PhoneWindow                                        └ SurfaceControl ────┐
    └ DecorView                                                            │ binder 句柄
       └ ViewRootImpl ── 一个窗口的显示总管家 ──────────────────────────┐    │
          ├ Choreographer(收vsync、排帧)                              │    │
          │   └ DisplayEventReceiver ──(vsync Connection)──────────► EventThread(app)
          ├ Surface(画布的Java壳)                                     │    │
          ├ BLASTBufferQueue(App侧buffer池+提交器)                     │    │
          │      └ GraphicBuffer × 3 ←─ App进程里的显示内存             ▼    ▼
          └ ThreadedRenderer(硬件渲染器)                          Layer #135
                └ RenderThread(独立渲染线程)                    (SF里的图层对象,
                                                                 持有buffer、参与合成)
  • SurfaceControl:指向 SF 里某个 Layer 的"遥控器"(binder 句柄)。App 和 WMS 都持有它,但真正干活的对象在 SF 进程。
  • Layer:SF 里的图层。一个窗口的像素容器。
  • BLASTBufferQueue :Android 12+ 的提交通道。App 的 RenderThread 画完一帧,把 buffer 封进 SurfaceControl.Transaction 直接提交给 SF(旧架构是 App/SF 之间一条双向 binder 的 BufferQueue,BLAST 干掉了中间商)。
  • ViewRootImpl:一个窗口 = 一个 ViewRootImpl,它串起 Choreographer/Surface/Renderer 和与 WMS 的会话。

1.8 硬件绘制 vs 软件绘制

  • 软件绘制 (默认已淘汰):主线程里 Skia 用 CPU 直接画到 Bitmap(就是一块 buffer),lockCanvas → draw → unlockCanvasAndPost。同步、慢。

  • 硬件绘制(默认):分两步走------

    1. UI 线程 :遍历 View 树,把 draw() 录制成一棵 RenderNode 操作树(display list)。注意是录制,不产生像素!
    2. RenderThread:独立线程,把操作树交给 Skia,Skia 通过 OpenGL ES 发命令给 GPU,GPU 异步渲染到 buffer。

    好处:主线程不被 GPU 拖累;display list 可以缓存复用(动画只改属性不用重录)。

本文示例 App 走硬件绘制 + BLAST 提交,是现代 Android 的主干道。


二、埋点:让流程可见

学习最快的方式是让代码自己开口。我在三个进程的关键函数里埋了 26 条日志,编号即流程顺序:(请注意编号是以APP开头为FF01-20,system_server开头的为FF3,SF开头的为FF4,并不是从01-44顺序)

进程 TAG 点位 所在文件
App FFApp FF01~FF20 ActivityThread / WindowManagerGlobal / ViewRootImpl / Choreographer / ThreadedRenderer
WMS FFWms FF30~FF34 WindowManagerService
SF FFSF FF40~FF44 SurfaceFlinger.cpp
编号 函数 回答的问题
FF01/02 handleResumeActivity onResume 前后
FF03/04 wm.addView / new ViewRootImpl 窗口管家何时诞生
FF05 ViewRootImpl.setView 窗口绑定 DecorView
FF06/07 addToDisplayAsUser 调用/返回 去WMS登记窗口的binder往返
FF08 scheduleTraversals 遍历任务挂入队列
FF09 doTraversal vsync 放行、开始 measure/layout/draw
FF10/11 relayoutWindow 调用/返回 向WMS要画布的binder往返
FF12 BLAST surface ready 画布就绪
FF13/14 draw / 硬件分支 绘制入口
FF15 reportDrawFinished 首帧画完上报WMS
FF16/17 scheduleVsync / onVsync vsync 请求/到达(每帧循环)
FF18~20 ThreadedRenderer.draw 录制 display list、移交 RenderThread
FF30 WMS addWindow 窗口登记进 system_server
FF31~33 WMS relayoutWindow/createSurfaceControl 画布在WMS侧的诞生
FF34 WMS finishDrawingWindow WMS 知道"你画完了"
FF40/41 SF createLayer Layer 在SF侧的诞生
FF42/43/44 SF commit/latchBuffer/composite 取走buffer、合成、送显

编译方式:make framework services surfaceflinger;抓取方式:

perl 复制代码
adb logcat -c
adb shell am start -n com.hmi.bev.carapp/.MainActivity
adb logcat -d -v time | grep -E "FFApp|FFWms|FFSF"

下面正式开讲。所有日志来自一次真实冷启动,pid:5689=carapp,1215=system_server,308=SF,1848=splitapp(分屏对侧,也在启动),1817=Launcher(退场中)


三、主流程走读

3.1 生命周期的终点,显示的起点

ActivityThread::handleResumeActivity 一路会调用到 ViewRootImpl::setView 方法,方法内会跨进程调用 WMS::addWindow 来添加窗口。 ◦ WMS 会为该窗口创建一个 Session(会话) ,用于 后续向 SF 请求创建 Surface。 ◦ 后续 App 执行 ViewRootImpl::performTraversals 测绘时,会走到 ViewRootImpl::relayoutWindow,进而调用到 WMS::relayoutWindow 方法。 ◦ WMS::relayoutWindow 方法会创建 SurfaceControl 对象,其内部利用 SurfaceComposerClient 向 SF 请求创建 生产者-消费者模型。 ◦ SF 收到消息后,创建 Layer、Producer、Consumer、BufferQueue 对象。 ◦ WMS::relayoutWindow 方法后续创建一个 Surface.cpp 对象,内部持有 Producer 的引用。将这个 Surface.cpp 对象绑定到 Surface.java 的 mNativeObject 引用上。 ◦ 至此,Surface、BufferQueue、Layer 三者便联系了起来,App 与 SF 之间可以进行数据传输了

css 复制代码
07:56:43.869  5689  5689 D FFApp: FF01 handleResumeActivity enter ActivityRecord{...}
07:56:43.871  5689  5689 D FFApp: FF02 performResumeActivity done, activity=ComponentInfo{...MainActivity}
java 复制代码
// ActivityThread.java:5029
@Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        android.util.Slog.d("FFApp", "FF01 handleResumeActivity enter " + r);
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;
​
        // TODO Push resumeArgs into the activity for consideration
        // skip below steps for double-resume and r.mFinish = true case.
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
            // Although the activity is resumed, it is going to be destroyed. So the following
            // UI operations are unnecessary and also prevents exception because its token may
            // be gone that window manager cannot recognize it. All necessary cleanup actions
            // performed below will be done while handling destruction.
            return;
        }
​
        final Activity a = r.activity;
        android.util.Slog.d("FFApp", "FF02 performResumeActivity done, activity=" + a.getComponentName());
​
        if (localLOGV) {
            Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
                    + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
        }
        ...
          
         if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    android.util.Slog.d("FFApp", "FF03 calling wm.addView(decor, l)");
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

① 里回调了你写的 onResume()------FF01/FF02 之间夹着 App 自己打的 MainActivity onResume

此刻要建立的认知:onResume 执行完,屏幕上还没有这个 Activity 的任何像素。 手里只有一个 PhoneWindow 和它包裹的 DecorViewsetContentView 早就装好的 View 树)------一堆 Java 对象而已。屏幕上显示的是 AMS 更早准备的 Splash Screen(启动画面窗口,日志后面会看到它的 layer #117)。接下来的所有日志,就是把"一堆 Java 对象"变成"像素"的过程。

3.2 创建 ViewRootImpl:窗口管家上任

makefile 复制代码
07:56:43.871  5689  5689 D FFApp: FF03 calling wm.addView(decor, l)
07:56:43.896  5689  5689 D FFApp: FF04 ViewRootImpl created for com.hmi.bev.carapp
scss 复制代码
// WindowManagerGlobal.java:397
if (windowlessSession == null) {
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,          //核心动作就是 new ViewRootImpl
                        windowlessSession, new WindowlessWindowLayout());
            }
            android.util.Slog.d("FFApp", "FF04 ViewRootImpl created for " + view.getContext().getPackageName());
            view.setLayoutParams(wparams);
​
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
​
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
                // BadTokenException or InvalidDisplayException, clean up.
                if (viewIndex >= 0) {
                    removeViewLocked(viewIndex, true);
                }
                throw e;
            }

wm.addView() 一路进到 WindowManagerGlobal.addView(),核心动作就是 new ViewRootImpl。看它的构造函数:

arduino 复制代码
public ViewRootImpl(Context context, Display display) {
    ...
    mChoreographer = Choreographer.getInstance();   // ★
}

Choreographer.getInstance() 会创建 FrameDisplayEventReceiver,其 native 构造 binder 到 SF:

ini 复制代码
// frameworks/native/libs/gui/DisplayEventReceiver.cpp
mEventConnection = sf->createDisplayEventConnection(vsyncSource);

这一步在 App 与 SF 的 EventThread(app) 之间建立了一条 Connection (底层 socketpair)。从此 vsync 信号能从 SF 唤醒这个进程------后面所有 FF17 日志的物理前提在此刻奠定

3.3 setView:一次精心的排序

makefile 复制代码
07:56:43.897  5689  5689 D FFApp: FF05 setView enter
07:56:43.912  5689  5689 D FFApp: FF08 scheduleTraversals (post TRAVERSAL callback)   ← 先!
07:56:43.912  5689  5689 D FFApp: FF06 addToDisplayAsUser call (binder->WMS)
07:56:43.916  1215  2117 D FFWms: FF30 addWindow title=...MainActivity type=1
07:56:43.927   308  1464 I FFSF: FF40 createLayer 8ba7de3 ...MainActivity
07:56:43.928   308  1464 I FFSF: FF41 createLayer done '8ba7de3 ...#130' id=130
07:56:43.940  5689  5689 D FFApp: FF07 addToDisplayAsUser returned res=11
scss 复制代码
framework\base\core\java\android\view\ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                android.util.Slog.d("FFApp", "FF05 setView enter " + mWindowAttributes.getTitle());
                mView = view;
​
                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);
            ....
                
                
            // ViewRootImpl.java:1457(源码注释:Schedule the first layout -before- adding to the
            // window manager, to make sure we do the relayout before receiving any other events)
           requestLayout();                                    // ① → FF08
​
            ....
            android.util.Slog.d("FFApp", "FF06 addToDisplayAsUser call (binder->WMS)"); // ② :1486 → FF06
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                            mTempControls, attachedFrame, compatScale);
                    android.util.Slog.d("FFApp", "FF07 addToDisplayAsUser returned res=" + res);

注意日志顺序:FF08(挂遍历任务)打在 FF06(跨进程注册窗口)之前。这不是日志乱序,是源码的刻意设计

scss 复制代码
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();       // FF08
        }
    }
scss 复制代码
​
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            android.util.Slog.d("FFApp", "FF08 scheduleTraversals (post TRAVERSAL callback)");
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 插入同步屏障
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);// 挂遍历回调,等待到FF17,就调到下面的FF09开工
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

先把遍历任务挂进 Choreographer 队列,再去 binder 注册窗口。等注册返回、下个 vsync 一到,遍历立即开跑,一次不用多等。

① FF08 scheduleTraversals()(ViewRootImpl.java:2623)做两件事:

同步屏障让主线程暂停处理普通消息,优先等"异步"的 vsync 消息------遍历任务从此在等一个信号。等待到FF17,就调到下面的FF09开工

② FF06这条 binder->WMS进了 system_server从而打印出了FF30

应用端是如何 SurfaceFlinger 建立链接,进行通信。 它们直接使用 SurfaceSession 通信,SurfaceSession 的创建时机发生在应用第一个窗口显示(addWindow流程)流程,APP 端触发 addWindow 流程是通过匿名 Binder -> Session 这个类与 WindowManagerService 通信的。 在 WindowManagerService::addWindow 方法会先创建 WindowState 对象,此时也会将 Session 作为参数传递。 然后又会执行 WindowState::attach 方法,这里会触发 SurfaceSession 的创建逻辑

java 复制代码
// WindowManagerService.java:1408
public int addWindow(Session session, IWindow client, LayoutParams attrs, ...) {
    android.util.Slog.d("FFWms", "FF30 addWindow title=" + attrs.getTitle() + " type=" + attrs.type);                  // ← FF30
    ...
    final WindowState win = new WindowState(this, session, ...);    // 建户口档案
    ...
    ...
    win.attach();
    mWindowMap.put(client.asBinder(), win);
    win.initAppOpsState();

WindowState 只是一条登记记录,此刻还没有画布。 但日志里 SF 同期打出了 FF40/41------layer #130 诞生了:

scss 复制代码
// SurfaceFlinger.cpp:5358
status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult) {
    ALOGI("FFSF FF40 createLayer %s", args.name.c_str());           // ← FF40
    ...
    result = createBufferStateLayer(args, &outResult.handle, &layer);  // new Layer
    result = addClientLayer(args, ...);                             // 挂进layer树
    ALOGI("FFSF FF41 createLayer done '%s' id=%d", String8(outResult.layerName).c_str(),
          static_cast<int>(outResult.layerId));                    // ← FF41

名字带窗口 hash 前缀 8ba7de3 的 #130 是 WMS 为窗口建的容器层 (WMS 摆位置、做动画用的"外壳"),不是画布 。真正的画布 layer 要等 3.5 节的 #135。这里先记住一个事实:WMS 自己也是 SF 的客户,它建的 layer(容器层、动画 leash、Task 层)比 App 的画布 layer 多得多 ------后面日志里成串的 animation-leashSplitDecorManager 都是它们。

FF07 的 res=11(二进制 1011)= ADD_OKAY | ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS | ...,注册成功。

3.4 vsync 循环:FF16 与 FF17 的二重奏

在继续主线前,先看这对贯穿全场的日志:

python 复制代码
(此后每 ~16.7ms 循环,本文只截一次)
... FF16 scheduleVsync (request next vsync)
... FF17 onVsync received ts=64251308413
... FF16 scheduleVsync ...
07:56:43.987  5689  5689 D FFApp: FF09 doTraversal -> performTraversals
java 复制代码
// Choreographer.java:987
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void scheduleVsyncLocked() {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
            android.util.Slog.d("FFApp", "FF16 scheduleVsync (request next vsync)");
            mDisplayEventReceiver.scheduleVsync();  // binder → SF EventThread::requestNextVsyn
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
​
// Choreographer.java:1284
public void onVsync(long timestampNanos, ...) {
    Slog.d("FFApp", "FF17 onVsync received ts=" + timestampNanos);
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);                  // 异步消息:能穿过同步屏障
    mHandler.sendMessageAtTime(msg, ...);
}
scss 复制代码
// \frameworks\base\core\java\android\view\ViewRootImpl.java
void doTraversal() {
        if (mTraversalScheduled) {
            android.util.Slog.d("FFApp", "FF09 doTraversal -> performTraversals");    // ← FF09
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
​
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
​
            performTraversals();            //首帧 performTraversals
​
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

完整信号回路(对应前置知识 1.3):

scss 复制代码
硬件vsync(HWC上报) → SF调度器按app相位分发 → EventThread(app)检查活跃Connection
→ 往bitube(socketpair)写一个字节 → App主线程Looper的epoll被唤醒
→ onVsync(FF17) → doFrame() → 执行到期的CALLBACK_TRAVERSAL → FF09

ts 相邻差值 ≈ 16.7ms,就是 60Hz 刷新周期。每一帧的 measure/layout/draw 都被这对日志包着:FF16 是"预约",FF17 是"到号",FF09 是"开工"。

防止流程混乱,这里再贴下调用

硬件 vsync 到达 └─ Choreographer.FrameDisplayEventReceiver.onVsync() Choreographer.java:1284 (FF17) └─ Choreographer.doFrame() → doCallbacks(CALLBACK_TRAVERSAL) └─ TraversalRunnable.run() ViewRootImpl.java:9546 └─ doTraversal() ViewRootImpl.java:2643 (FF09) └─ performTraversals() ViewRootImpl.java:3093 --> FF10 -FF12 ├─ performMeasure() ... ├─ performLayout() ... └─ performDraw() ViewRootImpl.java:4825 └─ draw(fullRedrawNeeded, ...) ViewRootImpl.java:4990 ├─ FF13 打印(surface.isValid() 之后) :4996 ├─ FF14 打印(isHardwareEnabled() 分支) :5091 └─ mAttachInfo.mThreadedRenderer.draw() → FF18

3.5 relayout:App 拿到画布

FF09-doTraversal调用 首帧 performTraversals 走到一半发现没有 Surface,发起首帧最贵的一次调用链

scss 复制代码
base\core\java\android\view\ViewRootImpl.java
private void performTraversals() {
    ...
    ...
      if (mFirst || viewVisibilityChanged) {
                    mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
                }
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);  //调用relayoutWindow,后续流程打印FF10 - FF12
                cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
                        == RELAYOUT_RES_CANCEL_AND_REDRAW;
                cancelReason = "relayout";
    ...
    ...
      // Ask host how big it wants to be
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      performLayout(lp, mWidth, mHeight);
      if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) {
                mActiveSurfaceSyncGroup.markSyncReady();
            }
}
ini 复制代码
07:56:44.005  5689  5689 D FFApp: FF10 relayoutWindow enter vis=0
07:56:44.045  1215  2117 D FFWms: FF31 relayoutWindow win=8ba7de3 ...MainActivity vis=0 req=2560x1440
07:56:44.046  1215  2117 D FFWms: FF32 createSurfaceControl win=8ba7de3 ...
07:56:44.047   308   340 I FFSF: FF40 createLayer com.fawvw.hmi.bev.carapp/.MainActivity
07:56:44.047   308   340 I FFSF: FF41 createLayer done '...MainActivity#135' id=135   ★画布诞生
07:56:44.048  1215  2117 D FFWms: FF33 SurfaceControl created OK for 8ba7de3 ...
07:56:44.099  5689  5689 D FFApp: FF11 relayout returned, surfaceControlValid=true frame=Rect(0, 0 - 2560, 1440)
07:56:44.103  5689  5689 D FFApp: FF12 app surface ready (BLAST=true, size=2560x1440)

App 侧(ViewRootImpl.java:8418):

csharp 复制代码
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, ...) {
    Slog.d("FFApp", "FF10 relayoutWindow enter vis=" + viewVisibility);
    ...
    relayoutResult = mWindowSession.relayout(mWindow, params,
            requestedWidth, requestedHeight, viewVisibility, ...,
            mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,   // ★出参
            mTempInsets, mTempControls, mRelayoutBundle);
    Slog.d("FFApp", "FF11 relayout returned, surfaceControlValid=" + mSurfaceControl.isValid()
            + " frame=" + mTmpFrames.frame);

注意 mSurfaceControl出参 :App 递进去一个空壳,WMS 填好再跨进程拷回来。binder 传的不是内存不是 buffer,是 SF 里 layer #135 的"遥控器"句柄

ViewRootImpl 创建的时候就有 mSurface 和 mSurfaceControl 2个成员变量,但是只是个 java 层的对象实例,目前还没有对应 native 的实现,真正赋值的方式是在 relayoutWindow 流程中,后面解释

WMS 侧链路(WindowManagerService.java):

csharp 复制代码
// :2152
public int relayoutWindow(Session session, IWindow client, ...) {
    android.util.Slog.d("FFWms", "FF31 relayoutWindow win=" + win.getName()
                    + " vis=" + viewVisibility + " req=" + requestedWidth + "x" + requestedHeight);     // 布局计算:算出2560x1440
    ...
    result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);  // :2373
​
// :2611
private int createSurfaceControl(SurfaceControl outSurfaceControl, int result,
            WindowState win, WindowStateAnimator winAnimator) {
        android.util.Slog.d("FFWms", "FF32 createSurfaceControl win=" + win.getName());
        if (!win.mHasSurface) {
            result |= RELAYOUT_RES_SURFACE_CHANGED;
        }
​
        WindowSurfaceController surfaceController;
        try {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
            surfaceController = winAnimator.createSurfaceLocked();     // new SurfaceControl
            //   → SurfaceControl.nativeCreate → SurfaceComposerClient::createSurface
            //     → binder → SurfaceFlinger::createLayer  ★ FF40/41     这里调用打印FF40/41
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        if (surfaceController != null) {
            surfaceController.getSurfaceControl(outSurfaceControl);    // 填出参
            android.util.Slog.d("FFWms", "FF33 SurfaceControl created OK for " + win.getName());
            ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
​
        } else {
            // For some reason there isn't a surface.  Clear the
            // caller's object so they see the same state.
            ProtoLog.w(WM_ERROR, "Failed to create surface control for %s", win);
            outSurfaceControl.release();
        }
​
        return result;
    }

relayoutWindow 流程是在 addWindow 流程之后, addWindow 流程会在 system_server 进程为要显示的窗口创建对应的 WindowState 。所以这里在执行 relayoutWindow 的时候可以通过windowForClientLocked 方法获取到对应的 WindowState ,而 WindowStateAnimator 是在 WindowState 构造方法中创建的。 拿到了 WindowState 和 WindowStateAnimator 就可以创建SurfaceControl了,注意参数,也就是打印FF33这里,这个 outSurfaceControl 就是在应用端传递过来的,参数前面加上了"out"是 binder 作为出参的方式,也就是说 system_server 进程会对这个参数进行赋值,这样应用端 ViewRootImpl 下的 SurfaceControl 就会持有一个 native 层的 SurfaceControl 指针

SF 里 layer #135 诞生(无窗口 hash 前缀,与 #130 区分)------它才是 App 像素的最终容器。此刻三方各持一份指向它的钥匙:

scss 复制代码
carapp(5689)              system_server(1215)         SurfaceFlinger(308)
mSurfaceControl ────────── WindowState.mSurfaceControl ─→ Layer #135(真身)
scss 复制代码
// SurfaceFlinger.cpp:5358
status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult) {
    ALOGI("FFSF FF40 createLayer %s", args.name.c_str());           // ← FF40
    ...
    result = createBufferStateLayer(args, &outResult.handle, &layer);  // new Layer
    result = addClientLayer(args, ...);                             // 挂进layer树
    ALOGI("FFSF FF41 createLayer done '%s' id=%d", String8(outResult.layerName).c_str(),
          static_cast<int>(outResult.layerId));                    // ← FF41

再回到 App(ViewRootImpl.java:8553):

csharp 复制代码
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, ...) {
    Slog.d("FFApp", "FF10 relayoutWindow enter vis=" + viewVisibility);
    ...
     relayoutResult = mWindowSession.relayout(mWindow, params,
            requestedWidth, requestedHeight, viewVisibility, ...,
            mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,   // ★出参,作为参数传递到 system_sertvice 进程
            mTempInsets, mTempControls, mRelayoutBundle);
    Slog.d("FFApp", "FF11 relayout returned, surfaceControlValid=" + mSurfaceControl.isValid()
            + " frame=" + mTmpFrames.frame);
    ...
    ...
    ...
      if (mSurfaceControl.isValid()) {
         if (!useBLAST()) {
             mSurface.copyFrom(mSurfaceControl);      // 旧架构
         } else {
             updateBlastSurfaceIfNeeded();            // ★ 建 BLASTBufferQueue,现在都走这给mSurface赋值
        }
    Slog.d("FFApp", "FF12 app surface ready (BLAST=true ...)");   // ← FF12
    mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
}

updateBlastSurfaceIfNeeded() 创建 BLASTBufferQueue (App 本地的 buffer 池 + 事务提交器)。RenderThread 以后画完一帧,由它把 buffer 封进 SurfaceControl.Transaction 直接提交 SF------现代显示的提交主干道。

上面所说mSurfaceControl 作为参数(out参数)传递到 system_server ,执行 relayoutWindow 流程,在这个过程中会创建 native 层的 SurfaceControl 也会对应用端传过去的 mSurfaceControl 进行赋值 经过 system_server 处理后的 mSurfaceControl 有值了,再给 mSurface 赋值

调用链

arduino 复制代码
ViewRootImpl::init 
    Surface::init
    SurfaceControl::init 
    ViewRootImpl::relayoutWindow  -- SurfaceControl作为出参
        Session::relayout
            WindowManagerService::relayoutWindow
                WindowManagerService::createSurfaceControl  -- 创建显示的Surface
                    WindowStateAnimator::createSurfaceLocked
                        WindowSurfaceController::init
                            SurfaceControl.Build::init
                            SurfaceControl::init
                                  nativeCreate  -- native层创建Surface
                    WindowSurfaceController::getSurfaceControl
                        SurfaceControl::copyFrom
                            SurfaceControl::assignNativeObject -- 将Surface的指针和句柄传递给应用端
    Surface::updateBlastSurfaceIfNeeded  -- 赋值给应用端的Surface
        BLASTBufferQueue::init
        BLASTBufferQueue::createSurface
            Surface::transferFrom
                    Surface::setNativeObjectLocked

来看看这个updateBlastSurfaceIfNeeded函数,逻辑还是比较清晰的。 之前分析过在 system_server 执行 relayoutWindow 流程创建 SurfaceControl 的时候 native 层也会创建一个 Surface 。这里又看到通过 BLASTBufferQueue::createSurface 创建了一个 Surface ,这里可能会怀疑为什么又要创建一个 Surface ,这里可以先确定一个结论: 这里"创建"的 Surface 其实和之前创建 SurfaceControl 时创建的 Surface 是一个 native 层的 Surface

scss 复制代码
// ViewRootImpl.java
void updateBlastSurfaceIfNeeded() {
        if (!mSurfaceControl.isValid()) {
            return;
        }
​
        if (mBlastBufferQueue != null && mBlastBufferQueue.isSameSurfaceControl(mSurfaceControl)) {
            mBlastBufferQueue.update(mSurfaceControl,
                mSurfaceSize.x, mSurfaceSize.y,
                mWindowAttributes.format);
            return;
        }
​
        // If the SurfaceControl has been updated, destroy and recreate the BBQ to reset the BQ and
        // BBQ states.
        if (mBlastBufferQueue != null) {
            mBlastBufferQueue.destroy();
        }
        mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
                mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
        mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
        Surface blastSurface = mBlastBufferQueue.createSurface();
        // Only call transferFrom if the surface has changed to prevent inc the generation ID and
        // causing EGL resources to be recreated.
        mSurface.transferFrom(blastSurface);
    }

先看看 java 层是如何通过 Surface::transferFrom 给另一个 Surface 赋值的,可以看到 Java 层的 Surface 中定义了一个变量 mNativeObject ,这个变量和 SurfaceControl 是一样都代表着 native 层的指针。 总之,经过这段调用,应用端的 Surface 也持有了对应的 native 层的 Surface 指针

java 复制代码
/**
     * This is intended to be used by {@link SurfaceView#updateWindow} only.
     * @param other access is not thread safe
     * @hide
     * @deprecated
     */
    @Deprecated
    @UnsupportedAppUsage
    public void transferFrom(Surface other) {
        if (other == null) {
            throw new IllegalArgumentException("other must not be null");
        }
        if (other != this) {
            final long newPtr;
            synchronized (other.mLock) {
                newPtr = other.mNativeObject;          // 设置 surface 指针
                other.setNativeObjectLocked(0);
            }
​
            synchronized (mLock) {
                if (mNativeObject != 0) {
                    nativeRelease(mNativeObject);         // 释放之前的
                }
                setNativeObjectLocked(newPtr);            // 设置新的
            }
        }
    }
​
private void setNativeObjectLocked(long ptr) {
        if (mNativeObject != ptr) {
            if (mNativeObject == 0 && ptr != 0) {
                mCloseGuard.open("Surface.release");
            } else if (mNativeObject != 0 && ptr == 0) {
                mCloseGuard.close();
            }
            mNativeObject = ptr;                        // 设置 native 指针
            mGenerationId += 1;
            if (mHwuiContext != null) {
                mHwuiContext.updateSurface();
            }
        }
    }

BLAST Buffer Queue机制完全填补了Buffer Queue机制的缺点:

  1. BufferQueue的创建放在了业务进程,surfaceflinger中只接受Buffer,同时所有图层属性的提交都通过Transaction进行,大大减轻了surfaceflinger的负担;

  2. 支持Transaction的合并操作,多个Transaction可以合并为一个提交给surfaceflinger进程,从而可以保证Buffer的提交和几何属性的提交都可在同一帧完成;

  3. 支持多进程间的协同能力(BLAST SyncEngine),可以对Transaction跨进程传递,携带不同进程的Buffer一起提交给surfaceflinger,实现进程间的帧同步。

3.6 绘制三部曲:录制,而不是画

makefile 复制代码
07:56:44.112  5689  5689 D FFApp: FF08 scheduleTraversals (post TRAVERSAL callback)   ← 第二次!
07:56:44.112  5689  5689 D FFApp: FF16 scheduleVsync
(等一个 vsync......FF17)
07:56:44.158  5689  5689 D FFApp: FF13 draw enter, surface valid
07:56:44.158  5689  5689 D FFApp: FF14 hardware draw path begin
07:56:44.160  5689  5689 D FFApp: FF18 ThreadedRenderer.draw enter
07:56:44.164  5689  5689 D FFApp: FF19 display list built (RenderNode tree ready)
07:56:44.186  5689  5689 D FFApp: FF20 syncAndDrawFrame done syncResult=0 (frame submitted to RenderThread)

为什么 FF08 出现第二次? 第一轮 performTraversals 拿回 Surface 后没直接画------窗口配置变了,需要再来一轮完整遍历,于是又挂任务、又等 vsync。这揭示了一条铁律:每一轮遍历都严格对齐 vsync,绝不自作主张插帧。 从"拿到画布"到"开始画"隔一个周期,是机制使然不是卡顿。

FF13/FF14 的分岔(ViewRootImpl.java:4990):

这里是上面的 performTraversals() ViewRootImpl.java:3093 --> FF10 -FF12 ├─ performMeasure() ... ├─ performLayout() ... └─ performDraw() ViewRootImpl.java:4825 └─ draw(fullRedrawNeeded, ...) ViewRootImpl.java:4990 → FF13 ├─ FF13 打印(surface.isValid() 之后) :4996 ├─ FF14 打印(isHardwareEnabled() 分支) :5091 └─ mAttachInfo.mThreadedRenderer.draw() → FF18

java 复制代码
// ViewRootImpl.java:4990
private boolean draw(boolean fullRedrawNeeded, ...) {
    Surface surface = mSurface;
    if (!surface.isValid()) return false;
    Slog.d("FFApp", "FF13 draw enter, surface valid");
    ...
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {  // ①有脏区才画
        if (isHardwareEnabled()) {                                     // ②硬件加速
            Slog.d("FFApp", "FF14 hardware draw path begin");
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);   // 调用 FF18
        } else {
            drawSoftware(...);      // 软件分支
        }
    }

彩蛋:本例后续 44.353 有一条 FF13 但没有 FF14------那轮脏区为空,draw() 直接返回。App 不是每个 vsync 都画,有变化才画。

不要问为什么从FF14 直接打印了FF18,因为FF15是由RenderThread的帧完成回调触发(在下面),FF16,FF17是VSYNC信号申请,收到

FF18~20(ThreadedRenderer.java:795):

arduino 复制代码
// ThreadedRenderer.java:795
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    Slog.d("FFApp", "FF18 ThreadedRenderer.draw enter");
    updateRootDisplayList(view, callbacks);      // ①
    Slog.d("FFApp", "FF19 display list built (RenderNode tree ready)");
    int syncResult = syncAndDrawFrame(frameInfo); // ②
    Slog.d("FFApp", "FF20 syncAndDrawFrame done ...");

① 内部从 DecorView 开始递归:

ini 复制代码
RecordingCanvas canvas = mRootNode.beginRecording(...);
canvas.drawRenderNode(view.updateDisplayListIfDirty());   // 递归每个View
mRootNode.endRecording();

每个 View 持有一个 RenderNode。updateDisplayListIfDirty() 里执行你写的 draw(canvas)------此刻不产生任何像素drawRect/drawText 只被"录制"成 RenderNode 上的操作;子 View 的 RenderNode 作为 DrawRenderNodeOp 挂到父节点。递归完成,DecorView 的 RenderNode 就是记录整个 UI 的操作树(display list) 。为什么录制不直画?主线程不碰 GPU、display list 可缓存复用、动画帧可重放。

syncAndDrawFrame → native DrawFrameTask::postAndWait()

scss 复制代码
mRenderThread->queue().post([this]() { run(); });   // 任务投给RenderThread
mSignal.wait(mLock);                                 // UI线程只等"同步",不等渲染

UI 线程等的是 RenderNode 树所有权移交、Surface 绑定这些同步动作 ,做完就返回(FF20)。从此刻起,本帧与 UI 线程无关。

RenderThread 侧异步执行(首帧还夹着 EGL 初始化、buffer 分配等一次性成本,日志里 App 进程同期出现的 OpenGLRenderer: Unable to match the desired swap behavior 就是 eglCreateWindowSurface 的痕迹):

scss 复制代码
// CanvasContext::draw()
Frame frame = mRenderPipeline->getFrame();     // ① 向BLASTBufferQueue dequeue一块GraphicBuffer
mRenderPipeline->draw(mRenderNodes, ...);      // ② 重放display list → Skia → GL命令 → GPU
mRenderPipeline->swapBuffers(frame);           // ③ buffer+GPU fence 封进Transaction提交SF

3.7 首帧完成上报

ini 复制代码
07:56:44.258  5689  5689 D FFApp: FF15 reportDrawFinished seqId=0 (first frame drawn & reported to WMS)
07:56:44.263  1215  1590 D FFWms: FF34 finishDrawingWindow win=8ba7de3 ...MainActivity
less 复制代码
// ViewRootImpl.java:4718 ------ 由RenderThread的帧完成回调触发,不是主线程
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
    Slog.d("FFApp", "FF15 reportDrawFinished seqId=" + seqId);
    mWindowSession.finishDrawing(mWindow, t, seqId);    // binder → WMS
less 复制代码
void finishDrawingWindow(Session session, IWindow client,
            @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
        if (postDrawTransaction != null) {
            postDrawTransaction.sanitize(Binder.getCallingPid(), Binder.getCallingUid());
        }
​
        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                WindowState win = windowForClientLocked(session, client, false);
                android.util.Slog.d("FFWms", "FF34 finishDrawingWindow win="               //FF34
                        + (win != null ? win.getName() : "null"));
                ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
                        win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                if (win != null && win.finishDrawing(postDrawTransaction, seqId)) {
                    if (win.hasWallpaper()) {
                        win.getDisplayContent().pendingLayoutChanges |=
                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                    }
                    win.setDisplayLayoutNeeded();
                    mWindowPlacerLocked.requestTraversal();
                }
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }
​

WMS 侧(WindowManagerService.java:2654):win.finishDrawing() 把窗口状态推进到 READY_TO_SHOW ,随后 requestTraversal() 触发 WMS 的布局遍历------决定何时真正 show 这个窗口、何时移除 Splash、何时播过渡动画。

seqId 值得单独一讲:它对应 WMS 的 BLASTSyncGroup 。当 WMS 要编排多窗口同步动画(本例是分屏切换)时,给窗口发 seqId,App 提交的事务在 WMS 侧攒着不 apply,等组里所有窗口都画完首帧再统一放行 。本例 seqId=0 = 没有同步组,buffer 到了直接上屏。对照组实验中同样场景出现过 seqId=1:首帧画好后被扣了 341ms 才 latch------不是渲染慢,是在等分屏对侧的 App 画完一起进场。"首帧延迟"的大头往往不在渲染,而在 transition 编排。

3.8 SF 合成上屏:最后一棒

arduino 复制代码
07:56:44.262   308   308 I FFSF: FF42 commit (vsync-sf wakeup, latch+rebuild)
07:56:44.263   308   308 I FFSF: FF43 latched buffer, layer='com.hmi.bev.carapp/.MainActivity#135'
07:56:44.264   308   308 I FFSF: FF44 composite (HWC/GPU compose + present)

App 的 BLAST transaction 到达 SF 后并不立即处理------SF 标记 layer 有新数据,等自己的 vsync(vsync-sf 相位,前置知识 1.3 的第二路)。主循环每周期两拍:

第一拍 commit(SurfaceFlinger.cpp:2338):

scss 复制代码
bool SurfaceFlinger::commit(...) {
    ALOGI("FFSF FF42 commit ...");                    // ← FF42
    bool mustComposite = latchBuffers() || shouldCommit;

latchBuffers()(:4074)遍历 layer 树,对有新帧的 layer 调 latchBuffer()------latch = acquire 那块刚渲染好的 buffer(先等 GPU fence 释放),提升为 layer 的当前帧

less 复制代码
// SurfaceFlinger.cpp:4134
if (layer->latchBuffer(visibleRegions, latchTime)) {
    ALOGI("FFSF FF43 latched buffer, layer='%s'", layer->getDebugName());  // ← FF43

FF43 打出 #135你的首帧正式成为 SF 的合成输入。 同刻 latch 的还有 Splash #117 等兄弟 layer。

第二拍 composite(SurfaceFlinger.cpp:2493):

scss 复制代码
CompositeResultsPerDisplay SurfaceFlinger::composite(...) {
    ALOGI("FFSF FF44 composite ...");                 // ← FF44
    mCompositionEngine->present(refreshArgs);          // ①

① 内部三步(前置知识 1.6 的落地):

  1. rebuildLayerStacks:按 Z 序遍历,算每层可见区域
  2. 问 HWC:逐 layer "你能 overlay 直接叠吗?"→ 回报 DEVICE(硬件)或 CLIENT(GPU 合成)
  3. presentDisplay :CLIENT 层用 SF 自带 RenderEngine 合成进 framebuffer;最终 layer→plane 绑定提交 HWC → 驱动在下一个 VBI page flip → 面板逐行扫出新帧

下一个扫描周期,你的首帧物理上成为屏幕上的光。 从 FF01 到这里,395ms(首帧有大量一次性初始化;稳态帧只有十几毫秒)。

3.9 尾声:首帧"已显示"≠"可见"

日志继续往后还有一课:

arduino 复制代码
44.263  FF43 latch #135        ← 首帧已合成(被Splash盖着)
44.313  FF43 latch #135        ← 第二帧(App 连续绘制)
45.115  FFSF: createLayer '...animation-leash of window_anim'   ← Splash退场动画开始
45.200  FF43 latch #135        ← 第三帧
~45.3   Splash 移除 → 用户完整看到 App

Splash(AMS 为冷启动预绘的窗口)要等 App 首帧 READY 后才开始退场动画。所以"首帧已上屏"和"用户看见内容"之间还隔着一段动画------这是 UX 设计,不是性能问题。看日志要区分"layer 有内容"和"layer 可见"两件事。


四、全景总结

一张图收束全文(编号即日志点位,可当流程检查单用):

scss 复制代码
【App·主线程】                【system_server】            【SurfaceFlinger】
FF01 handleResumeActivity
FF02 (onResume完)
FF03 wm.addView
FF04 new ViewRootImpl ──(构造时注册vsync Connection)──→ EventThread(app)
FF05 setView
FF08 scheduleTraversals(任务挂队)
FF06 addToDisplay ──binder──→ FF30 addWindow(new WindowState)
                              └─容器层──────────→ FF40/41 createLayer #130
FF07 返回
   ⏳ FF16请求 → 硬件vsync → FF17到达 → FF09 doTraversal
   (performMeasure / performLayout)
FF10 relayoutWindow ──binder──→ FF31 relayoutWindow
                              FF32 createSurfaceControl ─binder─→ FF40/41 createLayer #135 ★
                              FF33 OK
FF11 SurfaceControl到手
FF12 BLASTBufferQueue就绪
   ⏳ 再等一个vsync(FF16→FF17)
FF13/FF14 draw
FF18 ThreadedRenderer.draw
FF19 录制display list(不产生像素!)
FF20 移交RenderThread(主线程下班)
   【App·RenderThread】dequeue buffer → Skia→GL→GPU渲染 → Transaction提交
FF15 reportDrawFinished ──binder──→ FF34 finishDrawingWindow(READY_TO_SHOW)
                                                            ⏳ 等vsync-sf相位
                                                            FF42 commit
                                                            FF43 latch #135 ★
                                                            FF44 composite→HWC→page flip
                                                               ↓ 下一扫描周期
                                                            像素上屏 👁

每个角色的一句话定位

  • ViewRootImpl ------ 窗口的显示总管家
  • Choreographer ------ 节拍器,vsync 的请求与消费
  • WMS ------ 户籍科 + 导演:登记窗口、发画布、编排动画(BLASTSyncGroup 是它的片场闸门)
  • Layer/SurfaceFlinger ------ 像素的容器与合成师
  • HWC ------ 最后一级投影仪,兼发 vsync 的钟表匠

三条最反直觉、也最值钱的结论

  1. onResume 时窗口还什么都没有------Surface 要到首次 performTraversals 的 relayout 才诞生;
  2. draw() 不画像素------UI 线程只录 display list,像素由 RenderThread+GPU 异步产出,靠 fence 做交接;
  3. 首帧画好 ≠ 立刻上屏------中间还隔着一个 vsync-sf 相位、(可能有)BLASTSyncGroup 的统一放行、(通常有)启动动画的退场。

五、动手建议

想继续深挖,顺着这套"埋点→读日志"的方法可以钻的方向:

  1. fence 机制 :在 Layer::latchBuffer 里打 fence 状态,观察 GPU 完成时刻与 SF 消费时刻
  2. HWC 决策adb shell dumpsys SurfaceFlingercomposition type 字段,验证哪些 layer 走了 overlay
  3. 掉帧定位adb shell dumpsys SurfaceFlinger --latency <layer> 拿 vsync/desired/present 三个时间戳
  4. transition 编排 :给 WMS 的 BLASTSyncEngine 埋日志,量化"等待队友"的耗时
  5. 稳态帧:给动画中的 App 抓 FF16/FF17/FF09 循环,对照 Choreographer 的 FrameInfo 理解每帧节奏

源码比任何博客都诚实。给系统加上自己的日志,让它亲口告诉你发生了什么------这是学显示体系(乃至任何复杂系统)最快的路。

相关推荐
CircleMouse19 分钟前
画一个Android智能手机机器人
android·智能手机·机器人
l1t1 小时前
DeepSeek总结的DuckDB 如何更快地运行递归 CTE
java·开发语言·数据库·mysql·duckdb
小手cool1 小时前
使用递归对数组进行反转操作
java·数据结构·算法
MetaLite1 小时前
SpringBoot分页接口怎么设计-pageSize不设上限会发生什么
java·spring boot·后端
晚安code1 小时前
LangChain4j AiService 实战:会话记忆与结构化输出
java·langchain
数据知道1 小时前
PHP 代码审计实战——ThinkPHP、Laravel 历史漏洞模式深度剖析
android·网络·web安全·网络安全·php·laravel
晚安code1 小时前
LangChain4j 实战:RAG、工具调用、护轨与 SSE 流式输出
java·langchain
郑州光合科技余经理2 小时前
本地生活平台搭建:统一订单表与多后台切换怎么拆
java·开发语言·前端·系统架构·uni-app·php·ai编程
拍客圈2 小时前
换服务器 mozcjpeg 5.0.0
android