13 KiB
13 KiB
流畅度优化
目录
卡顿原因分析
卡顿的定义
卡顿是指界面渲染不流畅,帧率低于 60fps(每秒 60 帧),用户感知到画面不连续。
卡顿的主要原因
1. 主线程阻塞
// ❌ 错误示例:主线程执行耗时操作
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 主线程执行耗时操作
loadDataFromDatabase(); // 阻塞主线程
processLargeImage(); // 阻塞主线程
}
// ✅ 正确做法:使用异步处理
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 异步执行耗时操作
new Thread(() -> {
loadDataFromDatabase();
processLargeImage();
// 更新 UI 回到主线程
runOnUiThread(() -> {
updateUI();
});
}).start();
}
2. 布局复杂
<!-- ❌ 错误示例:布局层级过深 -->
<LinearLayout>
<LinearLayout>
<LinearLayout>
<LinearLayout>
<TextView />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- ✅ 正确做法:使用 ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView />
</androidx.constraintlayout.widget.ConstraintLayout>
3. 过度绘制
// ❌ 错误示例:多层背景
<LinearLayout
android:background="@drawable/bg1">
<LinearLayout
android:background="@drawable/bg2">
<TextView
android:background="@drawable/bg3" />
</LinearLayout>
</LinearLayout>
// ✅ 正确做法:移除不必要的背景
<LinearLayout>
<TextView />
</LinearLayout>
4. 频繁创建对象
// ❌ 错误示例:在 onDraw 中创建对象
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint(); // 每次绘制都创建新对象
canvas.drawText("Hello", 0, 0, paint);
}
// ✅ 正确做法:复用对象
public class MyView extends View {
private Paint mPaint; // 复用 Paint 对象
public MyView(Context context) {
super(context);
mPaint = new Paint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("Hello", 0, 0, mPaint);
}
}
5. 动画性能问题
// ❌ 错误示例:使用 View 动画(在主线程)
view.animate()
.translationX(100)
.setDuration(1000)
.start();
// ✅ 正确做法:使用属性动画(硬件加速)
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0, 100);
animator.setDuration(1000);
animator.start();
60fps保证
帧率计算
fps = 1000ms / 单帧耗时
60fps = 1000ms / 16.67ms
保证 60fps 的要求
- 单帧耗时 < 16.67ms
- 主线程不阻塞
- 布局不复杂
- 不过度绘制
- 使用硬件加速
性能监控
public class FrameMonitor {
private Choreographer choreographer;
private long lastFrameTime = 0;
private int frameCount = 0;
private long totalFrameTime = 0;
public void start() {
choreographer = Choreographer.getInstance();
choreographer.postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTime != 0) {
long frameTime = (frameTimeNanos - lastFrameTime) / 1_000_000; // 转换为毫秒
totalFrameTime += frameTime;
frameCount++;
if (frameTime > 16.67) {
Log.w("FrameMonitor", "Frame dropped: " + frameTime + "ms");
}
}
lastFrameTime = frameTimeNanos;
choreographer.postFrameCallback(this);
}
});
}
public float getAverageFps() {
if (frameCount == 0) {
return 0;
}
float averageFrameTime = totalFrameTime / (float) frameCount;
return 1000.0f / averageFrameTime;
}
}
VSYNC机制
VSYNC 简介
VSYNC(Vertical Synchronization)是垂直同步信号,用于同步屏幕刷新和 GPU 渲染。
VSYNC 工作原理
时间轴:
0ms 16.67ms 33.34ms 50ms
|--------|--------|--------|
VSYNC VSYNC VSYNC
- VSYNC 信号触发:每 16.67ms 触发一次
- CPU 处理:处理输入事件、测量布局
- GPU 渲染:渲染帧到缓冲区
- 屏幕显示:显示缓冲区内容
VSYNC 优化
// 使用 VSYNC 同步动画
view.postOnAnimation(() -> {
// 在下一个 VSYNC 执行
updateAnimation();
});
Choreographer
Choreographer 简介
Choreographer 是 Android 4.1+ 引入的帧调度器,用于协调动画、输入和绘制。
Choreographer 工作原理
public class Choreographer {
// 三种回调类型
public static final int CALLBACK_INPUT = 0; // 输入事件
public static final int CALLBACK_ANIMATION = 1; // 动画
public static final int CALLBACK_TRAVERSAL = 2; // 绘制
// 执行顺序
// 1. CALLBACK_INPUT:处理输入事件
// 2. CALLBACK_ANIMATION:执行动画
// 3. CALLBACK_TRAVERSAL:执行绘制
}
Choreographer 使用
// 在下一个 VSYNC 执行
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
// 执行操作
updateUI();
// 继续下一帧
Choreographer.getInstance().postFrameCallback(this);
}
});
// 延迟执行
Choreographer.getInstance().postFrameCallbackDelayed(callback, delayMillis);
帧率监控
public class FrameRateMonitor {
private Choreographer choreographer;
private long lastFrameTime = 0;
private int droppedFrames = 0;
public void start() {
choreographer = Choreographer.getInstance();
choreographer.postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
if (lastFrameTime != 0) {
long frameTime = (frameTimeNanos - lastFrameTime) / 1_000_000;
if (frameTime > 16.67) {
droppedFrames += (int) (frameTime / 16.67) - 1;
Log.w("FrameRate", "Dropped frames: " + droppedFrames);
}
}
lastFrameTime = frameTimeNanos;
choreographer.postFrameCallback(this);
}
});
}
}
过度绘制优化
什么是过度绘制?
过度绘制是指同一像素被绘制多次,浪费 GPU 资源。
过度绘制检测
1. 开发者选项
设置 -> 开发者选项 -> 调试 GPU 过度绘制 -> 显示过度绘制区域
- 蓝色:绘制 1 次(正常)
- 绿色:绘制 2 次(可接受)
- 浅红色:绘制 3 次(需要优化)
- 深红色:绘制 4 次以上(必须优化)
2. 代码检测
// 启用过度绘制检测
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
过度绘制优化方法
1. 移除不必要的背景
<!-- ❌ 错误示例:多层背景 -->
<LinearLayout
android:background="@drawable/bg1">
<LinearLayout
android:background="@drawable/bg2">
<TextView />
</LinearLayout>
</LinearLayout>
<!-- ✅ 正确做法:移除不必要的背景 -->
<LinearLayout>
<TextView />
</LinearLayout>
2. 使用 clipRect
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 裁剪绘制区域
canvas.clipRect(0, 0, width, height);
// 只绘制可见区域
drawContent(canvas);
}
3. 使用 ViewStub
<!-- 延迟加载不必要的布局 -->
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/heavy_layout" />
4. 优化自定义 View
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 只绘制可见区域
if (shouldDraw()) {
drawContent(canvas);
}
}
流畅度优化工具
1. GPU 渲染模式分析
设置 -> 开发者选项 -> GPU 渲染模式分析 -> 在屏幕上显示为条形图
- 绿色线:16.67ms 基准线
- 红色条:超过 16.67ms,表示卡顿
2. Systrace/Perfetto
# 分析流畅度
python systrace.py -t 10 -o trace.html gfx view input
3. Android Studio Profiler
- CPU Profiler:分析 CPU 使用
- Memory Profiler:分析内存使用
- Network Profiler:分析网络请求
4. Layout Inspector
- 查看布局层级
- 分析布局性能
- 查看 View 属性
5. 自定义监控工具
public class PerformanceMonitor {
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable monitorRunnable = new Runnable() {
@Override
public void run() {
// 监控主线程性能
long startTime = System.currentTimeMillis();
handler.post(() -> {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
if (duration > 16) {
Log.w("Performance", "Main thread blocked: " + duration + "ms");
}
});
handler.postDelayed(this, 100);
}
};
public void start() {
handler.post(monitorRunnable);
}
public void stop() {
handler.removeCallbacks(monitorRunnable);
}
}
面试常见问题
Q1: 如何保证 60fps?
答案:
- 单帧耗时 < 16.67ms
- 主线程不阻塞:避免在主线程执行耗时操作
- 布局优化:减少布局层级,使用 ConstraintLayout
- 避免过度绘制:移除不必要的背景
- 使用硬件加速:启用硬件加速
- 优化动画:使用属性动画,避免 View 动画
Q2: 什么是 VSYNC?
答案:
- VSYNC(Vertical Synchronization)是垂直同步信号
- 用于同步屏幕刷新和 GPU 渲染
- 每 16.67ms 触发一次(60fps)
- 保证画面不撕裂,流畅显示
Q3: Choreographer 的作用?
答案:
- Choreographer 是帧调度器
- 协调输入事件、动画、绘制
- 按顺序执行:输入 -> 动画 -> 绘制
- 保证在 VSYNC 信号时执行
Q4: 如何检测卡顿?
答案:
- GPU 渲染模式分析:查看条形图
- Systrace/Perfetto:分析系统性能
- Android Studio Profiler:分析 CPU、内存
- 自定义监控:监控帧率、主线程阻塞
- 开发者选项:启用过度绘制检测
Q5: 过度绘制如何优化?
答案:
- 移除不必要的背景:减少背景层数
- 使用 clipRect:只绘制可见区域
- 使用 ViewStub:延迟加载布局
- 优化自定义 View:只绘制必要内容
- 使用硬件加速:提升绘制性能
Q6: 主线程阻塞的原因和解决方案?
答案:
-
原因:
- 执行耗时操作(网络请求、数据库操作)
- 复杂计算
- 同步等待
-
解决方案:
- 使用异步处理(线程、协程)
- 使用 Handler 切换到后台线程
- 使用 AsyncTask、RxJava 等框架
- 优化算法,减少计算时间
Q7: 流畅度优化的最佳实践?
答案:
- 主线程优化:避免阻塞,使用异步处理
- 布局优化:减少层级,使用 ConstraintLayout
- 绘制优化:避免过度绘制,使用硬件加速
- 动画优化:使用属性动画,避免 View 动画
- 监控性能:使用工具检测,及时发现问题
总结
流畅度优化是 Android 性能优化的重要部分,主要从以下几个方面入手:
- 保证 60fps:单帧耗时 < 16.67ms
- 主线程优化:避免阻塞,使用异步处理
- 布局优化:减少层级,使用 ConstraintLayout
- 绘制优化:避免过度绘制,使用硬件加速
- VSYNC 机制:理解 VSYNC 和 Choreographer
- 性能监控:使用工具检测,及时发现问题
通过合理的流畅度优化,可以提升用户体验,让应用运行更加流畅。
最后更新:2024年