# 流畅度（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
<!-- ❌ 错误：嵌套过深 -->
<LinearLayout>
    <LinearLayout>
        <LinearLayout>
            <TextView />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

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

#### 使用include和merge

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

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

#### 避免过度绘制

```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
<!-- AndroidManifest.xml -->
<application
    android:hardwareAccelerated="true">
</application>
```

```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全解读]]