Files
mkdocs/docs/Obsidian笔记体系/Areas/06-性能优化体系/流畅度(Choreographer+VSYNC).md
renjianbo 8a4717277d 测试
2026-01-12 17:14:58 +08:00

12 KiB
Raw Blame History

流畅度Choreographer+VSYNC

概述

流畅度是Android应用性能的核心指标之一直接影响用户体验。本文档深入解析VSYNC机制和Choreographer的工作原理以及如何分析和优化应用流畅度。

VSYNC机制

什么是VSYNC

VSYNCVertical Synchronization垂直同步是显示系统与GPU之间的同步机制确保帧在正确的时机刷新到屏幕。

VSYNC的作用

  1. 防止画面撕裂: 避免在画面刷新过程中显示不完整的帧
  2. 统一刷新时机: 所有绘制操作在VSYNC信号到来时同步执行
  3. 保证流畅度: 确保以固定频率通常60Hz刷新画面

VSYNC工作流程

VSYNC信号16.67ms间隔60Hz
    ↓
SurfaceFlinger接收VSYNC
    ↓
通知应用层开始绘制
    ↓
应用执行measure/layout/draw
    ↓
提交帧到SurfaceFlinger
    ↓
下一个VSYNC时显示

VSYNC时间线

Frame N-1    Frame N      Frame N+1
   |            |            |
VSYNC ──────> VSYNC ──────> VSYNC
   |            |            |
   |<-- 16.67ms --><-- 16.67ms -->

关键时间点:

  • VSYNC间隔: 16.67ms60Hz
  • 绘制窗口: 必须在16.67ms内完成
  • 超过时间: 会掉帧Jank

Choreographer源码解析

Choreographer的作用

Choreographer是Android系统提供的帧调度器负责协调应用层的绘制时机与VSYNC信号同步。

核心类结构

public final class Choreographer {
    // VSYNC回调接口
    private final FrameDisplayEventReceiver mDisplayEventReceiver;
    
    // 回调队列
    private final CallbackQueue[] mCallbackQueues;
    
    // 当前帧时间
    private long mFrameTimeNanos;
    
    // 帧间隔(纳秒)
    private static final long FRAME_INTERVAL_NANOS = 16_666_667L; // 16.67ms
}

关键方法

1. postFrameCallback

public void postFrameCallback(FrameCallback callback) {
    postFrameCallbackDelayed(callback, 0);
}

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    if (callback == null) {
        throw new IllegalArgumentException("callback must not be null");
    }
    
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

作用: 注册一个回调,在下一帧绘制时执行。

使用场景:

  • 动画帧回调
  • 自定义绘制
  • 性能监控

2. doFrame

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos = System.nanoTime();
    final long jitterNanos = startNanos - frameTimeNanos;
    
    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.");
        }
    }
    
    // 执行回调
    mFrameInfo.markInputHandlingStart();
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    
    mFrameInfo.markAnimationsStart();
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    
    mFrameInfo.markPerformTraversalsStart();
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}

回调执行顺序:

  1. INPUT: 输入事件处理
  2. ANIMATION: 动画执行
  3. TRAVERSAL: 视图遍历measure/layout/draw
  4. COMMIT: 提交帧

FrameDisplayEventReceiver

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        // 接收VSYNC信号
        long now = System.nanoTime();
        if (timestampNanos > now) {
            timestampNanos = now;
        }
        
        if (mHavePendingVsync) {
            Log.w(TAG, "Already have a pending vsync event.  There should only be "
                    + "one at a time.");
        } else {
            mHavePendingVsync = true;
        }
        
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
    
    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame);
    }
}

流畅度分析

1. 使用Systrace/Perfetto

# 录制Systrace
python systrace.py -t 10 -o trace.html gfx view sched freq idle am wm

# 关键指标:
# - Frame: 每一帧的耗时
# - VSYNC: VSYNC信号
# - UI Thread: 主线程执行情况

分析要点:

  • 查看Frame是否超过16.67ms
  • 识别主线程阻塞
  • 查找耗时操作

2. 使用GPU Rendering Profile

// 在开发者选项中开启"GPU渲染模式分析"
// 或使用代码开启
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);

指标说明:

  • 绿色线: 16.67ms基准线
  • 蓝色: 测量时间
  • 紫色: 布局时间
  • 红色: 绘制时间

3. 使用Choreographer监控

public class FrameMonitor {
    private Choreographer mChoreographer;
    private long mLastFrameTimeNanos;
    private int mDroppedFrames;
    
    public void start() {
        mChoreographer = Choreographer.getInstance();
        mChoreographer.postFrameCallback(mFrameCallback);
    }
    
    private Choreographer.FrameCallback mFrameCallback = 
        new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            if (mLastFrameTimeNanos != 0) {
                long frameInterval = frameTimeNanos - mLastFrameTimeNanos;
                if (frameInterval > 16_666_667L * 1.5) {
                    // 掉帧检测
                    mDroppedFrames++;
                    Log.w("FrameMonitor", "Dropped frame: " + 
                        (frameInterval / 1_000_000) + "ms");
                }
            }
            mLastFrameTimeNanos = frameTimeNanos;
            mChoreographer.postFrameCallback(this);
        }
    };
}

