Android 性能优化之界面优化

Android 性能优化之界面优化

  • FrameTime 两帧画面间隔耗时(也可简单认为是单帧渲染耗时)
  • Frame Rate 帧率:指的是 1 秒钟播放多少帧
  • FPS 每秒帧数(Frames Per Second):就是 1 秒内画面刷新次数,其实 FPS 就是帧率的简称
  • Jank 卡顿次数(精确卡顿率):帧率波动、画面不连续,通常是由掉帧引起的
  • Frame Dropped 掉帧:因性能不足(渲染耗时过长)或网络延迟等问题导致实际帧率低于目标预期值,无法按目标时间绘制渲染完所有的帧,从而导致部分帧被丢弃了,表现为界面卡顿或画面不流畅 停滞
  • Frame Lost 丢帧(Frame Discard 抛帧、 Frame Throttled 压帧):主动丢帧,由于性能限制,主动地丢弃一些帧以保持画面的流畅性,可能帧率不及预期,但能够满足界面不卡顿
  • Frame Skipped 跳帧:主动有策略地跳过某些帧的显示(可能是连续或非关键帧),但可能帧率能够维持预期值(比如视频倍速播放)地

界面优化

  • 在 onDraw 方法中避免创建新对象,减少内存分配和垃圾回收
  • 减少布局层级嵌套
    • 优先使用 ConstraintLayout 减少布局嵌套
    • 使用 <include> 标签复用布局,减少布局层级
    • 使用 <merge><ViewStub> 标签优化布局层级
  • 打开开发者选项 -> 调试 GPU 过度绘制,通过显示的不同颜色来区分是存在过度绘制
  • 通过 Layout Inspector 工具查看布局层级,排查是否存在多层无用的嵌套
  • Systrace 是一个强大的系统级性能数据采样和分析工具,支持分析渲染流程(Vsync、Input、Animation、Traversal),调试中执行命令生成 HTML 报告供查看,可以在代码中使用 Trace#traceBegin 和 Trace#traceEnd 记录应用内日志
  • 利用 adb shell dumpsys 进行排查优化

Looper 消息循环日志监控

  • 替换主线程 Looper 的 Printer 日志打印,通过在消息执行前后的打印信息计算消息执行时间,从而判断是否存在卡顿,若 dispatchMessage 执行时间异常,则判定为界面卡顿
java 复制代码
Looper.getMainLooper().setMessageLogging(new Printer() {
    @Override
    public void println(String x) {
        //
        if (x.startsWith(">>>>>")) { //消息开始
            startTime = System.currentTimeMillis();
        } else if (x.startsWith("<<<<<")) { //消息结束
            long duration = System.currentTimeMillis() - startTime;
            //判断
        }
    }
});

Choreographer 编舞者进行帧监控

  • 进行掉帧检测,在每一帧回调时计算帧间隔时间,若间隔时间超过 16 毫秒,就认为可能存在掉帧的情况
java 复制代码
public class FrameMonitor implements Choreographer.FrameCallback {
    //系统每 16.67ms(60Hz 每 1 秒刷新 60 次,即 1000/60)发出一个 VSYNC 信号来通知刷新一次屏幕
    //假设在界面不卡顿的情况下,界面应该至少间隔 16.67ms 刷新一次,因此理论上至少每 16.67ms 应该会触发一次回调
    //如果发生界面卡顿了,那么回调的触发时间间隔就会超过 16.67 ms
    private static final long FRAME_TIME_THRESHOLD = 16L; //毫秒
    private long lastFrameTimeNanos = 0;

    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTimeNanos != 0) {
            long jitterMillis = (frameTimeNanos - lastFrameTimeNanos) / 1000000;
            if (jitterMillis > FRAME_TIME_THRESHOLD) {
                //计算相邻帧时间差
                System.out.println("帧间隔过长: " + jitterMillis + " ms");
            }
        }
        lastFrameTimeNanos = frameTimeNanos;
        //注册下一帧回调
        Choreographer.getInstance().postFrameCallback(this);
    }

    public void start() {
        Choreographer.getInstance().postFrameCallback(this);
    }

    public void stop() {
        Choreographer.getInstance().removeFrameCallback(this);
    }
}    

FrameMetrics

  • Android 高版本直接获取帧率相关数据信息
java 复制代码
public class MainActivity extends Activity {
    private Window.OnFrameMetricsAvailableListener frameMetricsListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建帧指标监听器
        frameMetricsListener = new Window.OnFrameMetricsAvailableListener() {
            @Override
            public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                // 
                //表示完整帧(获取到 Vsync 信号到帧完成的总时间)的时间
                long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
                //处理帧指标数据,可以在这里进行日志记录或其他操作
            }
        };
        //添加监听器
        getWindow().addOnFrameMetricsAvailableListener(frameMetricsListener, null); //可以指定 Handler
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //移除监听器
        if (frameMetricsListener != null) {
            getWindow().removeOnFrameMetricsAvailableListener(frameMetricsListener);
        }
    }
}

BlockCanary

  • 基于 Looper 的 Printer 日志打印,内部也是使用了 Looper.getMainLooper().setMessageLogging 通过替换主线程 Looper 打印的日志方案实现

腾讯 Matrix

  • 结合了 Choreographer 和 Looper 监控方案,分析 UI 线程的 Message 执行耗时

JankStats 卡顿统计(Metrics 指标库)

  • Android 高版本采用 FrameMetrics,Android 低版本采用 Choreographer
groovy 复制代码
dependencies {
    implementation "androidx.metrics:metrics-performance:1.0.0-beta02"
}
相关推荐
Devil枫2 小时前
Kotlin高级特性深度解析
android·开发语言·kotlin
ChinaDragonDreamer2 小时前
Kotlin:2.1.20 的新特性
android·开发语言·kotlin
雨白12 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹14 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空15 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭16 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日17 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安17 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑17 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟21 小时前
CTF Web的数组巧用
android