Android 15 显示子系统深度解析(三):Vsync机制与显示性能优化实战

307 阅读19分钟

引言

在前两篇文章中,我们深入分析了SurfaceFlinger的核心机制和图形缓冲区管理。本文将继续探讨Android显示系统中最关键的同步机制——Vsync(垂直同步),以及如何基于Vsync构建流畅的用户体验。

Vsync是Android显示系统的"心跳",它像一个精准的节拍器,协调着应用绘制、系统合成、屏幕显示三个环节的完美配合。理解Vsync机制,是掌握Android性能优化的关键。

本文将基于Android 15 (AOSP)实际源码,深入分析以下核心内容:

  • Vsync信号的产生与分发机制
  • Choreographer如何协调应用侧渲染
  • 三重缓冲如何减少掉帧
  • 使用Systrace/Perfetto诊断性能问题的完整实战

系列导航: 这是"Android 15 核心子系统深度解析"系列的第3篇文章。建议先阅读前两篇了解显示系统基础。

一、Vsync垂直同步机制

1.1 Vsync的作用

在传统的CRT显示器时代,电子枪从屏幕左上角开始,逐行扫描到右下角,完成一帧画面的显示。当电子枪回到起点准备下一帧时,会发出一个"垂直同步信号"(Vsync),告诉系统"现在可以安全地更新帧缓冲了"。

虽然现代LCD/OLED显示器没有电子枪,但Vsync的概念被完整保留下来,成为同步渲染管线的关键机制。

Vsync解决的核心问题:

  1. 画面撕裂(Screen Tearing)

    • 问题:GPU渲染速度快于Display刷新,导致一帧画面显示了上下两个不同的内容
    • 解决:Vsync确保GPU渲染和Display刷新同步进行
  2. 资源浪费

    • 问题:GPU盲目高速渲染,浪费CPU/GPU资源和电量
    • 解决:Vsync让渲染按需进行,每个Vsync周期最多渲染一帧
  3. 流程协调

    • 问题:应用绘制、SurfaceFlinger合成、Display显示三个环节需要精确配合
    • 解决:Vsync作为全局时钟,协调三者的工作节奏

1.2 Vsync信号的产生

Android 15的Vsync信号有两个来源:硬件Vsync和软件Vsync。

硬件Vsync

03-01-vsync-generation-distribution.png

硬件Vsync的产生路径:

Display Driver → HWC (IComposerCallback::onVsync) →
SurfaceFlinger → VSyncReactor → VsyncSchedule

硬件Vsync是Display Driver在每次完成一帧扫描后发出的真实信号,通过HWC 3.0的回调接口上报给SurfaceFlinger。

让我们看看HWC回调的源码(位于hardware/interfaces/graphics/composer/aidl/android/hardware/graphics/composer3/IComposerCallback.aidl):

interface IComposerCallback {
    /**
     * 硬件Vsync回调
     * @param display 发生Vsync的显示设备ID
     * @param timestamp Vsync时间戳(纳秒)
     * @param vsyncPeriodNanos Vsync周期(纳秒)
     */
    oneway void onVsync(long display, long timestamp, int vsyncPeriodNanos);

    /**
     * 热插拔回调
     */
    oneway void onHotplug(long display, boolean connected);

    /**
     * 刷新率改变回调
     */
    oneway void onRefresh(long display);
}

软件Vsync

硬件Vsync虽然准确,但频繁的中断会带来性能开销。Android采用了一种聪明的优化策略:

  1. 初始阶段: 使用硬件Vsync采样,建立Vsync模型
  2. 稳定阶段: 关闭硬件Vsync,使用VSyncPredictor基于历史数据预测下一次Vsync
  3. 校准阶段: 定期使用硬件Vsync校准预测模型

这种混合策略兼顾了准确性和性能。

VSyncPredictor的核心逻辑(源码位于frameworks/native/services/surfaceflinger/Scheduler/VSyncPredictor.cpp):

class VSyncPredictor : public VsyncTracker {
    // 历史Vsync时间戳队列
    std::deque<nsecs_t> mTimestamps;

