华为手机上,System.currentTimeMillis() 返回的时间戳不正确

33
问题在于System.currentTimeMillis()返回错误的毫秒数,主要出现在将来的不同时间范围内,有时长达6个月,但从几秒到几个月不等。
此问题发生在平板电脑型号为Huawei M2-A201W,安卓系统版本为5.1.1,内核版本为:**3.10.74-gdbd9055**
我的第一个想法是NTP与时间发生了混乱,但我有成千上万台这些平板电脑,其中一些没有网络连接、没有SIM卡,所以没有GSM / 3G / 4G。
我使用System.currentTimeMillis()将行创建时间保存在本地sqlite数据库中的列中。
我使用的这些平板电脑中很常见(每次调用System.currentTimeMillis()约30%的情况下)。

1
它每次都返回错误的时间,还是只有大约30%的调用会出错?如果它大多数情况下都能正常工作,你可以连续调用它10次,然后从返回的值中猜测哪一个是正确的(大多数应该是正确的)。 (但答案看起来更有趣,这是第二选择)。 - Ped7g
如果我使用SQLite数据库生成时间戳,但并不是每次都这样做,我们如何知道它是否真正起作用?那么,Sqlite实际上使用了哪种本地方法呢? - Arlind Hajredinaj
1
由于你遇到了某种错误,回答它的最好方法是尝试一下。做一些应用程序(如果你不能轻松地使用当前修改后的应用程序进行测试),它将随机插入一些数据到数据库中(可能与一些for延迟循环交错进行一些计算,以不与任何基于定时器的东西同步(如delay(msec))),然后检查数据,如果存储的时间戳均匀增长并与当前时间相连,或者数据中存在一些跳跃。如果确实是30%,那么你应该很容易在几千条记录中看到它。 - Ped7g
我会尝试您的解决方案。由于设备离线,我正在尝试获取GPS时间。 - Arlind Hajredinaj
请查看Sammy T的答案:https://dev59.com/IZ7ha4cB1Zd3GeqPlZXB - sailfish009
显示剩余4条评论
6个回答

7
当你没有SIM卡,因此没有GSM/3G/4G信号时,你的手机无法根据网络提供的时间/时区更新正确的时间。因此,带有网络的设备显示正确的时间,而其他没有网络的设备可能会显示错误的时间,需要手动设置正确的时间。System.currentTimeMillis()从系统中读取时间,但在断电后,时钟将不起作用。
检查是否有应用程序使用Socket或DatagramSocket阻止了NTP(UDP端口123)。注意:NTP适用于所有主机或路由器的时钟必须相同的情况。如果您的设备切换到两个(或多个)不同的网络并从不同的来源获取时间更新,则可能会波动时间。
最终,您的系统时间正在更改,这就是为什么它波动的原因。如果您在手动禁用自动日期和时间后手动System.currentTimeMillis(),我相信它不会波动(没有异常)。如果是这种情况,那么你的华为平板电脑没有漏洞。

7
作为使用System.currentTimeMillis()的解决方法,也许你可以让sqlite处理时间戳的创建,看看是否能解决你的问题?
使用timestamp default current_timestampdefault(strftime('%Y-%m-%d %H:%M:%f', 'now'))来定义/更改“created”列,如果你需要毫秒级别的时间戳,就像这样: sqlite> create table my_table(id integer primary key autoincrement not null, name text, created timestamp default(strftime('%Y-%m-%d %H:%M:%f', 'now')) not null); sqlite> insert into my_table(name) values ('MyTestRow1');
sqlite> insert into my_table(name) values ('MyTestRow2'); sqlite> select * from my_table; 1|MyTestRow1|2017-08-07 10:08:50.898
2|MyTestRow2|2017-08-07 10:08:54.701

SQLite 如何生成时间戳?它是否也使用了 System.currentMillis 的同一本地调用呢? - Arlind Hajredinaj
2
看起来他们有自己的C实现方式,http://www.sqlite.org/src/doc/trunk/src/date.c 不过不确定它是否能解决你的问题,你需要尝试并在显示奇怪结果的设备上进行测试。 - jbilander

6
Android平台中,Java API调用System.currentTimeMillis()会使用POSIX APIgettimeofday获取毫秒级时间。详见此处
static jlong System_currentTimeMillis(JNIEnv*, jclass) {
    timeval now;
    gettimeofday(&now, NULL);
    jlong when = now.tv_sec * 1000LL + now.tv_usec / 1000;
    return when;
}

它假设每次调用gettimeofday都会成功。我猜你的问题可能就出在这里。

最好检查每个API调用的返回值,确定如果发生错误应该做什么。

因此,我建议在JNI中使用更可靠的方法,并像下面这样实现自己的方法。按顺序调用这些POSIX API,如果gettimeofday失败,则调用clock_gettime,如果再次失败,则调用time

struct timeval now;
if (gettimeofday(&now, NULL) != 0) {
    struct timespec ts;
    if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
        now.tv_sec = ts.tv_sec;
        now.tv_usec = ts.tv_nsec / 1000LL;
    } else {
        now.tv_sec = time(NULL);
        now.tv_usec = 0;
    }
}
jlong when = now.tv_sec * 1000LL + now.tv_usec / 1000LL;
__android_log_print(ANDROID_LOG_INFO, "TAG", "%lld", when);

你能解释一下其他API调用clock_gettime()和time()与clock_gettime()方法有什么不同吗? - Arlind Hajredinaj

4

如果在Linux内核中运行"date +%s"命令,能否本地获取时间戳?

