JankStats 推出 alpha 版本

917 阅读8分钟

△ 插图作者: Virginia Poltrack

△ 插图作者: Virginia Poltrack

作者 / Chet Haase, Android 开发技术推广工程师

卡顿 (名词): 指应用性能糟糕,可能导致丢帧、界面动画不连贯和用户体验不佳等问题。请参阅 "不开心的用户" 词条。

性能问题很难调试。我们常常不清楚要从何下手、使用何种工具、用户遇到了什么问题,以及那些问题在现实的设备上有何表现。

过去几年间,Android 团队一直努力推出更多工具,用于调试各种问题,从分析 启动性能 到测试 具体代码路径,再到测试和优化特定 用例IDE 中的可视化分析器,各领域均有涉猎。所有这些工具均针对开发期间的测试设计,用于帮助您调试和修复在本地运行时发现的问题。

同时,Google Play 的 Android VitalsFirebase 均提供信息中心,供开发者了解其应用在实际用户设备上的运行情况。

尽管如此,在实际情况中,我们仍然很难发现应用中可能存在的问题,尤其是用户设备上可能出现的问题。这可不是您坐在座椅中用着熟悉的开发机器能碰到的问题。虽说性能信息中心可提供一定帮助,但在用户遇到问题时,它却未必能让您充分了解所发生情况的详细信息。

JankStats 应运而生: 这是首个专为在用户设备上检测及报告应用的性能问题而构建的 AndroidX 库。

JankStats 是占用空间相对较小的 API,主要有三大目标: 捕获每帧的性能信息、在用户设备 (不仅是开发设备) 上运行、以及在应用出现性能问题时启用检测,并报告所发生的情况。

每帧性能

Android 平台已提供多种方法,用于获取帧性能数据。例如,从 API 24 开始就可以使用 FrameMetrics 获取相关数据,后续多个版本也在进一步丰富该功能,以便为您提供更多详细信息。如果在更早期的版本上运行应用,也有多种方法可供您获取时间信息,虽说不够准确,但仍十分实用。

因此,如果您想确保自己的帧持续时间逻辑适用于所有版本,就需要在不同的 API 版本中实现不同的测试和报告机制。现在,您可以使用统一的 JankStats API 来实现这些功能。除此之外,它还提供了更多惊喜 (请继续阅读本文!)。

JankStats 通过提供单一 API 来报告每帧的时间,从而简化您的工作,并会在内部委派适当机制 (比如 API 24 以上会委派给 FrameMetrics)。您不必关心这些数据的来源,只需让 JankStats 告诉您完成特定事项花费的时间,然后便可在回调中获取相关信息。

创建和监听 JankStats 数据就是这么简单: 只需完成创建,然后就可以坐下来 (好吧,是您的代码 "坐" 下来) 监听。以下是 JankStats 的示例 JankLoggingActivity 中的步骤范例:

val jankFrameListener = JankStats.OnFrameListener { frameData ->
 // real app would do something more interesting than log this...
 Log.v("JankStatsSample", frameData.toString())
}
jankStats = JankStats.createAndTrack(
   window,
   Dispatchers.Default.asExecutor(),
   jankFrameListener,
)

此处的 Log.v() 调用仅作范例使用,并非您在应用中应采取的操作。在实际操作中,您可能应汇整/储存/上传数据,以供日后分析使用,而非将数据发布于日志中。无论如何,下面是在 API 30 模拟器上运行时产生的输出示例 (为便于阅读,已删除部分 logcat 的输出内容,并添加了空白行):

JankStats.OnFrameListener: FrameData(frameStartNanos=827233150542009, frameDurationUiNanos=27779985, frameDurationCpuNanos=31296985, isJank=false, states=[Activity: JankLoggingActivity])

JankStats.OnFrameListener: FrameData(frameStartNanos=827314067288736, frameDurationUiNanos=89903592, frameDurationCpuNanos=94582592, isJank=true, states=[RecyclerView: Dragging, Activity: JankLoggingActivity])

JankStats.OnFrameListener: FrameData(frameStartNanos=827314167288732, frameDurationUiNanos=88641926, frameDurationCpuNanos=91526926, isJank=true, states=[RecyclerView: Settling, RecyclerView: Dragging, Activity: JankLoggingActivity])

JankStats.OnFrameListener: FrameData(frameStartNanos=827314183945923, frameDurationUiNanos=4731405, frameDurationCpuNanos=8283405, isJank=false, states=[RecyclerView: Settling, Activity: JankLoggingActivity])

