androidinterview.com

Android System Design Interview Questions

How do you get accurate time in Android?

Tier: Less commonDifficulty: Medium

You do not get trustworthy wall clock time from the device, because the user can set it to anything. So you ask a source you trust once, and from then on you count forward with a clock the user cannot touch. For anything that is only a duration, you never need a date at all, you need SystemClock.elapsedRealtime().

The clocks Android gives you do different jobs, and picking the wrong one is the actual trap in this question.

  • System.currentTimeMillis(), wall clock time. This is the value the user changes in Settings. The device normally sets it from the carrier over NITZ or from a network time server, and it can simply be wrong.
  • SystemClock.elapsedRealtime(), milliseconds since boot, including deep sleep. This is the one for a session length, a timeout or a retry backoff, because nothing the user does moves it.
  • SystemClock.uptimeMillis(), milliseconds since boot, excluding deep sleep. Right for animation and UI timing, wrong for anything that has to keep counting while the device sleeps.
  • System.nanoTime(), the JVM monotonic clock. It is what people reach for out of habit from server code. On Android it is not guaranteed to advance during deep sleep, so elapsedRealtime() is the answer here instead.
  • SystemClock.currentNetworkTimeClock(), added in API 31. It hands you a java.time.Clock built on the network time the platform already syncs. It throws DateTimeException when no sync has happened yet, so you still need a fallback behind it.

The offset technique

For a trusted date, take the time from your backend once and remember how far it sits from the monotonic clock. Everything after that is arithmetic, with the device clock out of the picture.

// At sync time, from the server's response.
val offset = serverNowMillis - SystemClock.elapsedRealtime()

// Every read after that. Nothing here can be changed in Settings.
fun trustedNow(): Long = offset + SystemClock.elapsedRealtime()

The one thing to remember is that elapsedRealtime() restarts at zero on boot, so a persisted offset is meaningless after a reboot. Re-sync on boot, and re-sync on a schedule to keep drift small.

Where the trusted time comes from

Your own server's response is the cheap version and it is usually enough, because you need it to be honest rather than accurate to the millisecond. Reading a Date header off a request you were making anyway costs nothing extra. Raw SNTP from an app is more work than people expect, since there is no platform API for it, which is why most apps do not bother. On API 31 and up, currentNetworkTimeClock() gives you the platform's own synced time without a round trip.

Tradeoffs I'd call out

  • A round trip against freshness. Fetching server time costs a request, so you cache the offset and re-sync on a schedule rather than per read.
  • No network, no trusted time. A cold start offline has nothing to anchor to. Decide what the feature does then, block, or run on device time and mark the result as untrusted.
  • Client checks are a courtesy. Expiry of a token or a promotion has to be validated on the server. A client side check on System.currentTimeMillis() is defeated by changing the clock, so the client check is there to give a good message, not to enforce anything.

Read more SystemClock (opens in a new tab)