# 流畅度(Choreographer+VSYNC) ## 概述 流畅度是Android应用性能的核心指标之一,直接影响用户体验。本文档深入解析VSYNC机制和Choreographer的工作原理,以及如何分析和优化应用流畅度。 ## VSYNC机制 ### 什么是VSYNC VSYNC(Vertical 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.67ms(60Hz) - **绘制窗口**: 必须在16.67ms内完成 - **超过时间**: 会掉帧(Jank) ## Choreographer源码解析 ### Choreographer的作用 Choreographer是Android系统提供的帧调度器,负责协调应用层的绘制时机与VSYNC信号同步。 ### 核心类结构 ```java 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 ```java 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 ```java 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 ```java 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 ```bash # 录制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 ```java // 在开发者选项中开启"GPU渲染模式分析" // 或使用代码开启 View.setLayerType(View.LAYER_TYPE_HARDWARE, null); ``` **指标说明**: - **绿色线**: 16.67ms基准线 - **蓝色**: 测量时间 - **紫色**: 布局时间 - **红色**: 绘制时间 ### 3. 使用Choreographer监控 ```java 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 ```gradle dependencies { debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0' } ``` ```java // 自动检测主线程阻塞 // 超过阈值会弹出通知 ``` ## 流畅度优化策略 ### 1. 减少主线程耗时操作 ```java // ❌ 错误:在主线程做耗时操作 @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. 优化布局性能 #### 减少布局层级 ```xml ``` #### 使用include和merge ```xml ``` #### 避免过度绘制 ```java // 开启过度绘制检测 // 开发者选项 -> 显示过度绘制区域 // 优化方法: // 1. 移除不必要的背景 view.setBackground(null); // 2. 使用clipRect裁剪绘制区域 canvas.clipRect(left, top, right, bottom); // 3. 使用ViewStub延迟加载 ``` ### 3. 优化自定义View绘制 ```java 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. 使用硬件加速 ```xml ``` ```java // 代码中开启 view.setLayerType(View.LAYER_TYPE_HARDWARE, null); // 注意:硬件加速不是万能的 // 某些操作(如clipPath)在硬件加速下可能更慢 ``` ### 5. 优化动画性能 ```java // ❌ 错误:使用属性动画在复杂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优化列表 ```java // 优化RecyclerView性能 recyclerView.setHasFixedSize(true); // 固定大小 recyclerView.setItemViewCacheSize(20); // 增加缓存 recyclerView.setDrawingCacheEnabled(true); // 使用DiffUtil更新数据 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(oldList, newList)); diffResult.dispatchUpdatesTo(adapter); ``` ### 7. 避免内存抖动 ```java // ❌ 错误:频繁创建对象 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. **动画问题** - 复杂动画计算 - 未使用硬件加速 - 动画冲突 ### 掉帧检测 ```java // 使用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. **用户感知**: 关注用户可感知的流畅度 ## 相关链接 - [[README]] - [[启动优化方法论]] - [[内存优化(LeakCanary原理)]] - [[09-调试与工具链/Systrace_Perfetto全解读]]