Files
mkdocs/docs/android面试/性能优化/流畅度优化.md
2026-01-15 11:53:37 +08:00

13 KiB
Raw Permalink Blame History

流畅度优化

目录


卡顿原因分析

卡顿的定义

卡顿是指界面渲染不流畅,帧率低于 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 的要求

  1. 单帧耗时 < 16.67ms
  2. 主线程不阻塞
  3. 布局不复杂
  4. 不过度绘制
  5. 使用硬件加速

性能监控

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 简介

VSYNCVertical Synchronization是垂直同步信号用于同步屏幕刷新和 GPU 渲染。

VSYNC 工作原理

时间轴:
0ms    16.67ms   33.34ms   50ms
|--------|--------|--------|
  VSYNC   VSYNC   VSYNC
  1. VSYNC 信号触发:每 16.67ms 触发一次
  2. CPU 处理:处理输入事件、测量布局
  3. GPU 渲染:渲染帧到缓冲区
  4. 屏幕显示:显示缓冲区内容

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

答案:

  1. 单帧耗时 < 16.67ms
  2. 主线程不阻塞:避免在主线程执行耗时操作
  3. 布局优化:减少布局层级,使用 ConstraintLayout
  4. 避免过度绘制:移除不必要的背景
  5. 使用硬件加速:启用硬件加速
  6. 优化动画:使用属性动画,避免 View 动画

Q2: 什么是 VSYNC

答案:

  • VSYNCVertical Synchronization是垂直同步信号
  • 用于同步屏幕刷新和 GPU 渲染
  • 每 16.67ms 触发一次60fps
  • 保证画面不撕裂,流畅显示

Q3: Choreographer 的作用?

答案:

  • Choreographer 是帧调度器
  • 协调输入事件、动画、绘制
  • 按顺序执行:输入 -> 动画 -> 绘制
  • 保证在 VSYNC 信号时执行

Q4: 如何检测卡顿?

答案:

  1. GPU 渲染模式分析:查看条形图
  2. Systrace/Perfetto:分析系统性能
  3. Android Studio Profiler:分析 CPU、内存
  4. 自定义监控:监控帧率、主线程阻塞
  5. 开发者选项:启用过度绘制检测

Q5: 过度绘制如何优化?

答案:

  1. 移除不必要的背景:减少背景层数
  2. 使用 clipRect:只绘制可见区域
  3. 使用 ViewStub:延迟加载布局
  4. 优化自定义 View:只绘制必要内容
  5. 使用硬件加速:提升绘制性能

Q6: 主线程阻塞的原因和解决方案?

答案:

  • 原因

    1. 执行耗时操作(网络请求、数据库操作)
    2. 复杂计算
    3. 同步等待
  • 解决方案

    1. 使用异步处理(线程、协程)
    2. 使用 Handler 切换到后台线程
    3. 使用 AsyncTask、RxJava 等框架
    4. 优化算法,减少计算时间

Q7: 流畅度优化的最佳实践?

答案:

  1. 主线程优化:避免阻塞,使用异步处理
  2. 布局优化:减少层级,使用 ConstraintLayout
  3. 绘制优化:避免过度绘制,使用硬件加速
  4. 动画优化:使用属性动画,避免 View 动画
  5. 监控性能:使用工具检测,及时发现问题

总结

流畅度优化是 Android 性能优化的重要部分,主要从以下几个方面入手:

  1. 保证 60fps:单帧耗时 < 16.67ms
  2. 主线程优化:避免阻塞,使用异步处理
  3. 布局优化:减少层级,使用 ConstraintLayout
  4. 绘制优化:避免过度绘制,使用硬件加速
  5. VSYNC 机制:理解 VSYNC 和 Choreographer
  6. 性能监控:使用工具检测,及时发现问题

通过合理的流畅度优化,可以提升用户体验,让应用运行更加流畅。


最后更新2024年