    // 预测下一次Vsync时间
    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) {
        // 基于历史数据计算Vsync周期
        const auto period = currentPeriod();

        // 找到timePoint之后最近的Vsync时刻
        return timePoint + period - (timePoint % period);
    }

    // 添加硬件Vsync采样,校准模型
    void addVsyncTimestamp(nsecs_t timestamp) {
        mTimestamps.push_back(timestamp);

        // 保留最近N个样本(通常20个)
        if (mTimestamps.size() > mHistorySize) {
            mTimestamps.pop_front();
        }

        // 重新计算周期(去除异常值)
        recalculatePeriod();
    }
};

1.3 Vsync分发机制

SurfaceFlinger收到Vsync信号后,需要分发给两类消费者:应用进程和SurfaceFlinger自己。但这两者的需求不同:

  • 应用进程: 需要提前收到Vsync,以便有足够时间绘制(通常提前2ms)
  • SurfaceFlinger: 准时收到Vsync,立即开始合成

这就是Vsync Phase Offset(相位偏移)机制。

EventThread: App-Vsync vs SF-Vsync

Android维护了两个EventThread:

// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
    // 创建app-vsync的EventThread,带-2ms offset
    mAppConnectionHandle = mScheduler->createConnection(
            "app", mFrameTimeline->getTokenManager(),
            /* workDuration */ configs.late.appWorkDuration,
            /* readyDuration */ configs.late.sfWorkDuration);

    // 创建sf-vsync的EventThread,0ms offset
    mSfConnectionHandle = mScheduler->createConnection(
            "sf", mFrameTimeline->getTokenManager(),
            /* workDuration */ configs.late.sfWorkDuration,
            /* readyDuration */ Duration::fromNs(0));
}

时序关系:

真实Vsync时刻: T0App-Vsync触发: T0 - 2ms (提前触发)
  ↓ (应用有2ms时间开始绘制)
真实Vsync到达: T0SF-Vsync触发: T0 (准时触发)
  ↓
SurfaceFlinger开始合成

这个2ms的提前量是可配置的,取决于设备性能和屏幕刷新率。

EventThread源码分析

EventThread负责接收Vsync信号并分发给注册的消费者(源码位于frameworks/native/services/surfaceflinger/Scheduler/EventThread.h):

class EventThread {
public:
    // 创建EventConnection供应用连接
    virtual sp<EventThreadConnection> createEventConnection(
            EventRegistrationFlags eventRegistration = {}) const = 0;

    // Vsync回调,由VsyncSchedule触发
    void onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime);

private:
    // 线程主循环
    void threadMain() {
        while (true) {
            // 等待Vsync信号
            waitForVsyncSignal();

            // 分发给所有注册的Connection
            dispatchVsyncEvent();
        }
    }

    // 已注册的连接列表
    std::vector<sp<EventThreadConnection>> mConnections;
};

应用通过DisplayEventReceiver连接到EventThread:

// 应用侧代码(Choreographer内部)
mDisplayEventReceiver = new DisplayEventReceiver(
        Looper.myLooper(), vsyncSource, configChanged);

关键要点:

  • 硬件Vsync准确但开销大,软件Vsync预测效率高
  • Phase Offset让应用提前收到Vsync,有更多时间绘制
  • EventThread是Vsync分发的中枢,维护所有消费者连接

1.4 Android 15的Vsync优化

Android 15在Vsync机制上进行了多项优化:

1. 动态刷新率支持(Variable Refresh Rate)

Android 15支持根据内容动态调整刷新率:

  • 静态内容: 降低到30Hz或更低,节省电量
  • 动画/视频: 提升到90Hz/120Hz,保证流畅
  • 游戏: 匹配游戏帧率,减少judder

2. Vsync预测算法改进

引入机器学习模型,基于历史数据更准确地预测Vsync:

  • 考虑温度变化对Display刷新率的影响
  • 自适应调整预测窗口大小
  • 更快地检测和适应刷新率变化

