1.手机系统时间存在的问题
Android系统或者Java程序在打时间戳时,经常使用System.currentTimeMillis(),如果直接用这个函数来打时间戳,会存在潜在的问题,即无法保证单调性和顺序性,对于这个函数,Java官方解释是这样的
currentTimeMillis
public static long currentTimeMillis()
Returns the current time in milliseconds. Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the underlying operating system and may be larger. For example, many operating systems measure time in units of tens of milliseconds.
See the description of the class Date for a discussion of slight discrepancies that may arise between "computer time" and coordinated universal time (UTC).
Returns:
the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
currentTimeMillis是1970年1月1日到当前时间经过的毫秒数,这个时间依赖系统日期,其底层实现就是直接获取系统日期,不管是家用机还是服务器,亦或是手机,系统日期一般都不准确,需要校准机制,比如NTP协议,考虑这样的情况,系统时钟走快了10秒钟,编程者调用currentTimeMillis在11:00:10时间点打下时间戳A,5秒后,NTP开始修正系统时钟,因为系统时钟快了10秒,这时NTP会将系统时钟修正为11:00:05,如果这是打下时间戳B,明明时间戳B发生在时间戳A五秒后,但是时间戳却是:
A 11:00:10
B 11:00:05
A时间戳反倒比B时间戳晚了5秒。
为了解决这种情况,保证时间戳的顺序性,NTP协议有一种平滑同步机制,当NTP要把系统时钟往回拨的时候,平滑同步机制其作用,这时候,NTP会让系统时钟停住,直到系统时间与实际时间重合,然后松开系统时钟,过程如下图,假设系统时间快了10秒:
| 系统时钟 | 真实时间 |
|---|---|
| 11:00:10 | 11:00:00 |
| 11:00:11 | 11:00:01 |
| 11:00:12 | 11:00:02 |
| 11:00:13 | 11:00:03 |
| 11:00:14 | 11:00:04 |
| 11:00:15 (此时校准开始,锁住系统时钟) | 11:00:05 |
| 11:00:15 | 11:00:06 |
| 11:00:15 | 11:00:07 |
| 11:00:15 | 11:00:08 |
| 11:00:15 | 11:00:09 |
| 11:00:15 | 11:00:10 |
| 11:00:15 | 11:00:11 |
| 11:00:15 | 11:00:12 |
| 11:00:15 | 11:00:13 |
| 11:00:15 | 11:00:14 |
| 11:00:15 (系统时间与真实时间一致,松开系统时钟) | 11:00:15 |
| 11:00:16 | 11:00:16 |
| 11:00:17 | 11:00:17 |
这种机制虽然保证了时间戳的单调性,但是却有一个Bug,这也是这篇博客要解决的问题,即在系统时钟锁定期间,所有时间戳是一样的,在真实时间11:00:05到11:00:15之间产生的时间戳,都是11:00:15!
在PC或者服务器上,这种情况出现并不多,影响系统时钟的主要因素是温度,PC和服务器所处环境温度较为恒定,NTP校准频率也不低,产生上述Bug的概率不高。
但是在手机上,手机温度变化很大,不管是从室外进入室内的室温变化,还是小米之类的手机自身发热问题,都让系统时钟精度极大降低,而手机的时间校准机制不同于服务器,手机上的时间优先获取基站时间,因为手机要和基站通信,时间必须与基站同步,而国内的基站时间本身就不精确!各个基站之间时间并不一致,这导致用户在从一个基站移动到另一个基站后,手机时间会有较大幅度校准!出现上述Bug的概率非常大。
因基站时间不同导致的怪闻,时光隧道现象:
http://www.chinanews.com/sh/2013/05-23/4846887.shtml
2.解决方案
在时间戳中加入CPU计时器值,可以解决上述问题,Java中,通过nanoTime获取这个值。IOS中,用mach_absolute_time()函数获取这个值。下面只针对nanoTime讲解。
System.nanoTime()
public static long nanoTime()
Returns the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds.
This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time. The value returned represents nanoseconds since some fixed but arbitrary origin time (perhaps in the future, so values may be negative). The same origin is used by all invocations of this method in an instance of a Java virtual machine; other virtual machine instances are likely to use a different origin.
This method provides nanosecond precision, but not necessarily nanosecond resolution (that is, how frequently the value changes) - no guarantees are made except that the resolution is at least as good as that of currentTimeMillis().
Differences in successive calls that span greater than approximately 292 years (2^63 nanoseconds) will not correctly compute elapsed time due to numerical overflow.
The values returned by this method become meaningful only when the difference between two such values, obtained within the same instance of a Java virtual machine, is computed.
For example, to measure how long some code takes to execute:
long startTime = System.nanoTime();
// ... the code being measured ...
long estimatedTime = System.nanoTime() - startTime;
To compare two nanoTime values
long t0 = System.nanoTime();
...
long t1 = System.nanoTime();
one should use t1 - t0 < 0, not t1 < t0, because of the possibility of numerical overflow.
Returns:
the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds
nanoTime获取的时间与系统时钟无关,其底层实现是利用多数CPU都有的内置计时器,nanoTime获取的时间是这个计时器启动到现在的时间,单位是纳秒。
虽然单位是纳秒,但是精度并非纳秒级,Java并不保证这个计时的精度,这个计时器的最大值是292.3年。
在Android中,打时间戳时,应该同时打上currentTimeMillis和nanoTime的值,在后续数据处理时,如果遇到currentTimeMillis值一样的情况,可以通过nanoTime来计算时间戳之间的顺序及时间间隔。

本文探讨了Android系统中使用currentTimeMillis()获取时间戳存在的问题,包括系统时间依赖NTP校准可能导致的时间跳跃和单调性丢失。为解决此问题,建议结合System.nanoTime()来获取更精确且具有顺序性的时序信息,以确保时间戳的正确性。在手机设备上,由于温度变化和基站时间不精确,此类问题尤为突出。

1639

被折叠的 条评论
为什么被折叠?



