资讯动态

cw-omnibus 实战:基于 Android 7.0 FrameMetrics 的运行时 Jank 检测

发布时间:2026/10/15 6:24:22 来源:尧图企业网站定制
示例工程教程【免费下载链接】cw-omnibusSource code to omnibus edition of _The Busy Coders Guide to Android Development_项目地址https://gitcode.com/gh_mirrors/cw/cw-omnibus点击查看免费下载本文基于 cw-omnibus 仓库中的 FrameMetrics 示例工程Jank/FrameMetrics讲解如何利用 Android 7.0API 24引入的 frame metrics 帧指标上报 API在应用运行时量化每一帧的耗时分布从而定位 jank卡顿的成因。读完本文你将掌握Window.OnFrameMetricsAvailableListener的注册与注销、FrameMetrics.getMetric()九大类指标的含义与聚合统计方法并能在真实设备上复现一次完整的录制—滚动—停止—读日志的卡顿分析流程。示例工程速览用滚动视频列表制造并观测 jankFrameMetrics 示例是一个独立可运行的 Android 应用工程其目录结构如下源码均在 app/src/main 下Jank/FrameMetrics/ ├── README.markdown ├── app/ │ ├── build.gradle # 依赖与 SDK 配置 │ └── src/main/ │ ├── AndroidManifest.xml # 单 Activity、存储读取权限 │ ├── java/com/commonsware/android/jank/framemetrics/ │ │ ├── MainActivity.java # 监听器实现与录制控制 │ │ ├── AggregateFrameMetrics.java# 帧指标聚合统计 │ │ ├── RecyclerViewActivity.java # 复用的 RecyclerView 宿主 │ │ └── RowController.java # 视频行条目绑定 │ └── res/ │ ├── layout/row.xml # 缩略图 标题行布局 │ ├── menu/actions.xml # Record / Stop 两个菜单项 │ └── values(-v21)/ # 主题与配色 └── build.gradle / settings.gradle # 顶层 Gradle 配置这个应用从系统MediaStore中加载设备上的视频文件以 RecyclerView 列表呈现每行包含缩略图与标题见 row.xml 与 RowController.java。滚动这个列表本身就是典型的 UI 压力场景缩略图解码、布局测量、绘制都可能成为卡顿来源。应用的目的是在用户滚动列表的过程中实时记录每一帧的耗时构成供事后分析。核心 APIWindow.OnFrameMetricsAvailableListenerFrameMetrics 系列 API 从 Android 7.0API 24开始提供。其入口是Window.OnFrameMetricsAvailableListener接口在窗口绘制每完成一帧后系统会回调该监听器并携带这一帧的指标数据。示例的完整实现集中在 MainActivity.javapublic class MainActivity extends RecyclerViewActivity implements LoaderManager.LoaderCallbacksCursor, Window.OnFrameMetricsAvailableListener {注册与注销监听器MainActivity 通过菜单项控制录制的启停菜单定义见 actions.xml其中stop项默认android:visiblefalse与record互斥切换。点击Record时开始监听if (item.getItemId()R.id.record) { record.setVisible(false); stop.setVisible(true); afmnew AggregateFrameMetrics(); getWindow() .addOnFrameMetricsAvailableListener(this, new Handler(handlerThread.getLooper())); return(true); }点击Stop时注销监听并输出统计结果else if (item.getItemId()R.id.stop) { record.setVisible(true); stop.setVisible(false); getWindow().removeOnFrameMetricsAvailableListener(this); afm.log(getClass().getSimpleName()); afmnull; return(true); }要点如下addOnFrameMetricsAvailableListener(listener, handler)的第二个参数指定了回调执行的线程。示例为此专门创建了一个名为FrameMetrics的HandlerThread并设置后台优先级Process.THREAD_PRIORITY_BACKGROUNDMainActivity.java其生命周期与 Activity 对齐onCreate中start()onDestroy中quitSafely()MainActivity.java。这样做的目的是把帧指标的聚合计算从 UI 线程剥离避免统计行为本身污染正在测量的渲染流程。回调签名onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int droppedEvents)中的第三个参数droppedEvents表示自上一次回调以来被系统丢弃的帧指标事件数量。当回调处理跟不上帧生成速度时该值会大于 0示例将其一并累加统计。FrameMetrics 度量项全解每一帧到达监听器时通过FrameMetrics.getMetric(常量)读取该帧在渲染各阶段的耗时。Android 官方将其定义为纳秒ns为单位的时长指标。示例中的 AggregateFrameMetrics.java 完整覆盖了全部九类指标animationDuration metrics.getMetric(FrameMetrics.ANIMATION_DURATION); commandIssueDuration metrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION); drawDuration metrics.getMetric(FrameMetrics.DRAW_DURATION); inputHandlingDuration metrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION); layoutMeasureDuration metrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION); swapBuffersDuration metrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION); syncDuration metrics.getMetric(FrameMetrics.SYNC_DURATION); unknownDelayDuration metrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION); totalDuration metrics.getMetric(FrameMetrics.TOTAL_DURATION);各指标含义如下指标常量含义官方 API 语义卡顿排查指向ANIMATION_DURATION窗口动画如属性动画执行耗时动画回调是否过于繁重COMMAND_ISSUE_DURATION渲染命令下发耗时每帧提交的绘制命令数量与复杂度DRAW_DURATIONView.draw()阶段耗时View 树绘制复杂度、无效区域INPUT_HANDLING_DURATION输入事件处理耗时触摸/滚动事件处理是否阻塞LAYOUT_MEASURE_DURATIONlayout 与 measure 阶段耗时布局层级深度、测量计算量SWAP_BUFFERS_DURATION帧缓冲区交换swapBuffers耗时GPU 提交与同步开销SYNC_DURATION渲染管线同步/入队耗时与渲染线程/GPU 的同步等待UNKNOWN_DELAY_DURATION未被以上阶段覆盖的未知延迟其他排队等待如 vsync 等待TOTAL_DURATION整帧总耗时直接反映是否超过 16.6ms60fps 预算需要说明的是指标之间并非严格互斥TOTAL_DURATION是整帧的总体耗时当某帧总耗时明显超过 16.6ms对应 60Hz 刷新率时说明发生了 jank此时可继续查看是 draw、layout/measure、sync 还是 unknown delay 贡献了主要开销从而定位瓶颈阶段。聚合统计把逐帧数据变成可读结论逐帧打印单个指标意义有限示例采用先累加、再输出的聚合策略。AggregateFrameMetrics.java 的add()方法把每一帧的九类耗时分别累加同时累加droppedReports当用户点击 Stop 后log()一次性将各阶段的总耗时以纳秒为单位输出到 LogcatAggregateFrameMetrics.javavoid log(String tag) { Log.d(tag, String.format(animation: %dns, animationDuration)); Log.d(tag, String.format(command issue: %dns, commandIssueDuration)); Log.d(tag, String.format(draw: %dns, drawDuration)); Log.d(tag, String.format(input handling: %dns, inputHandlingDuration)); Log.d(tag, String.format(layout measure: %dns, layoutMeasureDuration)); Log.d(tag, String.format(swap buffers: %dns, swapBuffersDuration)); Log.d(tag, String.format(sync: %dns, syncDuration)); Log.d(tag, String.format(unknown: %dns, unknownDelayDuration)); Log.d(tag, String.format(total: %dns, totalDuration)); Log.d(tag, String.format(%d dropped reports, droppedReports)); }调用时传入的 tag 是MainActivityafm.log(getClass().getSimpleName())因此在 Logcat 中按MainActivity过滤即可看到完整报告。报告中各阶段耗时为整个录制周期的累加值通过比较不同阶段的总占比可以推断哪一阶段在滚动场景下消耗了最多时间dropped reports则提示统计期间回调处理是否出现过拥塞。工程配置要点SDK 与依赖app/build.gradleapp/build.gradle 中的关键配置dependencies { implementation com.android.support:recyclerview-v7:27.1.1 implementation com.android.support:support-fragment:27.1.1 implementation com.squareup.picasso:picasso:2.5.2 } android { compileSdkVersion 27 defaultConfig { minSdkVersion 24 // Android 7.0 起才有 FrameMetrics API targetSdkVersion 27 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }其中minSdkVersion 24是硬性前提——android.view.FrameMetrics与Window.OnFrameMetricsAvailableListener均为 Android 7.0API 24新增低版本设备无法使用这也是示例将 minSdk 设为 24 的直接原因。依赖方面recyclerview-v7 提供列表宿主Picasso 负责视频缩略图异步加载见 RowController.java 中.fit().centerCrop()的用法。运行时权限与界面资源示例需要读取设备上的视频信息因此 AndroidManifest.xml 声明了READ_EXTERNAL_STORAGE权限并在运行时通过ActivityCompat.requestPermissions()动态申请MainActivity.java拒绝授权时应用直接finish()退出MainActivity.java并在onSaveInstanceState中记录申请状态以正确处理配置变更。界面主题在 values-v21/styles.xml 中切换为 Material 主题并定义主色colors.xml低版本回退到 Holo 主题values/styles.xml。运行与结果解读工程使用 Gradle Wrapper 构建版本 4.4见 gradle-wrapper.properties顶层构建脚本由 build.gradle 与 settings.gradle仅包含:app模块组成。运行步骤如下用 Android Studio 打开Jank/FrameMetrics目录或在命令行执行gradle wrapper对应的./gradlew assembleDebug构建 APK安装到 Android 7.0 设备/模拟器启动 FrameMetrics Demo首次启动会请求存储权限授予后应用加载设备视频列表点击工具栏Record开始录制随后上下滚动视频列表数十秒模拟真实使用中的卡顿场景点击Stop停止录制此时聚合结果已写入 Logcat。观察 Logcat过滤MainActivity标签你会看到类似如下的输出D/MainActivity: animation: 12345678ns D/MainActivity: draw: 234567890ns D/MainActivity: layout measure: 45678901ns D/MainActivity: sync: 34567890ns D/MainActivity: unknown: 12345678ns D/MainActivity: total: 456789012ns D/MainActivity: 3 dropped reports解读建议先看total与录制帧数的关系总耗时 ÷ 帧数即为平均帧耗时超过 16.6ms 即意味着掉帧再看各分项占比例如滚动列表场景下draw或layout measure占比偏高通常指向缩略图解码、列表项布局复杂度等问题unknown占比高则提示存在未被覆盖的等待时间如 vsync 排队。进阶构造对照实验如果想更直观地体会帧指标能测出什么可以对照仓库中同属Jank目录的 ThreePaneBC 示例——该示例刻意构建了一个janky UI卡顿的界面。从源码结构可以推断将 FrameMetrics 的录制逻辑套用到这种刻意劣化的 UI 上total与draw/layout measure指标的异常会远比本示例的普通列表更明显非常适合验证指标 → 瓶颈阶段 → 优化点的完整排查链路。相关文件索引示例说明Jank/FrameMetrics/README.markdown监听器注册与录制控制MainActivity.java帧指标聚合与日志输出AggregateFrameMetrics.java列表宿主与行绑定RecyclerViewActivity.java、RowController.java权限与清单AndroidManifest.xml构建配置app/build.gradle、build.gradle菜单与布局actions.xml、row.xml卡顿对照样本Jank/ThreePaneBC/README.markdown赞分享示例工程教程【免费下载链接】cw-omnibusSource code to omnibus edition of _The Busy Coders Guide to Android Development_项目地址https://gitcode.com/gh_mirrors/cw/cw-omnibus点击查看免费下载相关推荐cw-omnibus 跨应用拖放实战基于 Android 7.0 的 Drag-and-Drop 双端实现解析cw omnibus 跨应用拖放实战基于 Android 7.0 的 Drag and Drop 双端实现解析 本篇文章围绕开源仓库 cw omnibus示例工程教程cw-omnibus 实战基于 ACTION_PROCESS_TEXT 实现 Android 文本处理集成cw omnibus 实战基于 ACTION_PROCESS_TEXT 实现 Android 文本处理集成 本指南以 cw omnibus 仓库中的 Proc示例工程教程cw-omnibus 实战基于 Android Design Support Library 的 Snackbar 轻提示示例详解cw omnibus 实战基于 Android Design Support Library 的 Snackbar 轻提示示例详解 cw omnibus 是示例工程教程上一篇如何实现Stirling-PDF与LibreOffice完美集成Office文档转PDF终极指南下一篇OneDark.nvim版本升级指南从v0.1.0到最新版平滑迁移创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