3. VRR配置支持

新增VrrConfig配置,允许HAL层精确控制刷新率:

struct VrrConfig {
    // 通知SurfaceFlinger下一帧的期望present时间
    int notifyExpectedPresentTime;
    // 最小帧间隔(纳秒)
    int minFrameInterval;
}

二、Choreographer与应用侧渲染

2.1 Choreographer工作原理

Choreographer(编舞者)是Android在应用侧与Vsync交互的桥梁,它协调输入、动画、绘制的执行时机,确保它们都在正确的Vsync边界上发生。

03-02-choreographer-workflow.png

Choreographer的初始化

Choreographer采用线程本地单例模式,每个Looper线程都有自己的Choreographer:

// frameworks/base/core/java/android/view/Choreographer.java
public final class Choreographer {
    // ThreadLocal存储,每个线程一个实例
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            return new Choreographer(looper, VSYNC_SOURCE_APP);
        }
    };

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);

        // 创建DisplayEventReceiver接收Vsync
        mDisplayEventReceiver = new FrameDisplayEventReceiver(looper, vsyncSource);
    }
}

2.2 VSYNC → doFrame → draw的链路

让我们跟踪一个完整的帧是如何从Vsync信号到达屏幕的:

步骤1: Vsync到达

private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
    @Override
    public void onVsync(long timestampNanos, long physicalDisplayId,
                       int frame, VsyncEventData vsyncEventData) {
        // Vsync回调在Display线程,需要切换到主线程
        Message msg = Message.obtain(mHandler, MSG_DO_FRAME);
        msg.setAsynchronous(true);  // 异步消息,优先处理
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
}

步骤2: doFrame执行

void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) {
    // 检查掉帧
    final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
    if (jitterNanos >= mFrameIntervalNanos) {
        final long skippedFrames = jitterNanos / mFrameIntervalNanos;
        if (skippedFrames > SKIPPED_FRAME_WARNING_LIMIT) {
            Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                    + "The application may be doing too much work on its main thread.");
        }
    }

    mLastFrameTimeNanos = frameTimeNanos;

    // 依次执行4种类型的Callback
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);     // 输入事件
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); // 动画
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); // 遍历(绘制)
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);    // 提交
}

步骤3: View遍历

// CALLBACK_TRAVERSAL会触发ViewRootImpl的performTraversals
void performTraversals() {
    // 测量
    if (mFirst || windowShouldResize) {
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    // 布局
    if (didLayout) {
        performLayout(lp, mWidth, mHeight);
    }

    // 绘制
    if (!cancelDraw) {
        performDraw();
    }
}

步骤4: 硬件加速绘制

private void performDraw() {
    if (mAttachInfo.mThreadedRenderer != null) {
        // 使用RenderThread进行硬件加速绘制
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        // 软件绘制(很少使用)
        draw(fullRedrawNeeded);
    }
}

ThreadedRenderer(RenderThread)是Android的GPU渲染线程,它与UI线程并行工作:

  • UI Thread: 构建DisplayList(绘制指令列表)
  • RenderThread: 执行DisplayList,调用OpenGL/Vulkan进行GPU渲染

2.3 帧率控制机制

Choreographer提供了灵活的帧率控制:

// 请求下一次Vsync回调
public void postFrameCallback(FrameCallback callback) {
    postFrameCallbackDelayed(callback, 0);
}

// 延迟N毫秒后请求Vsync回调
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

// 设置Vsync速率(降频)
// rate=2表示每2个Vsync执行一次(30fps on 60Hz display)
public void setVsyncRate(int rate) {
    mFPSDivisor = rate;
}

2.4 掉帧检测与回调

Choreographer内置了掉帧检测机制:

void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) {
    // 计算距离上一帧的时间差
    final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;

    // 如果超过一个Vsync周期,说明掉帧了
    if (jitterNanos >= mFrameIntervalNanos) {
        final long skippedFrames = jitterNanos / mFrameIntervalNanos;

        // 超过阈值(默认30帧),输出警告日志
        if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
            Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                    + "The application may be doing too much work on its main thread.");
        }

        // 记录到FrameMetrics,供性能分析
        mFrameMetrics.addJank(skippedFrames);
    }
}

