Android系统时间更新主要涉及到两种时间:NITZ时间和NTP时间。NITZ时间与手机服务运营商关系密切;而NTP网络时间则较为独立,访问某个服务器,获取时间即可。NTP时间获取的过程较为单一,且由于工作中主要是涉及到Android NTP时间的获取;所以这里先只介绍Android系统获取NTP时间的流程。
NTP其实是一种协议,Android系统中通过一个Socket向一个提供NTP服务的服务器发送请求来获取NTP时间。既然借助于Socket通信,那么功能实现的过程中就会涉及到服务器地址、请求超时时间等信息的配置。这部分我们在后续的分析过程中都会看到。
Android NTP时间更新主要由NetworkUpdateService服务处理,它决定何时更新NTP时间。在systemserver进程启动时,它会被初始化;同时,它是Binder的子类:
networkTimeUpdater = new NetworkTimeUpdateService(context);
ServiceManager.addService("network_time_update_service", networkTimeUpdater);
首先创建服务实例,接着将该服务发布到系统中。创建完服务对象,并当系统准备完成后,会直接启动该服务:
try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying NetworkTimeService running", e);
}
手段就是调用NetworkTimeUpdateService::systemRunning()方法。通过这两步,时间更新服务就处于工作状态了。
那么我们通过分析这两个步骤,来理解NetworkTimeUpdateService的工作流程
NetworkTimeUpdateService的构造函数实现如下:
public NetworkTimeUpdateService(Context context) {
mContext = context;
mTime = NtpTrustedTime.getInstance(context);//初始化NtpTrustedTime实例,它负责NTP时间的获取
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);//获取闹钟服务
//该PendingIntent对象用于当某次Ntp时间获取失败后,开启一个定时服务,去再次请求时间
Intent pollIntent = new Intent(ACTION_POLL, null);
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
//初始化了几个成员变量,它们决定了Ntp时间更新的细节
mPollingIntervalMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingInterval);
mPollingIntervalShorterMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingIntervalShorter);
mTryAgainTimesMax = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpRetry);
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);
mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
首先初始化NtpTrustedTime类实例,它负责NTP时间获取的具体操作,而NetworkTimeUpdateService服务决定何时触发NTP时间请求。
NtpTrustedTime实例的构造过程如下,它采用了单例设计模式:
private NtpTrustedTime(String server, long timeout) {
if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
mServer = server;
mTimeout = timeout;
}
public static synchronized NtpTrustedTime getInstance(Context context) {
if (sSingleton == null) {
final Resources res = context.getResources();
final ContentResolver resolver = context.getContentResolver();
//读取用户配置的NTP服务器地址、超时信息等
final String defaultServer = res.getString(
com.android.internal.R.string.config_ntpServer);
final long defaultTimeout = res.getInteger(
com.android.internal.R.integer.config_ntpTimeout);
final String secureServer = Settings.Global.getString(
resolver, Settings.Global.NTP_SERVER);
final long timeout = Settings.Global.getLong(
resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
final String server = secureServer != null ? secureServer : defaultServer;
sSingleton = new NtpTrustedTime(server, timeout);
sContext = context;
}
return sSingleton;
}
读取配置文件和数据库配置的Ntp服务器地址和请求超时时间等信息,然后创建NtpTrustedTime对象并返回。其中,涉及到的服务器地址和超时信息会在config.xml和数据库中配置。如这里的默认NTP请求地址配置为:
2.android.pool.ntp.org
NtpTrustedTime封装了具体请求NTP时间的接口,NetworkTimeUpdateService如果需要更新NTP时间,通过创建的mTime实例即可实现。
回到NetworkTimeUpdateService的构造函数,创建完NtpTrustedTime实例后,还会获取Alarm服务对象;因为,当某刻NTP请求失败,系统会定时地去尝试再次获取NTP时间。其中创建的mPendingPollIntent就代表我们的这种意图。随后,这里初始化了几个long型变量,它会在NTP时间请求过程中决定一些细节分支处理,所以我们要明确它们的具体含义:
//初始化了三个成员变量,它们决定了Ntp时间更新的细节
mPollingIntervalMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingInterval);//86400000ms
mPollingIntervalShorterMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingIntervalShorter);//60000ms
mTryAgainTimesMax = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpRetry);//3
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);//5000
初始化了这些成员变量,NetworkTimeUpdateService的创建工作就完成了。接着就要去启动该服务了。
NetworkTimeUpdateService实例创建完成后,会调用systemRunning()启动它:
/** 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);
}
说是启动,其实主要的工作就是初始化了一些触发时间更新的条件。
private void registerForTelephonyIntents() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
mContext.registerReceiver(mNitzReceiver, intentFilter);
}
private void registerForAlarms() {
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
}
}, new IntentFilter(ACTION_POLL));
}
private void registerForConnectivityIntents() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(mConnectivityReceiver, intentFilter);
}
/** Receiver for Nitz time events */
private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DBG) Log.d(TAG, "Received " + action);
if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
} else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
mNitzZoneSetTime = SystemClock.elapsedRealtime();
}
}
};
/** Receiver for ConnectivityManager events */
private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
if (DBG) Log.d(TAG, "Received CONNECTIVITY_ACTION ");
// Don't bother checking if we have connectivity, NtpTrustedTime does that for us.
Message message = mHandler.obtainMessage(EVENT_NETWORK_CHANGED);
// Send with a short delay to make sure the network is ready for use
mHandler.sendMessageDelayed(message, NETWORK_CHANGE_EVENT_DELAY_MS);
}
}
};
三个register方法分别注册了该服务关心的一些广播,其中一些广播的处理会通过发送消息事件去触发时间更新流程。其中,网络连接更新变化是会触发时间更新的;这在MyHandler的处理中可以看到。
MyHandler是NetworkTimeUpdateService内处理这些引发时间更新的异步消息事件的类,它工作在单独的线程中:
/** Handler to do the network accesses on */
private class MyHandler extends Handler {
public MyHandler(Looper l) {
super(l);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_AUTO_TIME_CHANGED:
case EVENT_POLL_NETWORK_TIME:
case EVENT_NETWORK_CHANGED:
onPollNetworkTime(msg.what);
break;
}
}
}
MyHandler中处理了三个事件,这样表明只在三种情况下,会主动去触发时间更新流程:
SettingsObserver会监听Settings.Global.AUTO_TIME字段是否变化,它对应的就是上面介绍的第一种触发情况:
/** Observer to watch for changes to the AUTO_TIME setting */
private static class SettingsObserver extends ContentObserver {
private int mMsg;
private Handler mHandler;
SettingsObserver(Handler handler, int msg) {
super(handler);
mHandler = handler;
mMsg = msg;
}
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();
}
}
当监听到Settings.Global.AUTO_TIME字段有变化时,会发送消息事件EVENT_AUTO_TIME_CHANGED给MyHandler;如果字段值设为了1,表明用户希望自动设置时间,那么此时就会去触发时间更新。
// Check the network time on the new thread
mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
通过发送EVENT_POLL_NETWORK_TIME事件;三种情况下,时间更新的流程都由onPollNetworkTime()处理,分析一种情况就等于分析了三种情况。直接看它的处理过程:
/**
* Checks if the user prefers to automatically set the time.
*/
private boolean isAutomaticTimeRequested() {
return Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0) != 0;
}
private void onPollNetworkTime(int event) {
// If Automatic time is not set, don't bother.
if (!isAutomaticTimeRequested()) return;
mWakeLock.acquire();
try {
onPollNetworkTimeUnderWakeLock(event);
} finally {
mWakeLock.release();
}
}
首先判断Settings.Global.AUTO_TIME字段的值,如果为1,则去自动更新、设置时间。onPollNetworkTimeUnderWakeLock()函数处理如下:
private void onPollNetworkTimeUnderWakeLock(int event) {
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) {//NITZ时间部分,如果两次时间的差异小于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) {//如果我们是第一次设置时间,或者两次更新时间的间隔大于了mPollingIntervalMs值,或者当前触发更新的事件是EVENT_AUTO_TIME_CHANGED;则继续更新流程
if (DBG) Log.d(TAG, "Before Ntp fetch");
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs) {//如果这次时间更新的时间节点和上一次时间更新时获取到结果的时间节点之间的差异大于mPollingIntervalMs时,认为时间过期,需要强制刷新时间;
mTime.forceRefresh();
}
// only update when NTP time is fresh
if (mTime.getCacheAge() < mPollingIntervalMs) {//这种情况下认为NTP时间是新鲜的、有效的
final long ntp = mTime.currentTimeMillis();//获取当前的NTP时间
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);//将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);//设置定时任务,隔一段时间后再去尝试刷新时间
}
NTP时间的刷新动作是由NtpTrustedTime::forceRefresh()函数实现:
@Override
public boolean forceRefresh() {
if (TextUtils.isEmpty(mServer)) {
// missing server, so no trusted time available
return false;
}
// 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);
}
}
final NetworkInfo ni = mCM == null ? null : mCM.getActiveNetworkInfo();
if (ni == null || !ni.isConnected()) {
if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
return false;
}
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;
}
}
它内部借助SntpClient类来实现此时的NTP socket请求:
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;
}
SntpClient封装了获取NTP时间的socket接口:
/**
* Sends an SNTP request to the given host and processes the response.
*
* @param host host name of the server.
* @param timeout network timeout in milliseconds.
* @return true if the transaction was successful.
*/
public boolean requestTime(String host, int timeout) {
InetAddress address = null;
try {
address = InetAddress.getByName(host);//
} catch (Exception e) {
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
}
return requestTime(address, NTP_PORT, timeout);
}
public boolean requestTime(InetAddress address, int port, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, 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
final long requestTime = System.currentTimeMillis();//开始请求的时间节点记录,当前时间
final 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);//开始读取响应
final long responseTicks = SystemClock.elapsedRealtime();//读取响应的时间时钟记录,从开机算起
final long responseTime = requestTime + (responseTicks - requestTicks);//响应时间 = 开始发送请求的时间节点 + (响应时钟节点 - 请求时钟节点)
// extract the results
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
final long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
final long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
final long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
/* do sanity check according to RFC */
// TODO: validate originateTime == requestTime.
checkValidServerReply(leap, mode, stratum, transmitTime);
long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
// receiveTime = originateTime + transit + skew
// responseTime = transmitTime + transit - skew
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
// = ((originateTime + transit + skew - originateTime) +
// (transmitTime - (transmitTime + transit - skew)))/2
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
if (DBG) {
Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
"clock offset: " + clockOffset + "ms");
}
// save our results - use the times on this side of the network latency
// (response rather than request time)
mNtpTime = responseTime + clockOffset;//计算得到的ntp时间
mNtpTimeReference = responseTicks;//读取响应的时间节点
mRoundTripTime = roundTripTime;
} catch (Exception e) {
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
socket.close();
}
}
return true;
}
一个标准的socket发送请求-读取响应的模式。实现中,记录了一些特殊的时间节点,我们需要注意。
系统是如何判定当前NTP时间是否需要刷新呢?
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs) {//如果这次时间更新的时间节点和上一次时间更新时获取到结果的时间节点之间的差异大于mPollingIntervalMs时,认为时间过期,需要强制刷新时间;
mTime.forceRefresh();
}
@Override
public long getCacheAge() {
if (mHasCache) {
return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;//mCachedNtpElapsedRealtime = client.getNtpTimeReference();
} else {
return Long.MAX_VALUE;
}
}
/**
* Returns the reference clock value (value of SystemClock.elapsedRealtime())
* corresponding to the NTP time.
*
* @return reference clock corresponding to the NTP time.
*/
public long getNtpTimeReference() {
return mNtpTimeReference;
}
具体的信息可以参考里面的注释。NTP时间刷新完成后,我们还需要将它设置到系统中,其实也就是设置到kernel中:
// only update when NTP time is fresh
if (mTime.getCacheAge() < mPollingIntervalMs) {//这种情况下认为NTP时间是新鲜的、有效的
final long ntp = mTime.currentTimeMillis();//获取当前的NTP时间
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);//将NTP时间设置到内核中,此时时间才会生效
}
} else {
if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
}
mLastNtpFetchTime = SystemClock.elapsedRealtime();
}
判断获取到的NTP时间的有效性,随后SystemClock.setCurrentTimeMillis()函数会将时间设置到内核中,让其生效;这样我们的时间就得到更新了。
如果某次的时间刷新操作失败了,系统会设置定时任务,在某个时间段之后再去触发时间刷新:
* Cancel old alarm and starts a new one for the specified interval.
*
* @param interval when to trigger the alarm, starting from now.
*/
private void resetAlarm(long interval) {
mAlarmManager.cancel(mPendingPollIntent);
long now = SystemClock.elapsedRealtime();
long next = now + interval;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
}
定时器任务触发时,会引起如下处理:
//用于定时器
private void registerForAlarms() {
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
}
}, new IntentFilter(ACTION_POLL));
}
向MyHandler发送EVENT_POLL_NETWORK_TIME事件,再次去调用onPollNetworkTime()函数处理当前的时间刷新请求,重复之前的分析流程。
NetworkTimeUpdateService服务启动及NTP时间刷新流程就分析完成了。