【问题标题】:System.currentTimeMillis() returns incorrect timestamp on HuaweiSystem.currentTimeMillis() 在华为上返回错误的时间戳
【发布时间】:2017-08-13 12:25:18
【问题描述】:

问题是System.currentTimeMillis() 返回错误的毫秒数,不同的时间范围主要在未来,有时长达 6 个月,但从几秒到几个月不等。

出现这种情况的设备是Android 5.1.1上的平板电脑型号Huawei M2-A201W,内核版本为:**3.10.74-gdbd9055**

我的第一个假设是 NTP 不知何故弄乱了时间,但我有成千上万的平板电脑,其中一些没有网络连接,没有 SIM 卡,所以没有 GSM/3G/4G .

我使用System.currentTimeMillis() 保存在表的列中,以了解何时在本地 sqlite 数据库中创建行。

这种异常情况在我使用的平板电脑上经常发生(每次System.currentTimeMillis() 呼叫的 30%)。

【问题讨论】:

  • 它是否每次都返回错误的时间,或者只有大约 30% 的调用?如果它大部分都有效,你可以连续调用它 10 次,然后从返回的值中猜测哪个是正确的(大部分应该是这样的)。 (但答案看起来更有趣,这是次要选项)。
  • 不是每次,而是 30% 的时间,如果我使用 sqlite 数据库生成时间戳,我们怎么知道它实际上正在工作,那么 Sqlite 实际使用什么本机方法?
  • 当您遇到某种错误时,最好的解决方法是尝试一下。做一些应用程序(如果你不能用当前修改的应用程序轻松测试),它将随机插入一些数据到数据库中(可能将它与一些for 延迟循环交错进行一些计算,与任何计时器不同步-基于(如delay(msec)),然后检查数据,如果存储的时间戳增长均匀并且与当前时间相关,或者数据中有一些跳跃。如果真的是30%,那么你应该在几千轻松记录。
  • 我会试试你的解决方案。此外,由于设备处于离线状态,我正在尝试获取 GPS 时间。
  • Stack Overflow 用于编程问题。你有什么问题?

标签: android linux ntp huawei-mobile-services


【解决方案1】:

作为使用System.currentTimeMillis() 的一种解决方法,也许您可​​以让 sqlite 处理时间戳创建,看看是否能解决您的问题?

使用timestamp default current_timestampdefault(strftime('%Y-%m-%d %H:%M:%f', 'now')) 定义/更改“已创建”列(如果您还需要毫秒),如下所示:

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
  • 看起来他们在 c 中有自己的实现,sqlite.org/src/doc/trunk/src/date.c 不过不确定它是否能解决您的问题,您必须尝试并在显示奇怪结果的设备上进行测试。跨度>
【解决方案2】:

当您没有 SIM 卡因此没有 GSM/3G/4G 时,您的手机无法根据网络提供的时间/区域更新正确的时间。

因此,有网络的设备会显示正确的时间,而没有网络的其他设备可能会显示错误的时间——您必须手动设置正确的时间。 System.currentTimeMilis() 从您的系统中读取时间。 g 但在开机时,时钟工作。

检查 NTP(UDP 端口 123)是否被使用 Socket 或 DatagramSocket 的应用阻止。注意:NTP适用于网络中所有主机或路由器时钟必须相同的场景。如果您的设备切换到两个(或更多)不同的网络并从不同的来源更新时间,它可能会波动时间。

最终,您的系统时间正在改变,这就是它波动的原因。如果您在手动禁用自动日期和时间后手动 System.currentTimeMilis(),我相信它不会波动(没有异常)。如果是这种情况,那么您的 Huewai 平板电脑没有错误。

【讨论】:

    【解决方案3】:

    Android 平台中的 java API 调用 System.currentTimeMillis() 使用 POSIX api gettimeofday 来获取以毫秒为单位的时间。见here

    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() 方法有何不同吗?
    【解决方案4】:

    如何通过在 Linux 内核中运行 "date +%s" 命令来本地获取时间戳?

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

      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 年 8 月 8 日星期二 16:15:58 GMT+06:00

    【讨论】:

    • 为什么你认为获取时间戳本身与 System.currentMillis 不同?
    • 我怀疑这个问题与固件有关,而不是硬件。很可能是因为所有华为手机都经过了大量的软件定制,例如情感用户界面。同样,我不完全确定,但我强烈建议您也使用内核的结果进行测试。 @Arlind
    • 另外,请确保所有设备在手机设置中同步。 @Arlind
    【解决方案5】:

    由于您提到大多数呼叫都得到了正确的时间并且只发生在 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);
    }
    

    【讨论】:

      【解决方案6】:

      这个设备有真正的硬件 RTC 吗?我无法通过谷歌搜索和阅读规格表找到明确的答案。一连串:

      $ dmesg -s 65535 | grep -i rtc
      

      shell 应该会给你一个答案。您应该会看到类似这样的内容(会因芯片组和内核版本而异):

      [    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 和与 Internet 的有效网络连接,以使这种没有时钟芯片的设备与世界时间保持同步。

      实时时钟:https://en.wikipedia.org/wiki/Real-time_clock

      【讨论】:

      • [3005741.222717s][2017:08:14 09:31:48][pid:3293,cpu0,system_server]rtc-pl031 fff04000.rtc: pl031_suspend+。 [3005741.222747s][2017:08:14 09:31:48][pid:3293,cpu0,system_server]rtc-pl031 fff04000.rtc:pl031_suspend-。 [3005750.005432s][2017:08:14 09:31:48][pid:3293,cpu0,system_server]唤醒irq num: 86, irq name: RTC0[3005750.005493s][2017:08 :14 09:31:48][pid:3293,cpu0,system_server]report_handle: id = 7 长度 = 5 buff = RTC0
      • 这是输出的一部分
      • @Arlind 我们需要最早的带有“rtc”的引导消息。这必须来自冷启动,而不是来自暂停的恢复。
      • 我不知道你的意思到底是什么我怎样才能得到这些信息?
      • 当你重启这台主机,并在启动后快速运行“dmesg”,你应该会看到一堆与内核加载和启动相关的内核消息。你看到他们了吗?典型 dmesg 输出的一些示例:tldp.org/LDP/LG/issue59/nazario.html
      猜你喜欢
      • 2017-09-12
      • 2015-09-27
      • 1970-01-01
      • 2013-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-26
      相关资源
      最近更新 更多