2026-01-12 13:30:25 +08:00
|
|
|
|
# 流畅度(Choreographer+VSYNC)
|
2026-01-12 17:14:58 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
流畅度是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全解读]]
|