Android - System.currentTimeMillis()计算时间差出现负数(NITZ和NTP机制学习)

一、问题描述

前几天APP突然爆出个问题,recyclerview中的item点击没反应,低概率,之前一直没遇到过。

二、问题原因

复现后看log发现,代码在点击事件中加个isFastClick判断,如果是用户频繁点击的话返回true不执行点击事件,而问题出现的时候就一直返回true,但是并没有频繁点击。

问题代码如下,加打印后发现curClickTime - lastClickTime这个值一直是负数,也就是说curClickTime小于之前的lastClickTime。测试的设备的系统时间设置的是自动网络更新,设备会同步Internet时间服务器的时间,当同步新的时间比之前的时间小时,就导致了时间差出现负数。

    private static final int MIN_CLICK_DELAY_TIME = 500;
    private static long lastClickTime;
    
	public static boolean isFastClick() {
        boolean flag = true;
		long curClickTime = System.currentTimeMillis();
        if ((curClickTime - lastClickTime) >= MIN_CLICK_DELAY_TIME) {
            flag = false;
			lastClickTime = curClickTime;
        }
		Log.d("isFastClick","flage = " + flag);
        return flag;
    }

三、解决办法

由于网络时间的不稳定性,在时间间隔的统计时可以使用:SystemClock.elapsedRealtime() ,它是系统启动到当前时刻经过的时间,包括了系统睡眠经过的时间。在CPU休眠之后,它依然保持增长。因此该判断方法可以修改为如下代码:

    private static final int MIN_CLICK_DELAY_TIME = 500;
    private static long lastClickTime;
    
	public static boolean isFastClick() {
        boolean flag = true;
		long curClickTime = SystemClock.elapsedRealtime();
        if ((curClickTime - lastClickTime) >= MIN_CLICK_DELAY_TIME) {
            flag = false;
			lastClickTime = curClickTime;
        }
		Log.d("isFastClick","flage = " + flag);
        return flag;
    }

四、系统时间网络同步

System.currentTimeMillis()获取的是当前系统时间,设置中自动同步网络时间开关也是默认开始的,上面的问题根本原因是自动更新时间后,更新后的时间小于之前的时间,于是看了下系统时间的更新机制,按照自己理解简单梳理了下流程,详细大家可以去看博客Android9.0 本地时区和本地时间的自动更新机制

Android通过网络同步时间有两种方式:NITZ和NTP,它们使用的条件不同,可以获取的信息也不一样。

1、相关概念

  • NITZ:Network Identity and Time Zone(网络标识和时区),是一种用于自动配置本地的时间和日期的机制,需要运营商支持(通过CS网络),可从运营商获取时间和时区具体信息。目前国内电信、移动都支持NITZ方式更新时间日期,而联通目前不支持。

  • NTP:Network Time Protocol(网络时间协议),用来同步网络中各个计算机的时间的协议。在手机中,NTP更新时间的方式是通过GPRS或wifi向特定服务器获取时间信息,只能更新时间,无法更新时区。

2、NITZ更新流程

第一种:当运营商基站发出更新时间的消息,基站附近的手机接收到对应消息后,最终会通过ServiceStateTracker类方法进行解析设置,流程如下:

(1)当运营商基站发出更新时间、时区的消息;

NITZ消息是基站发送的,手机等设备只能被动接收,一般在下面几种情况下会收到:
1、设备开机第一次注册网络;
2、手机丢网在注网络(如开关飞行模式);
3、跨地区,小基站信息发生变化;

(2)设备收到消息后通过RIL层上报EVENT_NITZ_TIME事件到ServiceStateTracker;

(3)ServiceStateTracker类handler收到消息后,继续调用setTimeFromNITZString方法设置运营商发送的时间和时区信息;

(4)最终在setAndBroadcastNetworkSetTime里调用SystemClock.setCurrentTimeMillis(time)设置系统时间,并发送广播ACTION_NETWORK_SET_TIME通知NetworkTimeUpdateService去更新NITZ设置时间mNitzTimeSetTime;

(5)最终在setAndBroadcastNetworkSetTimeZone里调用alarmManager.setTimeZone(zoneId);设置系统时区,并发送广播ACTION_NETWORK_SET_TIMEZONE;

第二种:ServiceStateTracker类初始化时会注册监听Settings.Global.AUTO_TIME和Settings.Global.AUTO_TIME_ZONE,Setting中开关会改变这两个设置,触发NITZ时间自动更新。

(1)数据库里的Settings.Global.AUTO_TIME和Settings.Global.AUTO_TIME_ZONE发生变化;

(2)回调到NitzStateMachine类中,由handleAutoTimeEnabled和handleAutoTimeZoneEnabled,更新时间计算方式:

long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
long adjustedCurrentTimeMillis = mSavedNitzTime.mValue + (elapsedRealtime - mSavedNitzTime.mElapsedRealtime)

(3)最终仍然是调用setAndBroadcastNetworkSetTimesetAndBroadcastNetworkSetTimeZone去更新时间。

3、NTP更新流程

NTP更新用到的变量说明,以下间阈值都可以在config文件中修改,路径: /frameworks/base/core/res/res/values/config.xml:

  • mPollingIntervalMs://较长的时间间隔,当同步时间成功后的下一次自动同步的时间间隔,默认为24小时
  • mPollingIntervalShorterMs://较短的时间间隔,当同步时间失败后的下一次自动同步的时间间隔,默认为1分钟
  • mTryAgainTimesMax://当同步时间失败后,会重新尝试同步的次数,默认是3次
  • mTimeErrorThresholdMs://时间会更新的阈值,只有服务器上的时间和设备时间相差超过这个阈值时,才会更新时间,默认是5s

整体流程如下:
(1)SystemServer中调用startOtherServices()创建NetworkTimeUpdateService类实例;

(2)NetworkTimeUpdateService初始化注册监听NITZ时间变化广播、Alarm定时更新时间、网络状态变化以及系统设置中自动更新时间开关的变化(注册NITZ广播ACTION_NETWORK_SET_TIME来更新mNitzTimeSetTime);

(3)NetworkTimeUpdateService类中,Handler中三个事件EVENT_AUTO_TIME_CHANGED,EVENT_POLL_NETWORK_TIME、EVENT_NETWORK_CHANGED中一个发生变化时,会主动触发系统时间更新;

(4)收到事件后执行onPollNetworkTime(int event),里面调用onPollNetworkTimeUnderWakeLock(int event),时间更新的时候要调用wakeLock,让CPU处于运行状态,防止休眠

(5)最终调用SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis())更新系统时间,该更新具体流程如下图。

系统时间更新具体流程:
在这里插入图片描述

参考文章:
高并发下,使用System.currentTimeMillis()计算时间差出现负数
Android9.0 本地时区和本地时间的自动更新机制
Android时间更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值