Android Vitals - 现在几点?
现在是几奌?
uptimeMillis()
对比nanoTime()
结论
注:漂亮的标题照片来自 Romain Guy。
昨天我有一个想法:
Py⚔@piwai
有人对 Android 生产环境中性能监控的博客系列感兴趣吗?
不是像“设置 Firebase 性能监控”(感觉有点无聊)那样的,而是像“Android 的工作原理”那样的。twitter.com/Piwai /status/1…2020年7月12日 下午22:26Py⚔ @Piwai我编写了代码来检测应用程序第一次 onDraw 发生的时间,但它在 API 25 上不起作用。我花了一段时间才弄清楚!
我收到了热情的回复,于是决定开始动笔。本系列博客将专注于生产环境中 Android 应用的稳定性和性能监控。它被命名为“Android Vitals”,因为它与 Google 自己的Android Vitals紧密相关:
Android Vitals 是 Google 为提升 Android 设备稳定性和性能而推出的一项计划。当选择加入的用户运行您的应用时,他们的 Android 设备会记录各种指标,包括应用稳定性、应用启动时间、电池使用情况、渲染时间和权限拒绝次数等数据。
如果您对未来的博客有任何问题或建议,请随时通过 Twitter联系我们!
为了热身,我先从一个简单的问题开始:
现在是几奌?
为了跟踪性能,我们需要测量时间间隔,即两个时间点之间的差值。JDK 提供了两种获取当前时间的方法:
// Milliseconds since Unix epoch (00:00:00 UTC on 1 January 1970)
System.currentTimeMillis()
// Nanoseconds since the VM started.
System.nanoTime()
Android 提供了一个SystemClock
类,其中添加了一些内容:
// (API 29) Clock that starts at Unix epoch.
// Synchronized using the device's location provider.
SystemClock.currentGnssTimeClock()
// Milliseconds running in the current thread.
SystemClock.currentThreadTimeMillis()
// Milliseconds since boot, including time spent in sleep.
SystemClock.elapsedRealtime()
// Nanoseconds since boot, including time spent in sleep.
SystemClock.elapsedRealtimeNanos()
// Milliseconds since boot, not counting time spent in deep sleep.
SystemClock.uptimeMillis()
我们应该选择哪一个?SystemClock
javadoc可以帮助回答这个问题:
- System#currentTimeMillis可由用户或手机网络设置,因此时间可能会不可预测地向前或向后跳动。间隔时间或已用时间的测量应使用不同的时钟。
- SystemClock#uptimeMillis在系统进入深度睡眠时停止。这是大多数间隔计时(例如 Thread#sleep(long)、Object#wait(long) 和 System#nanoTime)的基础。此时钟适用于间隔不跨越设备睡眠的间隔计时。
- SystemClock#elapsedRealtime和SystemClock#elapsedRealtimeNanos包含深度睡眠。此时钟是通用间隔计时的推荐基础。
应用程序的性能对深度睡眠中发生的事情没有影响,所以我们最好的选择SystemClock.uptimeMillis()
是System.nanoTime()
uptimeMillis()
对比nanoTime()
System.nanoTime()
比 更精确uptimeMillis()
,但这仅适用于微基准测试。在生产环境中跟踪性能时,我们需要毫秒级的分辨率。
让我们比较一下它们对性能的影响。我克隆了Android Benchmark Samples仓库,并添加了以下测试:
@LargeTest
@RunWith(AndroidJUnit4::class)
class TimingBenchmark {
@get:Rule
val benchmarkRule = BenchmarkRule()
@Test
fun nanoTime() {
benchmarkRule.measureRepeated {
System.nanoTime()
}
}
@Test
fun uptimeMillis() {
benchmarkRule.measureRepeated {
SystemClock.uptimeMillis()
}
}
}
在运行 Android 10 的 Pixel 3 上的结果:
System.nanoTime()
中位时间:208 纳秒SystemClock.uptimeMillis()
中位时间:116 纳秒
SystemClock.uptimeMillis()
速度几乎快了一倍!虽然这种差异应该不会对应用造成任何实际影响,但我们能找出它速度更快的原因吗?
uptimeMillis()
执行
SystemClock.uptimeMillis()
作为带有注释的本机方法实现@CriticalNative
。CriticalNative为不包含对象的方法提供更快的 JNI 转换。
public final class SystemClock {
@CriticalNative
native public static long uptimeMillis();
}
(来源)
本机实现位于SystemClock.cpp
:
int64_t uptimeMillis()
{
int64_t when = systemTime(SYSTEM_TIME_MONOTONIC);
return (int64_t) nanoseconds_to_milliseconds(when);
}
(来源)
systemTime()
定义于Timers.cpp
:
nsecs_t systemTime(int clock) {
static constexpr clockid_t clocks[] = {
CLOCK_REALTIME,
CLOCK_MONOTONIC,
CLOCK_PROCESS_CPUTIME_ID,
CLOCK_THREAD_CPUTIME_ID,
CLOCK_BOOTTIME
};
timespec t = {};
clock_gettime(clocks[clock], &t);
return nsecs_t(t.tv_sec)*1000000000LL + t.tv_nsec;
}
(来源)
nanoTime()
执行
System.nanoTime()
也被实现为用 注释的本机方法@CriticalNative
。
public final class System {
@CriticalNative
public static native long nanoTime();
}
(来源)
本机实现位于System.c
:
static jlong System_nanoTime() {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return now.tv_sec * 1000000000LL + now.tv_nsec;
}
(来源)
这两个实现其实很类似,都是调用clock_gettime()
。
事实证明,最近@CriticalNative
才被添加到,这解释了为什么它更慢!System.nanoTime()
结论
在跟踪生产应用程序中的性能时:
- 对于大多数用例来说,毫秒分辨率就足够了。
- 要测量时间间隔,请使用
SystemClock.uptimeMillis()
或System.nanoTime()
。后者在旧版 Android 上速度较慢,但这在这里无关紧要。 - 我更喜欢
SystemClock.uptimeMillis()
,因为我更容易与毫秒相关。- 100 毫秒是人类不再感觉自己在直接操作 UI 中的对象(即拥有“直观”体验)的极限,而是开始感觉自己在命令计算机为他们执行操作,然后等待答案(来源)
- 100毫秒是十分之一秒,这很容易记住。但我对纳秒没有同样的快速参考框架,我必须记住1毫秒=1,000,000纳秒,然后再进行计算。
SystemClock
不在 JDK 中,因此如果您正在编写可移植代码,那么System.nanoTime()
就可以了。