Android中各種Time API

語言: CN / TW / HK

時間API

為了跟蹤效能,我們需要測量時間間隔,即兩個時間點之間的差異。 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() vs 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 ns

SystemClock.uptimeMillis() 中值時間:116 ns

SystemClock.uptimeMillis() 幾乎快兩倍! 雖然這種差異應該不會對應用程式產生任何有意義的影響,但我們能弄清楚為什麼它要快得多嗎?

uptimeMillis() 實現

SystemClock.uptimeMillis() 實現為帶有@CriticalNative 註釋的本機方法。 CriticalNative 為不包含物件的方法提供更快的 JNI 轉換。 public final class SystemClock { @CriticalNative native public static long uptimeMillis(); }

原生實現在 SystemClock.c++ 中: 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 版本上速度較慢,但這在這裡無關緊要。