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"
}
相关推荐
消失的旧时光-19432 小时前
从 Kotlin 到 Dart:为什么 sealed 是处理「多种返回结果」的最佳方式?
android·开发语言·flutter·架构·kotlin·sealed
Jinkxs2 小时前
Gradle - 与Groovy/Kotlin DSL对比 构建脚本语言选择指南
android·开发语言·kotlin
&有梦想的咸鱼&2 小时前
Kotlin委托机制的底层实现深度解析(74)
android·开发语言·kotlin
LDORntKQH2 小时前
基于深度强化学习的混合动力汽车能量管理策略 1.利用DQN算法控制电池和发动机发电机组的功率分配 2
android
冬奇Lab3 小时前
Android 15 ServiceManager与Binder服务注册深度解析
android·源码·源码阅读
2501_916008894 小时前
深入解析iOS机审4.3原理与混淆实战方法
android·java·开发语言·ios·小程序·uni-app·iphone
独行soc6 小时前
2026年渗透测试面试题总结-20(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
常利兵6 小时前
2026年,Android开发已死?不,它正迎来黄金时代!
android
Risehuxyc6 小时前
备份三个PHP程序
android·开发语言·php
Doro再努力16 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim