12 KiB
12 KiB
流畅度(Choreographer+VSYNC)
概述
流畅度是Android应用性能的核心指标之一,直接影响用户体验。本文档深入解析VSYNC机制和Choreographer的工作原理,以及如何分析和优化应用流畅度。
VSYNC机制
什么是VSYNC
VSYNC(Vertical Synchronization,垂直同步)是显示系统与GPU之间的同步机制,确保帧在正确的时机刷新到屏幕。
VSYNC的作用
- 防止画面撕裂: 避免在画面刷新过程中显示不完整的帧
- 统一刷新时机: 所有绘制操作在VSYNC信号到来时同步执行
- 保证流畅度: 确保以固定频率(通常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信号同步。
核心类结构
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);
}
回调执行顺序:
- INPUT: 输入事件处理
- ANIMATION: 动画执行
- TRAVERSAL: 视图遍历(measure/layout/draw)
- 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)分析
掉帧原因
-
主线程阻塞
- 同步I/O操作
- 复杂计算
- 同步网络请求
-
布局复杂
- 嵌套层级过深
- 过度绘制
- 频繁measure/layout
-
内存问题
- 频繁GC
- 内存抖动
- 内存泄漏
-
动画问题
- 复杂动画计算
- 未使用硬件加速
- 动画冲突
掉帧检测
// 使用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%
测量方法
- Systrace/Perfetto分析
- GPU Rendering Profile
- Choreographer监控
- BlockCanary检测
最佳实践
- 测量优先: 先测量,找到瓶颈再优化
- 避免过度优化: 不要过早优化
- 系统化思考: 从架构层面考虑
- 持续监控: 建立性能监控体系
- 用户感知: 关注用户可感知的流畅度