NTP和NITZ的简介
NITZ:Network Identity and Time Zone(网络标识和时区),NITZ是一种GSM/WCDMA基地台方式,必须插入SIM卡,且需要运营商支持,从运营商处获取时间和时区信息。中国大陆运营商基本是不支持的。
NTP:Network Time Protocol(网络时间协议),用来同步网络中各个计算机的时间的协议。在Android设备中,NTP更新时间往往是通过网络(GPRS或WIFI)向NTP服务器获取时间(不包含时区信息)。
NITZ更新时间流程
NITZ更新时间依赖运营商,当运营商基站发出更新时间的消息,基站附近的手机接收到对应消息后,会通过RIL层上报UNSOL_NITZ_TIME_RECEIVED事件,此时ServiceStateTracker便会处理相关时间更新流程,相关时序图如下:
由于NITZ主要依赖于运营商,但在国内移动和联通貌似不怎么好用,在这里就不在详细说了,简单总结下如下:
1、在ServiceStateTracker构造方法里调用setOnNITZTime注册RIL事件RIL_UNSOL_NITZ_TIME_RECEIVED
2、RIL层上报RIL_UNSOL_NITZ_TIME_RECEIVED,在ServiceStateTracker的handleMessage里处理
3、调用ServiceStateTracker的setTimeFromNITZString设置时间和时区,在setAndBroadcastNetworkSetTime里调用setCurrentTimeMillis设置系统时间,并发送广播通知NetworkTimeUpdateService
NTP时间更新流程
NTP时间更新是通过网络(即GPRS或WIFI)去获取时间,在NetworkTimeUpdateService实现的,整体流程如下:
在SystemServer中, 会启动NetworkTimeUpdateService服务:
//frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
try {
// ......
if (!disableNetwork && !disableNetworkTime) {
try {
Slog.i(TAG, "NetworkTimeUpdateService");
networkTimeUpdater = new NetworkTimeUpdateService(context);
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
}
// ......
}
// ......
mActivityManagerService.systemReady(new Runnable() {
@Override
public void run() {
// ......
try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying NetworkTimeService running", e);
}
// ......
}
}
即SystemServer中会调用networkTimeUpdaterF.systemRunning()注册各种监听, 如下:
//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
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);
}
在registerForTelephonyIntents中监听如下动作:
- ACTION_NETWORK_SET_TIME
- ACTION_NETWORK_SET_TIMEZONE
在registerForAlarms中监听什么 ?
在registerForConnectivityIntents中监听网络状态改变(CONNECTIVITY_ACTION)的广播.
在SettingsObserver里监听Settings.Global.AUTO_TIME值的改变, 在这个值发生变化时, 发EVENT_AUTO_TIME_CHANGE消息
在监听到上述动作/广播后, 会在MyHandler中得到处理, 即调用onPollNetworkTime(), 如下:
private void onPollNetworkTime(int event) {
// If Automatic time is not set, don't bother.
if (!isAutomaticTimeRequested()) return;
final long refTime = SystemClock.elapsedRealtime();
// 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 currentTime = System.currentTimeMillis();
if (DBG) Log.d(TAG, "System time = " + currentTime);
// Get the NTP time
if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED) {
if (DBG) Log.d(TAG, "Before Ntp fetch");
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs) {
/* M: For multiple NTP server retry */
// mTime.forceRefresh();
int index = mTryAgainCounter % mNtpServers.size();
if (DBG) Log.d(TAG, "mTryAgainCounter = " + mTryAgainCounter + ";mNtpServers.size() = " + mNtpServers.size() + ";index = " + index+ ";mNtpServers = " + mNtpServers.get(index));
if (mTime instanceof NtpTrustedTime) {
((NtpTrustedTime)mTime).setServer(mNtpServers.get(index));
mTime.forceRefresh();
((NtpTrustedTime)mTime).setServer(mDefaultServer);
} else {
mTime.forceRefresh();
}
/* M: For multiple NTP server retry */
}
// only update when NTP time is fresh
if (mTime.getCacheAge() < mPollingIntervalMs) {
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 (DBG && mLastNtpFetchTime == NOT_SET
&& Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
Log.d(TAG, "For initial setup, rtc = " + currentTime);
}
if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
// Make sure we don't overflow, since it's going to be converted to an int
if (ntp / 1000 < Integer.MAX_VALUE) {
SystemClock.setCurrentTimeMillis(ntp);
}
} else {
if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
}
mLastNtpFetchTime = SystemClock.elapsedRealtime();
} else {
// Try again shortly
mTryAgainCounter++;
if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
resetAlarm(mPollingIntervalShorterMs);
} else {
// Try much later
mTryAgainCounter = 0;
resetAlarm(mPollingIntervalMs);
}
return;
}
}
resetAlarm(mPollingIntervalMs);
}
没有太复杂的逻辑, 就是网络获取时间, 并与本机时间比较, 如果时间差大于mTimeErrorThresholdMs值, 就更新本机时间.
这里涉及到几个阈值:
mPollingIntervalMs: 当NTP时间获取成功后,再次请求NTP时间的间隔
mPollingIntervalShorterMs: 当NTP时间获取失败后,再次请求NTP时间的间隔
mTimeErrorThresholdMs: 当NTP时间和系统时间不相同时,如果时间差大于此阀值,则更新系统时间
mTryAgainCounter: Retry的最大次数
这几个值都是在系统资源的config.xml配置的:
//frameworks/base/core/res/res/values/config.xml
<!-- Remote server that can provide NTP responses. -->
<string translatable="false" name="config_ntpServer">2.android.pool.ntp.org</string>
<!-- Normal polling frequency in milliseconds -->
<integer name="config_ntpPollingInterval">864000000</integer>
<!-- Try-again polling interval in milliseconds, in case the network request failed -->
<integer name="config_ntpPollingIntervalShorter">60000</integer>
<!-- Number of times to try again with the shorter interval, before backing
off until the normal polling interval. A value < 0 indicates infinite. -->
<integer name="config_ntpRetry">3</integer>
<!-- If the time difference is greater than this threshold in milliseconds,
then update the time. -->
<integer name="config_ntpThreshold">5000</integer>
<!-- Timeout to wait for NTP server response. -->
<integer name="config_ntpTimeout">20000</integer>
更新时间的关键是调用 mTime.forceRefresh(), 即NtpTrustedTime类的forceRefresh()方法, 如下:
//frameworks/base/core/java/android/util/NtpTrustedTime.java
public boolean forceRefresh() {
// ......
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;
}
}
即调用SntpClient, 通过UDP方式向NTP Server获取时间:
//frameworks/base/core/java/android/net/SntpClient.java
public boolean requestTime(String host, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
InetAddress address = InetAddress.getByName(host);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
// set mode = 3 (client) and version = 3
// mode is in low 3 bits of first byte
// version is in bits 3-5 of first byte
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
// get current time and write it to the request packet
long requestTime = System.currentTimeMillis();
long requestTicks = SystemClock.elapsedRealtime();
writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
socket.send(request);
// read the response
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
long responseTicks = SystemClock.elapsedRealtime();
long responseTime = requestTime + (responseTicks - requestTicks);
// extract the results
long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
// save our results - use the times on this side of the network latency
// (response rather than request time)
mNtpTime = responseTime + clockOffset;
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
if (false) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
socket.close();
}
}
return true;
}
上面requestTime()代码涉及到NTP取时的校正问题, 请参考NTP协议与计算
如果遇到NTP无法更新的问题, 需要分析logcat, 看看是否有SocketException或unknown host之类的网络错误. 可能的原因:
- 网络不可用
- NTP Server连接不上
总结
- NITZ的优先级要高于NTP的优先级,当NITZ更新系统时间后,NTP即使触发更新条件,也会检查NITZ更新时间距今是否超过864000000毫秒(10天,config_ntpPollingInterval),若不满10天,则重设Alarm并取消此次NTP更新请求。
- NITZ主要依赖于运营商上报,NTP则主要依赖于网络环境,NITZ通过被动接收获取时间,NTP通过访问NTP Server主动获取网络时间,最后都是通过调用SystemClock.setCurrentTimeMillis更新本机时间。