您可以在日志的 frameData 中看到一些有趣的内容:

  • 其中有部分帧带有 isJank=true 标记。该日志取自运行的示例应用 JankLoggingActivity,您可查看 完整示例 了解更多。该应用会强制产生一些长帧 (没错,用了 Thread.sleep()!),从而让 JankStats 判定其为卡顿。

  • 帧的时间信息中同时包含界面和 CPU 数据,但在 API 24 (FrameMetrics 被引入的版本) 之前的版本中,此信息仅包含界面持续时间。

  • 该日志是从我在应用中开始滑动 RecyclerView 时获取的。当 RecyclerView 开始移动 (被 "拖动") 以及 RecyclerView 开始自由滚动 (被 "放置") 时,我们可在开始之前看到与界面状态相关的信息 (仅列出 Activity 状态)。有关这些界面状态的详细信息,请阅读下文。

真实数据

不同于最近的基准库,创建 JankStats 的目的是为您提供来自用户设备的结果。能在开发机器上调试问题固然很好,但在现实中,用户会根据迥异的约束条件,在不同的设备上使用您的应用,对于这类情况,本地调试可能并不能发现和解决问题。

JankStats 提供 API 来检测您的应用,以提供您所需的性能数据和报告机制,以便您能上传这些数据并离线进行分析。

应用状态

最后 (请注意,这才是 JankStats 库的新亮点),JankStats 提供了一种方法,可让您了解出现性能问题时应用中实际发生的情况。我们经常听到的抱怨是: 现有的工具、信息中心和方法均未能提供足够的背景信息,不足以让您知晓用户实际遭遇到的性能问题。

例如,FrameMetrics API (在 API 24 版本中推出,JankStats 内部也有使用) 可以告诉您绘制帧需要多长时间,而您也可从中获取卡顿信息,但它无法让您知晓当时应用中的具体情况。当您尝试检测代码,并将其与 FrameMetrics 或其他性能测量工具集成时,该问题就需要开发者自行解决。但是,除非必须要在内部构建这种基础架构,那每个人都有许多别的工作要做。因此,卡顿问题通常得不到量化测试,而性能问题自然也无法解决。

同样,Android Vitals 信息中心也可以告诉您,应用存在性能问题,但无法告诉您问题发生时应用的具体运行情况。因此,您很难通过这些信息来知晓应该如何处理出现的问题。

JankStats 推出了 PerformanceMetricsState API,这套简单的方法可让您通过成对的字符串告诉系统在任意时刻您的应用所发生的事情。例如,您可能想知道,某个特定的 ActivityFragment 在何时处于活动状态,或 RecyclerView 何时处于滚动状态。

例如,下面是 JankStats 示例中的代码,表明该工具如何检测 RecyclerView,以向 JankStats 提供此信息:

val scrollListener = object : RecyclerView.OnScrollListener() {
 override fun onScrollStateChanged(recyclerView: RecyclerView,
                                   newState: Int)
 {
   val metricsState = metricsStateHolder?.state ?: return
     when (newState) {
       RecyclerView.SCROLL_STATE_DRAGGING -> {
         metricsState.addState("RecyclerView", "Dragging")
       }
       RecyclerView.SCROLL_STATE_SETTLING -> {
         metricsState.addState("RecyclerView", "Settling")
       }
       else -> {
         metricsState.removeState("RecyclerView")
       }
    }
 }
}

此状态可在您应用中的任何地方 (甚至从其他库) 注入,当其报告结果时,会被 JankStats 接收到。这样一来,当您从 JankStats 获取报告时,不仅可以知道每帧里各种事件花费的时间,还可以了解用户在那一帧期间做了什么,这可能会是相当有用的信息。

资源

下面这些资源可以帮助您了解有关 JankStats 的更多信息:

AndroidX 项目 : JankStats 位于 AndroidX 的 androidx.metrics 库中。

文档 : 开发者网站提供了新的 开发者指南,其中介绍了 JankStats 的用法。

示例代码 : 示例项目 展示了如何将 JankStats 对象实例化并进行侦听,以及如何针对重要的界面状态信息来监测应用。

错误报告 : 若您对该库有任何疑问,或是想提出 API 需求,欢迎向我们 提交错误报告

Alpha -> 1.0

JankStats 刚刚发布了首个 alpha 版本,这次发布的用意是: "我们认为这个 API 和功能会对 1.0 版本的发布颇有帮助,请先试用,并和我们分享您的反馈。"

今后我们还想针对 JankStats 做其他事情,包括添加某种聚合机制,甚至与现有的上传服务同步。不过,在推出首个版本之前,我们希望了解大家的使用情况,以及搜集大家想要的其他功能。我们希望这一版本在当前的基本状态下能对大家有所帮助。仅仅是轻松检测并记录界面状态信息这个功能,应该就可以为大家提供一些便利。

现在就请大家 获取 并试用此版本,我们恭候大家提出的 反馈。最重要的是,我们希望大家能借助 JankStats 找出并修复性能问题!您的用户正等着您呢,别让他们等太久了!

欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!