应用可以通过Window.addOnFrameMetricsAvailableListener()监听掉帧事件:

window.addOnFrameMetricsAvailableListener(
    new Window.OnFrameMetricsAvailableListener() {
        @Override
        public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
            // frameMetrics包含详细的帧耗时数据
            long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
            if (totalDuration > 16_000_000) {  // 16ms in nanoseconds
                Log.w(TAG, "Slow frame detected: " + totalDuration / 1_000_000 + "ms");
            }
        }
    },
    new Handler(Looper.getMainLooper())
);

性能优化提示:

  • Choreographer的回调执行在UI线程,必须快速完成(< 16ms)
  • 耗时操作应放到后台线程或RenderThread
  • 使用postFrameCallback可以精确控制动画执行时机

三、三重缓冲与帧率优化

3.1 双缓冲的局限性

在理解三重缓冲之前,先看看双缓冲为什么不够用。

双缓冲模型:

  • Buffer A: 当前Display正在扫描显示
  • Buffer B: App/GPU正在绘制

问题场景:

假设App绘制一帧需要15ms(接近16ms的限制):

Vsync 0 (T0):
  - Display: 显示Buffer A
  - App: 绘制Buffer B (耗时15ms)

Vsync 1 (T16ms):
  - Display: 显示Buffer B (刚完成的)
  - App: 想绘制Buffer A,但A还在被Display使用 ❌
  - 结果: App阻塞等待,这一帧掉了

Vsync 2 (T32ms):
  - Display: 还在显示Buffer B (因为没有新内容)
  - App: Buffer A被释放,开始绘制

问题根源: 只有2个Buffer,但需要3个角色同时使用:

  1. Display正在显示一个
  2. SurfaceFlinger正在合成一个
  3. App想要绘制一个

03-03-double-vs-triple-buffering.png

3.2 三重缓冲的引入

三重缓冲增加了第三个Buffer,解决了上述问题:

三重缓冲模型:

  • Buffer A: Display正在显示
  • Buffer B: SurfaceFlinger正在合成
  • Buffer C: App可以自由绘制

优化后的时序:

Vsync 0 (T0):
  - Display: 显示Buffer A
  - SurfaceFlinger: 合成Buffer B
  - App: 绘制Buffer C ✓

Vsync 1 (T16ms):
  - Display: 显示Buffer B
  - SurfaceFlinger: 合成Buffer C
  - App: 绘制Buffer A ✓ (已被Display释放)

Vsync 2 (T32ms):
  - Display: 显示Buffer C
  - SurfaceFlinger: 合成Buffer A
  - App: 绘制Buffer B ✓

优势:

  • ✓ App始终有可用Buffer,无需等待
  • ✓ 100%的Vsync利用率,流畅度大幅提升
  • ✓ 即使偶尔超过16ms,也能快速恢复

代价:

  • ⚠ 内存占用增加33% (3个Buffer vs 2个)
  • ⚠ 延迟增加1帧 (从输入到显示的延迟)

3.3 BufferQueue的maxAcquiredBufferCount

三重缓冲通过配置BufferQueue的maxAcquiredBufferCount实现:

// frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers) {
    // maxDequeuedBuffers = 2: 双缓冲 (App最多持有2个Buffer)
    // maxDequeuedBuffers = 3: 三重缓冲 (App最多持有3个Buffer)

    if (maxDequeuedBuffers < 1 || maxDequeuedBuffers > BufferQueueDefs::NUM_BUFFER_SLOTS - 1) {
        return BAD_VALUE;
    }

    std::lock_guard<std::mutex> lock(mCore->mMutex);
    mCore->mMaxDequeuedBufferCount = maxDequeuedBuffers;
    return NO_ERROR;
}

默认配置 (Android 15):

// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::setUpHWComposer() {
    // 默认三重缓冲
    mMaxAcquiredBufferCount = 3;
}

内存占用权衡:

  • 1080p RGBA_8888 Buffer: 约8MB
  • 三重缓冲额外开销: 8MB
  • 对于现代手机(8GB+ RAM),这个开销可以接受

3.4 Android 15的BufferQueue优化

Android 15进一步优化了BufferQueue:

1. 动态缓冲区数量调整

根据场景动态调整Buffer数量:

  • 静态内容: 使用2个Buffer,节省内存
  • 动画/滑动: 使用3个Buffer,保证流畅
  • 重度游戏: 可配置4个Buffer (通过setMaxDequeuedBufferCount(4))

2. 更智能的Buffer释放策略

// 当Buffer在队列中停留过久,主动释放
void BufferQueueCore::releaseOldBuffers() {
    const nsecs_t now = systemTime();
    for (auto& slot : mSlots) {
        if (slot.mBufferState == BufferSlot::ACQUIRED &&
            now - slot.mAcquiredTime > kMaxAcquiredBufferAge) {
            // 超过阈值(如500ms),主动释放
            releaseBuffer(slot);
        }
    }
}

3. 帧率感知的Buffer管理

VRR场景下,根据当前帧率动态调整Buffer超时时间:

  • 60Hz: 超过33ms未使用的Buffer释放
  • 30Hz: 超过66ms未使用的Buffer释放

四、显示性能问题诊断实战

4.1 常见性能问题类型

在实际开发中,显示性能问题主要分为三大类:

1. UI Thread耗时

  • 症状: 主线程执行时间过长,导致无法及时响应Vsync
  • 原因:
    • 过度绘制(Overdraw)
    • 布局层级过深
    • 主线程执行耗时操作(网络/IO/复杂计算)

2. RenderThread耗时

  • 症状: GPU渲染速度跟不上
  • 原因:
    • 复杂Shader计算
    • 大量纹理上传
    • Alpha混合过多

3. SurfaceFlinger耗时

  • 症状: 系统合成延迟
  • 原因:
    • Layer数量过多(> 30个)
    • 硬件Overlay资源不足,大量CLIENT合成
    • HWC合成策略不佳

4.2 工具链使用

Systrace分析Vsync/SurfaceFlinger/RenderThread

Systrace是Android最强大的性能分析工具,可以完整展示渲染管线的每个环节。

抓取trace:

# 抓取5秒的trace,关注graphics/view/sched/freq
python systrace.py gfx view sched freq -b 32768 -t 5 -o trace.html

# 参数说明:
# gfx: Graphics相关事件(SurfaceFlinger/HWC/BufferQueue)
# view: View系统事件(measure/layout/draw)
# sched: 线程调度事件
# freq: CPU频率变化
# -b 32768: buffer大小32MB,避免数据丢失
# -t 5: 抓取5秒

分析关键Track:

  1. surfaceflinger Track

    • 查看onMessageReceived: 合成循环触发
    • 查看handleMessageRefresh: 实际合成耗时
    • 目标: < 2ms
  2. UI Thread Track (应用主线程)

    • 查看Choreographer#doFrame: 是否及时响应Vsync
    • 查看performTraversals: measure/layout/draw耗时
    • 查看draw: 是否有慢速View
    • 目标: < 10ms
  3. RenderThread Track

    • 查看syncFrameState: DisplayList同步
    • 查看flush drawing commands: GPU命令提交
    • 目标: < 4ms

典型问题pattern:

Pattern 1: UI Thread阻塞
------------------------------------------------------------
UI Thread:  |=======doFrame(20ms)========|   超过16ms
RenderThread:         [idle]
SurfaceFlinger:       [idle]
 问题: UI Thread耗时过长,检查是否有耗时View或主线程IO

Pattern 2: RenderThread慢
------------------------------------------------------------
UI Thread:  |==doFrame(8ms)==|
RenderThread:        |========flush(15ms)=========|  
SurfaceFlinger:             [waiting]
 问题: GPU渲染慢,检查Overdraw和Shader复杂度

Pattern 3: 掉帧(Jank)
------------------------------------------------------------
Vsync 0:  App绘制
Vsync 1:  App绘制完成  SF合成  Display
Vsync 2:  [没有新内容,Display重复上一帧]   Jank
 问题: App超过1个Vsync周期才完成绘制

GPU呈现模式分析

GPU呈现模式是快速定位问题的利器:

# 开启GPU呈现条形图
adb shell setprop debug.hwui.profile true

# 或者通过设置界面
设置 → 开发者选项 → GPU呈现模式分析 → 在屏幕上显示为条形图

条形图解读:

每根柱子代表一帧,从下到上分为多段,颜色含义:

  • 绿色(swap buffers): Buffer swap时间
  • 蓝色(command issue): GPU命令提交
  • 红色(sync & upload): 纹理上传和同步
  • 橙色(measure/layout): 测量和布局
  • 蓝色(animation): 动画计算
  • 红色(input handling): 输入事件处理
  • 橙色(misc/vsync delay): 其他杂项

判断标准:

  • 绿色横线: 16ms阈值(60fps)
  • 柱子超过绿线: 掉帧
  • 柱子过高: 严重性能问题

dumpsys gfxinfo

# 查看指定应用的帧率统计
adb shell dumpsys gfxinfo <package_name>

# 查看最近120帧的详细数据
adb shell dumpsys gfxinfo <package_name> framestats

关键指标:

Stats since: 512345678ns
Total frames rendered: 1543
Janky frames: 89 (5.77%)
90th percentile: 14ms
95th percentile: 18ms
99th percentile: 26ms

Number Missed Vsync: 23
Number High input latency: 5
Number Slow UI thread: 34
Number Slow bitmap uploads: 8
Number Slow draw: 12

指标含义:

  • Janky frames: 掉帧数量和比例,目标< 1%
  • 90th/95th/99th percentile: 帧时间分位数,90th < 16ms为优秀
  • Missed Vsync: 未能及时响应Vsync的次数
  • Slow UI thread: UI线程超时次数

4.3 案例1: 丢帧问题定位与优化

问题现象: 用户反馈某RecyclerView滑动时明显卡顿。

步骤1: GPU呈现模式快速检查

开启GPU呈现条形图,滑动列表,观察到:

  • 很多柱子超过绿线(16ms)
  • 橙色部分(measure/layout)特别长

步骤2: Systrace深度分析

python systrace.py gfx view -b 32768 -t 5 -o trace.html

打开trace.html,找到UI Thread的doFrame:

UI Thread:
  Choreographer#doFrame                [16.2ms]
    ├─ CALLBACK_INPUT                  [0.3ms]
    ├─ CALLBACK_ANIMATION              [0.5ms]
    └─ CALLBACK_TRAVERSAL              [15.4ms]  ❌ 超时
        └─ ViewRootImpl.performTraversals
            ├─ performMeasure          [12.1ms]  ❌ 慢
            │   └─ RecyclerView.measure
            │       └─ LinearLayoutManager.onMeasure
            │           └─ 遍历所有child,每个child 0.5ms × 25个 = 12.5ms
            ├─ performLayout           [2.8ms]
            └─ performDraw             [0.5ms]

根本原因: RecyclerView的每个item布局层级过深,导致measure耗时过长。

优化方案:

  1. 使用ConstraintLayout扁平化item布局:
<!-- 优化前: 3层嵌套 -->
<LinearLayout>
    <RelativeLayout>
        <LinearLayout>
            <ImageView/>
            <TextView/>
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

<!-- 优化后: 1层ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
    <ImageView app:layout_constraintTop_toTopOf="parent"/>
    <TextView app:layout_constraintTop_toBottomOf="@id/image"/>
</androidx.constraintlayout.widget.ConstraintLayout>
  1. 避免在onBindViewHolder中执行复杂计算:
// 优化前
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Data data = items.get(position);
    holder.textView.setText(formatData(data));  // 每次bind都计算
}

