SystemClock.elapsedRealtime() 和 System.currentTimeMillis()

这两者都是获取时间戳的重要方法,但它们的设计目的、特性和适用场景有根本性的不同。用错它们会导致应用出现极其诡异且难以调试的bug。


核心结论

  • SystemClock.elapsedRealtime() : 用于测量时间间隔。它是一个单调递增的时钟,只关心过了多久,不关心"现在几点"。最适合用于计时、性能监控、超时判断等。
  • System.currentTimeMillis() : 用于获取日历时间(Wall Time)。它表示从UTC时间1970年1月1日午夜开始的毫秒数。它会因为用户修改时间、网络自动同步、时区切换等原因而发生跳跃。最适合用于记录事件发生的具体日期和时间。

详细对比分析

特性 SystemClock.elapsedRealtime() System.currentTimeMillis()
时间基准 从系统启动(包括深度睡眠)开始计算的时间。 Unix 纪元(1970-01-01 00:00:00 UTC) 开始计算的时间。
单调性 是单调时钟。时间只会向前走,不会因为系统时间被修改而回退或跳跃。 非单调时钟 。时间可能被用户、网络时间协议(NTP)或时区变化而向前或向后修改
休眠影响 包括设备深度睡眠(休眠)的时间。即使屏幕关闭,CPU休眠,这个值也在持续增加。 不包括休眠时间。当设备休眠时,这个时间实际上是"冻结"的。
主要用途 测量时间间隔、耗时、性能分析、超时检测、闹钟等需要稳定时间基准的场景。 记录事件发生的具体日期和时间,如日志时间戳、消息发送时间、日历事件等。
可变性 不可变。它的增长只与物理时间的流逝有关,用户无法改变。 可变。用户可以在系统设置中轻松修改这个时间,系统也会自动根据网络进行校准。

场景化示例

1. 计时器或秒表功能

假设你要做一个跑步App,需要记录用户跑步用了多久。

  • 错误做法

    java 复制代码
    long startTime = System.currentTimeMillis(); // 开始跑步
    // ... 用户跑步中 ...
    long elapsedTime = System.currentTimeMillis() - startTime; // 计算耗时

    风险 :如果用户在跑步过程中接到了电话,网络同步了时间,或者他自己修改了系统时间,你的 elapsedTime 会变得完全不准,可能突然变成负数或一个巨大的值。

  • 正确做法

    java 复制代码
    long startTime = SystemClock.elapsedRealtime(); // 开始跑步
    // ... 用户跑步中,即使设备休眠也没关系 ...
    long elapsedTime = SystemClock.elapsedRealtime() - startTime; // 计算真实耗时

    优点 :无论系统时间如何变化,设备是否休眠,elapsedTime 都能准确反映用户实际跑步花费的时间。

2. 记录消息发送时间

你要在聊天App中显示每条消息发送的准确时间。

  • 正确做法

    java 复制代码
    long timestamp = System.currentTimeMillis(); // 消息发送的时刻
    // 将 timestamp 保存到数据库,并发送给服务器
    // 在UI上显示时,需要转换成日期字符串:new Date(timestamp).toString() 或使用 SimpleDateFormat

    原因:你需要的是这个消息在"真实世界"中发生的时间点,而不是一个相对的时间间隔。即使用户后来修改了时间,这条消息记录的是"当时"的时间。

  • 错误做法

    java 复制代码
    long timestamp = SystemClock.elapsedRealtime(); // 千万不要这样做!

    后果:你得到的是一个从手机开机以来的相对时间,完全无法还原成可读的日期和时间(除非你同时记录了开机时刻的绝对时间,但这非常复杂且不必要)。


进阶细节与最佳实践

  1. SystemClock.uptimeMillis() : 这也是一个单调时钟,但它不包括设备深度睡眠的时间 。它常用于需要CPU持续运行的场景,例如Handler的延时任务 (postDelayed)。系统会保证在 uptimeMillis() 到达指定时间前,即使设备休眠也会唤醒CPU来执行任务。而 elapsedRealtime() 是包括休眠时间的。

  2. AlarmManager 的设置

    • 设置闹钟时,使用 ELAPSED_REALTIMEELAPSED_REALTIME_WAKEUP 作为闹钟类型,可以保证闹钟在精确的时间间隔后触发,不受系统时间修改的影响。
    • 使用 RTCRTC_WAKEUP 作为闹钟类型,则会在一个特定的日历时间点触发,例如提醒你在明天上午9点开会。
  3. 性能监控 :在代码块前后使用 elapsedRealtime() 来测量执行耗时是标准做法,因为它稳定可靠。

    java 复制代码
    long start = SystemClock.elapsedRealtime();
    // ... 执行一些操作 ...
    long cost = SystemClock.elapsedRealtime() - start;
    Log.d("Performance", "操作耗时: " + cost + " ms");
  4. 混合使用:在某些复杂场景下,你可能需要混合使用两者。例如,你想实现一个"在明天早上8点提醒我"的功能,但需要保证即使用户修改了时间,提醒也能准确触发。一个常见的策略是:

    • System.currentTimeMillis() 计算出明天早上8点的绝对时间戳。
    • 当前 elapsedRealtime() + (明天8点的时间戳 - 当前 currentTimeMillis()) 来计算出相对于单调时钟的延迟时间。
    • 使用 AlarmManager.set(ELAPSED_REALTIME_WAKEUP, calculatedDelay, pendingIntent) 来设置闹钟。

总结

如果你的需求是... 那么你应该使用...
测量一段代码的执行时间、给操作计时、实现秒表 SystemClock.elapsedRealtime()
设置一个相对于现在的延时(如下载超时15秒) SystemClock.elapsedRealtime()
记录事件发生的具体日期和时间(用于显示) System.currentTimeMillis()
设置一个在特定日历时间点发生的提醒 System.currentTimeMillis() (通常与AlarmManager的RTC类型配合)
Handler的postDelayed或需要CPU唤醒的定时任务 SystemClock.uptimeMillis() (系统内部使用)

牢记这条黄金法则:问"过了多久?"用 elapsedRealtime;问"现在几点?"用 currentTimeMillis 遵循这个原则可以避免绝大多数时间相关的错误。

相关推荐
低调小一4 小时前
深入理解 Android targetSdkVersion:从 Google Play 政策到依赖冲突
android
皆过客,揽星河4 小时前
Linux上安装MySQL8详细教程
android·linux·hadoop·mysql·linux安装mysql·数据库安装·详细教程
catchadmin5 小时前
开发 PHP 扩展新途径 通过 FrankenPHP 用 Go 语言编写 PHP 扩展
android·golang·php
花城飞猪6 小时前
Android系统框架知识系列(二十):专题延伸:JVM vs ART/Dalvik - Android运行时演进深度解析
android·jvm·dalvik
用户2018792831676 小时前
故事:老王的图书馆HashMap vs 小张的现代科技SparseArray
android
用户2018792831676 小时前
故事:两个图书馆的比喻ArrayMap
android
用户2018792831676 小时前
SparseArray、SparseIntArray 和 SparseLongArray 的差异
android
2501_916013747 小时前
App 上架全流程指南,iOS App 上架步骤、App Store 应用发布流程、uni-app 打包上传与审核要点详解
android·ios·小程序·https·uni-app·iphone·webview
牛蛙点点申请出战7 小时前
仿微信语音 WaveView 实现
android·前端·ios