4. 使用BlockCanary

dependencies {
    debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'
}
// 自动检测主线程阻塞
// 超过阈值会弹出通知

流畅度优化策略

1. 减少主线程耗时操作

// ❌ 错误:在主线程做耗时操作
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 同步网络请求
    String data = networkRequest(); // 阻塞主线程
    updateUI(data);
}

// ✅ 正确:异步处理
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 异步加载
    loadDataAsync();
}

private void loadDataAsync() {
    new Thread(() -> {
        String data = networkRequest();
        runOnUiThread(() -> {
            updateUI(data);
        });
    }).start();
}

2. 优化布局性能

减少布局层级

<!-- ❌ 错误:嵌套过深 -->
<LinearLayout>
    <LinearLayout>
        <LinearLayout>
            <TextView />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

<!-- ✅ 正确使用ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView />
</androidx.constraintlayout.widget.ConstraintLayout>

使用include和merge

<!-- 复用布局 -->
<include layout="@layout/common_header" />

<!-- 减少层级 -->
<merge>
    <TextView />
    <ImageView />
</merge>

避免过度绘制

// 开启过度绘制检测
// 开发者选项 -> 显示过度绘制区域

// 优化方法:
// 1. 移除不必要的背景
view.setBackground(null);

// 2. 使用clipRect裁剪绘制区域
canvas.clipRect(left, top, right, bottom);

// 3. 使用ViewStub延迟加载

3. 优化自定义View绘制

public class CustomView extends View {
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        // ❌ 错误在onDraw中创建对象
        // Paint paint = new Paint();
        
        // ✅ 正确:复用对象
        if (mPaint == null) {
            mPaint = new Paint();
        }
        
        // 避免在onDraw中做耗时计算
        // 预处理数据,缓存结果
    }
    
    private Paint mPaint;
}

4. 使用硬件加速

<!-- AndroidManifest.xml -->
<application
    android:hardwareAccelerated="true">
</application>
// 代码中开启
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);

// 注意:硬件加速不是万能的
// 某些操作如clipPath在硬件加速下可能更慢

5. 优化动画性能

// ❌ 错误使用属性动画在复杂View上
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0, 100);
animator.start();

// ✅ 正确使用ViewPropertyAnimator
view.animate()
    .translationX(100)
    .setDuration(300)
    .start();

// ✅ 更优:使用硬件层
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
view.animate()
    .translationX(100)
    .withEndAction(() -> {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    })
    .start();

6. 使用RecyclerView优化列表

// 优化RecyclerView性能
recyclerView.setHasFixedSize(true); // 固定大小
recyclerView.setItemViewCacheSize(20); // 增加缓存
recyclerView.setDrawingCacheEnabled(true);

// 使用DiffUtil更新数据
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(oldList, newList));
diffResult.dispatchUpdatesTo(adapter);

7. 避免内存抖动

// ❌ 错误:频繁创建对象
for (int i = 0; i < 1000; i++) {
    String str = new String("item" + i); // 每次循环创建新对象
    list.add(str);
}

// ✅ 正确复用对象或使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.setLength(0);
    sb.append("item").append(i);
    list.add(sb.toString());
}

掉帧Jank分析

掉帧原因

  1. 主线程阻塞

    • 同步I/O操作
    • 复杂计算
    • 同步网络请求
  2. 布局复杂

    • 嵌套层级过深
    • 过度绘制
    • 频繁measure/layout
  3. 内存问题

    • 频繁GC
    • 内存抖动
    • 内存泄漏
  4. 动画问题

    • 复杂动画计算
    • 未使用硬件加速
    • 动画冲突

掉帧检测

// 使用Choreographer检测掉帧
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        long currentTime = System.nanoTime();
        long frameInterval = currentTime - mLastFrameTime;
        
        if (frameInterval > 16_666_667L * 1.5) {
            // 掉帧
            int droppedFrames = (int) (frameInterval / 16_666_667L);
            Log.w("Jank", "Dropped " + droppedFrames + " frames");
        }
        
        mLastFrameTime = currentTime;
        Choreographer.getInstance().postFrameCallback(this);
    }
});

性能指标

目标值

  • FPS: 稳定在60fps
  • 帧耗时: < 16.67ms
  • 掉帧率: < 1%
  • ANR率: < 0.1%

测量方法

  1. Systrace/Perfetto分析
  2. GPU Rendering Profile
  3. Choreographer监控
  4. BlockCanary检测

最佳实践

  1. 测量优先: 先测量,找到瓶颈再优化
  2. 避免过度优化: 不要过早优化
  3. 系统化思考: 从架构层面考虑
  4. 持续监控: 建立性能监控体系
  5. 用户感知: 关注用户可感知的流畅度

相关链接