# 流畅度(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全解读]]