`Clock.getDefault()` isn’t configurable; add platform-aware selection & override hooks
Component(s): sdk, common, bootstrap
Environment: Cross-platform (JVM & Android)
Summary
Clock.getDefault() effectively hardwires System.nanoTime(). On Android this ignores deep sleep, leading to incorrect elapsed-time semantics for features like timeouts and background durations. There’s no easy global way to swap the default clock per platform.
Expected behavior
The SDK should choose a platform-appropriate default clock and allow an explicit override:
- JVM/server:
System.nanoTime()is fine for monotonic elapsed durations. - Android: prefer
SystemClock.elapsedRealtimeNanos()(includes deep sleep).
Actual behavior
Users cannot globally configure the default clock. Each component must be hand-wired (if possible), which is fragile and easy to miss.
Proposed change
Introduce a lightweight, platform-aware ClockProvider SPI with discovery (optimized for Android, similar in spirit to kotlinx.coroutines’ FastServiceLoader):
Hi @Sabaev in the PR https://github.com/open-telemetry/opentelemetry-android/pull/1381 I have created a AndroidClock which for android replaces the Clock.getDefault() usage with AndroidClock.INSTANCE usage. This should fix the handling in session timeout and app startup calculation
Though the usages from java side will not be fixed and in that case your proposed solution of service loader can be used
SystemClock.elapsedRealtimeNanos() is not suitable as it returns the time since boot, rather than the wall-clock time. System.nanoTime() is suitable for elapsed durations but doesn't provide a wall-clock time.
IMO the best approach to calculate wall-clock time is to obtain a baseline reading of System.currentTimeMillis() with SystemClock.elapsedRealtimeNanos() subtracted, and then for subsequent measurements add SystemClock.elapsedRealtimeNanos(). We follow this approach in the Embrace Android SDK: https://github.com/embrace-io/embrace-android-sdk/blob/main/embrace-android-utils/src/main/kotlin/io/embrace/android/embracesdk/internal/clock/NormalizedIntervalClock.kt
There are also alternative time source APIs in newer versions of Android such as GnssClock and currentNetworkTimeClock that might be worth investigating
Also related to #1363.
Here is the approach I’m currently using:
Shared Clock abstraction.
In our internal project we expose our own Clock interface, accessible via a static companion object. This allows us to retrieve a clock instance from anywhere — Kotlin modules, Java modules, Android code, or even inside ContentProviders — without worrying about initialization order.
Bootstrap layer.
For initialization we use a custom ServiceProvider, optimized for R8 to avoid reflection overhead.
Android implementation.
Clock.now
On Android API ≥ Tiramisu, we use SystemClock.currentNetworkTimeClock().
On API < Tiramisu, we follow the same “anchored time” technique used by Embrace (combining wall-clock time with a monotonic clock).
Clock.nanoTime
for monotonic clock we always use SystemClock.elapsedRealtimeNanos()
Fallback logic.
If currentNetworkTimeClock() throws a DateTimeException, we fall back to the anchored-time implementation
For monotonic time on Android, SystemClock.elapsedRealtimeNanos() is generally a better choice than System.nanoTime(). The reason: System.nanoTime() does not include time spent in deep sleep, so duration calculations (timeouts, background intervals, etc.) become inaccurate on real devices.
The Android documentation explicitly states:
The value returned by this method does not account for elapsed time during deep sleep. For timekeeping facilities available on Android, see android.os.SystemClock.
Android doc for System.nanoTime(): 🔗 https://developer.android.com/reference/java/lang/System#nanoTime()