JasonWang's Blog

Android系统时间自动更新机制

字数统计: 1.4k阅读时长: 6 min
2016/10/24

Android系统是如何更新时间的了?Android提供了两种时间更新方式,NITZ和NTP,其中NITZ是基于移动基站通信更新的,与语音通话时相同的一个通道,而NTP则是通过数据通道(此时手机必须能上网)来更新的。这里,就来了解下这两种时间更新方式具体是如何工作的吧?

两种时间更新机制

NITZ

NITZ(Network Identity and Time Zone,网络标识和时区),是一种用于自动配置本地的时间和日期的机制,同时也通过无线网向移动设备提供运营商信息。NITZ是自从PHASE 2+ RELEASE 96 的GSM中的可选功能,经常被用来自动更新移动电话的系统时钟。NITZ需要运营商网络支持(通过CS网络),目前国内电信、移动都支持NITZ方式更新时间日期,而联通目前不支持。

参考: https://en.wikipedia.org/wiki/NITZ

NTP

NTP:NTP(Network Time Protocol)提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC。 NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。这样就有了准确而可靠的时间源。时间按NTP服务器的等级传播。与NITZ不同的是,NTP需要从专门的NTP服务器来获取时间,只要手机连接上网络,都可以实现时间的更新。

参考: https://en.wikipedia.org/wiki/Network_Time_Protocol

Android如何更新系统时间

Android有一个专门的系统服务 NetworkTimeUpdateServcie来负责更新系统时间,该服务在系统启动时在SystemServer.java中被创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

if (!disableNetwork && !disableNetworkTime) {
try {
Slog.i(TAG, "NetworkTimeUpdateService");
networkTimeUpdater = new NetworkTimeUpdateService(context);
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
}
...
try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying NetworkTimeService running", e);
}

源码: /frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

服务初始化

NetworkTimeUpdateService初始时会:

  • 注册RIL的ACTION_NETWORK_SET_TIME以及ACTION_NETWORK_SET_TIMEZONE事件,以接受来自Telephony FW的NITZ时间更新;
  • 监听 ACTION_POLL事件(定时更新时间)以及手机网络连接状态;
  • 发送消息同步NTP时间
  • 监听 Settings中“自动更新时间”选项的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** Initialize the receivers and initiate the first NTP request */
public void systemRunning() {
registerForTelephonyIntents();
registerForAlarms();
registerForConnectivityIntents();

HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new MyHandler(thread.getLooper());
// Check the network time on the new thread
mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();

mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
mSettingsObserver.observe(mContext);
}

同步NTP时间

如果没有收到NITZ时间的更新并且NTP超过一定间隔没有更新时间,服务会主动去同步NTP时间:

1
2
3
4
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs) {
mTime.forceRefresh();
}

NTP从服务器获取时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public boolean forceRefresh() {
...
// We can't do this at initialization time: ConnectivityService might not be running yet.
synchronized (this) {
if (mCM == null) {
mCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
}
...
if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
final SntpClient client = new SntpClient();
if (client.requestTime(mServer, (int) mTimeout)) {
mHasCache = true;
mCachedNtpTime = client.getNtpTime();
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else {
return false;
}
}

源码: /frameworks/base/core/java/android/util/NtpTrustedTime.java

接收NITZ时间

Telephony Framework层在接收到最新的NITZ时间后,会主动发送广播请求更新系统时间,NetworkTimeUpateService接收到广播后,保存相应的NITZ时间,下一次poll请求时,就会将该事件更新为系统时间。

1
2
3
4
5
6
7
8
9
10
11
private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
} else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
mNitzZoneSetTime = SystemClock.elapsedRealtime();
}
}
};

监听Settings中 “自动确定时间和日期”的变化

在setting中勾选“自动确定时间和日期”、“自动确定时区”后对key值为AUTO_TIME和AUTO_TIME_ZONE的Preference进行了赋值.

源码路径:packages/apps/Settings/src/com/android/settings/DateTimeSettings.java

1
2
3
4
5
6
7
8
9
10
void observe(Context context) {
ContentResolver resolver = context.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
false, this);
}

@Override
public void onChange(boolean selfChange) {
mHandler.obtainMessage(mMsg).sendToTarget();
}

NetworkTimeUpdateService在检测到key值改变的时,就会发送一个消息EVENT_AUTO_TIME_CHANGED;handler接到消息后进行消息处理调用onPollNetworkTime(msg.what):

1
2
3
4
5
6
7
8
9
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_AUTO_TIME_CHANGED:
case EVENT_POLL_NETWORK_TIME:
case EVENT_NETWORK_CONNECTED:
onPollNetworkTime(msg.what);
break;
}
}

在onPollNetworkTime方法中先判断是否勾选“自动更新时间”,如果没勾选直接退出,如果勾选了再看。如果NITZ已经更新了(不为NOT_SET(-1)),且更新间隔小于mPollingIntervalMs(mPollingIntervalMs=246060*1000),则直接用NITZ更新系统时间,否则用NTP同步时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// If NITZ time was received less than mPollingIntervalMs time ago,
// no need to sync to NTP.
if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
resetAlarm(mPollingIntervalMs);
return;
}

final long ntp = mTime.currentTimeMillis();
mTryAgainCounter = 0;
// If the clock is more than N seconds off or this is the first time it's been
// fetched since boot, set the current time.
if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
|| mLastNtpFetchTime == NOT_SET) {
// Set the system time
......
if (ntp / 1000 < Integer.MAX_VALUE) {
   SystemClock.setCurrentTimeMillis(ntp);
}

当从NTP服务器上获取的时间和当前时间之差的绝对值大于一个阀值,则认为当前时间错误,需要更新时间。

总结:

  • 时间自动同步选项未勾选,不主动更新时间,直接返回;
  • NITZ已同步且上次NITZ同步未超过24小时,则设置定时器24小时后再触发同步,即广播NetworkTimeUpdateService.ACTION_POLL;
  • NTP上次成功同步超过24小时或用户勾选自动同步选项,则进行下面的NTP同步,否则同上设置定时器24小时后再触发同步

参考文献

原文作者:Jason Wang

更新日期:2017-04-29, 14:58:40

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 两种时间更新机制
    1. 1.1. NITZ
    2. 1.2. NTP
  2. 2. Android如何更新系统时间
    1. 2.1. 服务初始化
    2. 2.2. 同步NTP时间
    3. 2.3. 接收NITZ时间
    4. 2.4. 监听Settings中 “自动确定时间和日期”的变化
  3. 3. 参考文献