这里的"+%s"是自1970年01月01日00:00:00 UTC以来的秒数。(GNU Coreutils 8.24 Date手册)

  try {
            // Run the command
            Process process = Runtime.getRuntime().exec("date +%s");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));

            // Grab the results
            StringBuilder log = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                log.append(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

如果您打印此文档,
Log.e("unix_time: ", "" + log.toString());

您会得到一个Unix时间戳,例如1502187111 要将其转换回日期对象,请乘以1000,因为Java需要毫秒。
Date time = new Date(Long.parseLong(log.toString()) * 1000);
Log.e("date_time: ", "" + time.toString());

这将为您提供一个纯文本日期格式,例如2017年08月08日星期二,GMT+06:00 16:15:58


你认为为什么直接获取时间戳与System.currentMillis有所不同? - Arlind Hajredinaj
1
我怀疑问题与硬件无关,而是与固件有关。很可能是因为所有华为手机都经过了大量的软件定制,例如Emotion UI。再次强调,我并不完全确定,但我强烈建议您也测试一下内核的结果。@Arlind - Prokash Sarkar
另外,请确保所有设备都与手机设置中的相同时间同步。@Arlind - Prokash Sarkar

4
由于您提到大部分的通话都在正确时间进行,只有30%的情况是不正确的,因此我建议创建一个接收器来接收 ACTION_TIME_CHANGEDACTION_TIMEZONE_CHANGED 意图广播,以便查找何时更改了时间。也许这会给你一些线索,告诉你是什么改变了时间。
ConnectivityManager 一起使用,可以检测设备是否已连接及连接类型,或许某些连接正在触发时间更改。
// init the register and register the intents, in onStart, using:
receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      getNetworkInfo();
      if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))
         Log.d(this.getClass().getName(), "Detected a time change. isWifiConn: " +
             isWifiConn + " isMobileConn: " + isMobileConn);
      if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction()))
         Log.d(this.getClass().getName(), "Detected a timezone change. isWifiConn: " +
             isWifiConn + " isMobileConn: " + isMobileConn);
    }
};
IntentFilter filters = new IntentFilter();
filters.addAction(Intent.ACTION_TIME_CHANGED);
filters.addAction(Intent.ACTION_TIMEZONE_CHANGED);
registerReceiver(receiver, filters);
Log.d(DEBUG_TAG, "Receiver registered");
// do not forget to unregister the receiver, eg. onStop, using:
unregisterReceiver(receiver);
//...
private void getNetworkInfo() {
    ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
    isWifiConn = networkInfo.isConnected();
    networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
    isMobileConn = networkInfo.isConnected();
    Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
    Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);
}

1

这个设备是否有真正的硬件RTC?我在谷歌搜索和阅读规格说明书时没有找到确定的答案。运行以下命令:

$ dmesg -s 65535 | grep -i rtc

在命令行中执行应该会得到答案。你应该看到类似于这样的内容(取决于芯片组和内核版本):

[    3.816058] rtc_cmos 00:02: RTC can wake from S4
[    3.816429] rtc_cmos 00:02: rtc core: registered rtc_cmos as rtc0
[    3.816510] rtc_cmos 00:02: alarms up to one month, y3k, 242 bytes nvram, hpet irqs

如果grep没有返回任何消息(并且你已经针对整个内核消息缓冲区进行了grep),那么这就是答案。这些设备上没有时钟来维护时间。你始终需要NTP和一个可用的网络连接到互联网,以使没有时钟芯片的设备与世界时间保持同步。
实时时钟:https://zh.wikipedia.org/wiki/%E5%AE%9E%E6%97%B6%E6%97%B6%E9%92%9F

<6>[3005741.222717s][2017年08月14日09:31:48][pid:3293,cpu0,system_server]rtc-pl031 fff04000.rtc: pl031_suspend+。 <6>[3005741.222747s][2017年08月14日09:31:48][pid:3293,cpu0,system_server]rtc-pl031 fff04000.rtc: pl031_suspend-。 <4>[3005750.005432s][2017年08月14日09:31:48][pid:3293,cpu0,system_server]wake up irq num: 86,irq name: RTC0<6>[3005750.005493s][2017年08月14日09:31:48][pid:3293,cpu0,system_server]report_handle: id = 7 length = 5 buff = RTC0 - Arlind Hajredinaj
这是输出的一部分。 - Arlind Hajredinaj
@Arlind,我们需要最早的启动消息,其中包含“ rtc”。并且这必须是从冷启动而不是从挂起恢复。 - Jesse Adelman
我不知道你的意思,你能告诉我如何获取这些信息吗? - Arlind Hajredinaj
当您重新启动此主机并在引导后快速运行“dmesg”时,您应该看到一堆与内核加载和启动相关的内核消息。您看到它们了吗?一些典型的dmesg输出示例:http://tldp.org/LDP/LG/issue59/nazario.html - Jesse Adelman
<6>[ 8.965423秒][pid:1,cpu1,swapper/0]rtc-pl031 fff04000.rtc: rtc: 年份 118 月份 6 日 28 小时 11 分钟 22 秒 3 时间 0x5b5c51db <6>[ 8.966064秒][pid:1,cpu1,swapper/0]rtc-pl031 fff04000.rtc: rtc 核心: pl031 注册为 rtc0 <6>[ 10.697235秒][pid:1,cpu4,swapper/0]rtc-pl031 fff04000.rtc: 将系统时钟设置为 2018-07-28 11:22:05 UTC (1532776925) <11>[ 12.575531秒][pid:1,cpu2,init]init: 找不到 '/system/bin/audiouartctl',已禁用 'audiouartctl' - Arlind Hajredinaj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接