// 优化后
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Data data = items.get(position);
    holder.textView.setText(data.getFormattedText());  // 预计算缓存
}

优化效果:

  • measure耗时: 12.1ms → 3.2ms
  • 总帧时间: 16.2ms → 7.5ms
  • 掉帧率: 35% → 2%

4.4 案例2: 滑动卡顿分析

问题现象: 某页面包含大量图片,滑动时严重卡顿。

步骤1: Systrace分析

发现RenderThread的flush drawing commands耗时特别长(18ms),GPU是瓶颈。

步骤2: 检查Overdraw

# 开启Overdraw检测
adb shell setprop debug.hwui.overdraw show

屏幕显示大片红色区域(4x+过度绘制),问题确认:大量图片重叠绘制。

根本原因: 页面使用FrameLayout堆叠多层背景,每个item又有半透明遮罩,导致严重Overdraw。

优化方案:

  1. 移除不必要的背景:
<!-- 优化前 -->
<FrameLayout android:background="@color/white">
    <ImageView android:background="@drawable/placeholder"/>
</FrameLayout>

<!-- 优化后: 移除FrameLayout背景 -->
<FrameLayout>
    <ImageView android:background="@drawable/placeholder"/>
</FrameLayout>
  1. 使用clipRect减少绘制区域:
@Override
protected void onDraw(Canvas canvas) {
    // 只绘制可见区域
    canvas.clipRect(0, 0, getWidth(), getHeight());
    super.onDraw(canvas);
}
  1. 使用硬件层缓存复杂View:
// 对于不经常变化的复杂View,缓存为纹理
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);

优化效果:

  • Overdraw: 4x+ → 2x
  • RenderThread flush: 18ms → 5ms
  • 滑动帧率: 35fps → 58fps

4.5 案例3: 启动黑屏问题排查

问题现象: App启动时,白屏/黑屏持续2秒。

步骤1: Systrace分析冷启动

# 启动App前先开始抓trace
python systrace.py gfx view am sm -b 32768 -t 10 -o cold_start.html &
adb shell am start -W com.example.app/.MainActivity

分析结果:

ActivityManager:
  startActivity                         [50ms]

App Process (com.example.app):
  bindApplication                       [1200ms]  ❌ 启动慢
    ├─ Application.onCreate             [800ms]   ❌ 超时
    │   └─ 初始化第三方SDK               [750ms]   ❌ 根本原因
    └─ Activity.onCreate                [350ms]
        └─ setContentView                [300ms]

  performTraversals (第一次绘制)       [150ms]

SurfaceFlinger:
  createSurface                         [20ms]

Total cold start time: 1.7s  ❌

根本原因: Application.onCreate中同步初始化了多个第三方SDK(广告/统计/推送),阻塞主线程。

优化方案:

  1. 延迟初始化非关键SDK:
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // 关键SDK: 立即初始化
        CrashReport.initCrashReport(this);

        // 非关键SDK: 延迟到首屏绘制完成后
        Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {
            initNonCriticalSDKs();
        });
    }

    private void initNonCriticalSDKs() {
        // 后台线程初始化
        Executors.newSingleThreadExecutor().execute(() -> {
            initAdSDK();
            initAnalyticsSDK();
            initPushSDK();
        });
    }
}
  1. 使用启动窗口(Splash Screen)改善感知:
<!-- themes.xml -->
<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@drawable/launch_screen</item>
</style>

<!-- AndroidManifest.xml -->
<activity
    android:name=".MainActivity"
    android:theme="@style/AppTheme.Launcher">

优化效果:

  • Application.onCreate: 800ms → 80ms
  • 总冷启动时间: 1.7s → 0.4s
  • 用户感知: 黑屏2秒 → 立即显示品牌页

五、显示子系统调优最佳实践

基于前面的分析,总结以下最佳实践:

5.1 减少过度绘制

目标: Overdraw < 2x

方法:

  1. 移除不必要的背景
  2. 使用<merge>标签减少布局层级
  3. 使用clipRect精确控制绘制区域
  4. 避免透明图层叠加

检查工具:

adb shell setprop debug.hwui.overdraw show

5.2 优化Layer数量

目标: 屏幕Layer数量< 20个

方法:

  1. 合并多个小View为一个自定义View
  2. 使用TextureView/SurfaceView替代多个普通View(视频/游戏场景)
  3. 避免不必要的View层级

检查工具:

adb shell dumpsys SurfaceFlinger --list | wc -l

5.3 合理使用硬件加速

方法:

  1. 默认开启硬件加速(Android 15默认行为)
  2. 对于不支持硬件加速的绘制操作(如文字路径),使用软件层:
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  1. 对于静态复杂View,使用硬件层缓存:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// 更新后清除缓存
view.setLayerType(View.LAYER_TYPE_NONE, null);

5.4 避免UI线程耗时操作

方法:

  1. 网络/IO操作必须在后台线程
  2. 复杂计算使用AsyncTask/Kotlin Coroutines
  3. RecyclerView预加载和缓存
  4. 图片加载使用Glide/Coil等库(自动管理线程)

5.5 Android 15新特性的应用建议

1. 利用动态刷新率

// 针对静态内容,请求降低刷新率
Surface surface = getSurface();
surface.setFrameRate(30.0f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);

// 针对游戏,请求高刷新率
surface.setFrameRate(120.0f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);

2. 使用最新的Graphics API

  • 优先使用Vulkan替代OpenGL ES(游戏场景)
  • 使用RenderEffect API实现高性能模糊效果
  • 使用PixelCopy API高效截图

3. 启用Canvas现代化

Android 15的Canvas API经过重写,性能提升30%+:

// 自动启用(Android 15默认)
// 检查是否启用
if (Build.VERSION.SDK_INT >= 35) {
    Log.i(TAG, "Using modernized Canvas implementation");
}

总结

本文深入分析了Android 15显示系统的Vsync同步机制和性能优化:

  1. Vsync是显示系统的心跳,协调应用绘制、系统合成、屏幕显示三个环节
  2. 硬件Vsync和软件Vsync结合,在准确性和性能之间取得平衡
  3. Phase Offset机制让应用提前收到Vsync,有更多时间绘制
  4. Choreographer是应用侧与Vsync的桥梁,协调输入、动画、绘制的执行时机
  5. 三重缓冲通过增加一个Buffer,彻底解决双缓冲的等待问题,代价是内存增加33%
  6. Systrace/Perfetto是性能分析的最强工具,可以完整展示渲染管线的每个环节
  7. 性能优化的关键是定位瓶颈:UI Thread/RenderThread/SurfaceFlinger,对症下药

掌握这些原理和工具,就能够:

  • 诊断和解决各类显示性能问题
  • 构建流畅的用户体验(稳定60fps+)
  • 在性能和功耗之间取得最优平衡
  • 为后续的深度优化和问题排查打下坚实基础

参考资源

源码路径(AOSP 15)

frameworks/native/services/surfaceflinger/Scheduler/  # Vsync调度
frameworks/base/core/java/android/view/Choreographer.java  # Choreographer
frameworks/native/libs/gui/BufferQueue*.cpp  # BufferQueue

官方文档

推荐阅读

调试命令速查

# GPU呈现模式
adb shell setprop debug.hwui.profile true

# Overdraw检测
adb shell setprop debug.hwui.overdraw show

# Systrace抓取
python systrace.py gfx view sched freq -b 32768 -t 5 -o trace.html

# 查看帧率统计
adb shell dumpsys gfxinfo <package>

# 查看SurfaceFlinger状态
adb shell dumpsys SurfaceFlinger

# 查看Vsync信息
adb shell dumpsys SurfaceFlinger --vsync-info

# 强制GPU合成
adb shell setprop debug.sf.disable_hwc 1

# 显示Vsync信息
adb shell setprop debug.sf.show_cpu 1

系列文章


欢迎来我中的个人主页找到更多有用的知识和有趣的产品