最近遇到了一些手机不能自动更新时间的问题,所以便梳理了一遍时间更新的流程,这里做一下记录,本文基于Android 8.0源码。
既然是系统更新就要借助外部网络环境,所以这个机制是Android系统中少有的需要和网络通信的服务,简单学习一下也是有必要的。首先Android系统中时间更新有两种方法:NITZ(Network Identity and Time Zone)和NTP(Network Time Protocol)。前者借助于运营商实现,后者借助于一般互联网。
由于我们事先并不清楚具体实现的地方,我们就从设置入口开始,一步一步深入。在设置中开关自动更新时间的代码位置及内容如下:
android\packages\apps\uniscope\Settings\src\com\android\settings\datetime\AutoTimePreferenceController.java
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean autoEnabled = (Boolean) newValue;
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME,
autoEnabled ? 1 : 0);
mCallback.updateTimeAndDateDisplay(mContext);
return true;
}
可见是修改系统中Settings.Global.AUTO_TIME字段来确定是否开启自动更新时间的功能。了解源码的朋友应该清楚,修改一些系统变量也是实现跨进程通信的一种方法。不必直接通信,利用观察者模式,一方修改,另一方监听,就能根据变量变化及时做出响应。所以监听这个变量的地方大致就是自动更新时间逻辑执行的地方,我们可以全文搜索一下代码来一窥端倪。
通过分析我们发现主要在ServiceStateTracker和NetworkTimeUpdateService中监听了这个变量的变化,后者通过名字我们也基本能确定这里就是负责时间更新的。
前者就是时间更新中NITZ机制的主要实现,由于涉及到RIL,这是Android中负责通信功能的Telephony中间层,对这一部分还不是很熟悉,所以就不深入介绍,不过简单介绍一下大致流程:
首先ServiceStateTracker可以认为是和RIL的主要交互者,负责一些状态维护。它持有一个CommandsInterface对象,也就是RIL,首先注册了NITZ事件:
mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
当RIL上报事件时开始设置时间:
case EVENT_NITZ_TIME:
ar = (AsyncResult) msg.obj;
String nitzString = (String)((Object[])ar.result)[0];
long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
setTimeFromNITZString(nitzString, nitzReceiveTime);
break;
setTimeFromNITZString中就开始设置时间和时区,然后发送广播setAndBroadcastNetworkSetTime。在setTimeFromNITZString通过调用getAutoTime方法来判断是否需要自动更新时间。
刚才是RIL主动上报事件时通过判断来决定是否更新时间,而响应设置中开关的逻辑如下:
private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
Rlog.i(LOG_TAG, "Auto time state changed");
revertToNitzTime();
}
};
private void revertToNitzTime() {
if (Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME, 0) == 0) {
return;
}
if (DBG) {
log("Reverting to NITZ Time: mSavedTime=" + mSavedTime + " mSavedAtTime=" +
mSavedAtTime);
}
if (mSavedTime != 0 && mSavedAtTime != 0) {
long currTime = SystemClock.elapsedRealtime();
mTimeLog.log("Reverting to NITZ time, currTime=" + currTime
+ " mSavedAtTime=" + mSavedAtTime + " mSavedTime=" + mSavedTime);
setAndBroadcastNetworkSetTime(mSavedTime + (currTime - mSavedAtTime));
}
}
可见监听到变化时,首先判断是否需要更新时间,然后也是调用setAndBroadcastNetworkSetTime来更新时间和发广播。有人可能会问,当开关打开时RIL并没有上报事件,怎么设置时间?其实关键还是在setTimeFromNITZString方法中,不管是否更新时间,都会调用saveNitzTime方法将每次上报的时间存起来,所以才出现了在revertToNitzTime中设置时间时的时间计算公式为:mSavedTime + (currTime - mSavedAtTime)
讲完ServiceStateTracker之后我们再来看NetworkTimeUpdateService,这是一个系统服务,随着系统启动而启动:
android\frameworks\base\services\java\com\android\server\SystemServer.java
if (!disableNetwork && !disableNetworkTime) {
traceBeginAndSlog("StartNetworkTimeUpdateService");
try {
networkTimeUpdater = new NetworkTimeUpdateService(context);
ServiceManager.addService("network_time_update_service", networkTimeUpdater);
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
traceEnd();
}
接下来我们主要看一下NetworkTimeUpdateService,源码位置:
android\frameworks\base\services\core\java\com\android\server\NetworkTimeUpdateService.java
我们依旧从观察者入手:
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();
}
}
可见观察到值改变后交由Handler处理:
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;
}
}
}
发现三种时间都走的一个方法,onPollNetworkTime中首先判断是否需打开了自动更新功能,然后调用了onPollNetworkTimeUnderWakeLock,这个方法是重点:
private void onPollNetworkTimeUnderWakeLock(int event) {
final long refTime = SystemClock.elapsedRealtime();
if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
resetAlarm(mPollingIntervalMs);
return;
}
final long currentTime = System.currentTimeMillis();
if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED) {
if (mTime.getCacheAge() >= mPollingIntervalMs) {
mTime.forceRefresh();
}
if (mTime.getCacheAge() < mPollingIntervalMs) {
final long ntp = mTime.currentTimeMillis();
mTryAgainCounter = 0;
if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
|| mLastNtpFetchTime == NOT_SET) {
if (ntp / 1000 < Integer.MAX_VALUE) {
SystemClock.setCurrentTimeMillis(ntp);
}
}
mLastNtpFetchTime = SystemClock.elapsedRealtime();
} else {
mTryAgainCounter++;
if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
resetAlarm(mPollingIntervalShorterMs);
} else {
mTryAgainCounter = 0;
resetAlarm(mPollingIntervalMs);
}
return;
}
}
resetAlarm(mPollingIntervalMs);
}
第一个判断中,mNitzTimeSetTime在收到ServiceStateTracker发送的广播后被修改,否则就是初始值NOT_SET,也就是代表着是否经过NITZ更新。mPollingIntervalMs的值为86400000ms,就是24小时。这个判断就是如果时间已经更新过而且还不到下一个更新周期时,resetAlarm调用设置一个定时任务。
接下来看第二个判断,mLastNtpFetchTime 表示上次NTP更新时间,默认值也为NOT_SET。后面也是在判断是否在更新周期内,这个判断决定是否启用NTP机制开始更新时间。
接下来mTime是一个NtpTrustedTime类,这里运用的单例的方法:
mTime = NtpTrustedTime.getInstance(context);
这里是为了判断上次更新是否还有效,即24小时,相当于判断缓存有效时间,失效的话开始重新获得时间,否则就简单设置一下当前正确时间。下面我们看forceRefresh方法,不过先从那个单例说起:
android\frameworks\base\core\java\android\util\NtpTrustedTime.java
public static synchronized NtpTrustedTime getInstance(Context context) {
if (sSingleton == null) {
final Resources res = context.getResources();
final ContentResolver resolver = context.getContentResolver();
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;
if (null != sSingleton) {
final String backupServer = SystemProperties.get("persist.backup.ntpServer");
sSingleton.mNtpRetriesMax = res.getInteger(
com.android.internal.R.integer.config_ntpRetry);
if ((0 < sSingleton.mNtpRetriesMax) &&
(null != backupServer) &&
(0 != backupServer.length())) {
sSingleton.mBackupServer = (backupServer.trim()).replace("\"", "");
} else {
sSingleton.mNtpRetriesMax = 0;
sSingleton.mBackupServer = "";
}
}
}
return sSingleton;
}
相当于执行了初始化操作,设置了默认服务器地址:time.android.com。默认超时时间:5s。然后还有一个安全服务器和备用服务器根据具体情况进行设置。总之是为了构造一个NtpTrustedTime。然后看一下forceRefresh:
@Override
public boolean forceRefresh() {
if (TextUtils.isEmpty(mServer)) {
// missing server, so no trusted time available
System.out.println("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();
String mTargetServer = mServer;
if (true == getBackupmode()) {
this.setBackupmode(false);
mTargetServer = mBackupServer;
}
if (LOGD) Log.d(TAG, "Ntp Server to access at:" + mTargetServer);
if (client.requestTime(mTargetServer, (int) mTimeout)) {
mHasCache = true;
mCachedNtpTime = client.getNtpTime();
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else {
countInBackupmode();
return false;
}
}
首先确保服务器不为空,在判断是否有网络链接,然后构造一个真正去访问网络的SntpClient 对象,然后请求时间requestTime:
android\frameworks\base\core\java\android\net\SntpClient.java
public boolean requestTime(String host, int timeout) {
InetAddress address = null;
try {
address = InetAddress.getByName(host);
} catch (Exception e) {
EventLogTags.writeNtpFailure(host, e.toString());
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;
final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_NTP);
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
final long requestTime = System.currentTimeMillis();
final long requestTicks = SystemClock.elapsedRealtime();
writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
socket.send(request);
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);
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);
checkValidServerReply(leap, mode, stratum, transmitTime);
long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
if (DBG) {
Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
"clock offset: " + clockOffset + "ms");
}
mNtpTime = responseTime + clockOffset;
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
EventLogTags.writeNtpFailure(address.toString(), e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
if (socket != null) {
socket.close();
}
TrafficStats.setThreadStatsTag(oldTag);
}
return true;
}
了解socket的朋友看了应该就知道,使用的是UDP协议访问,服务端端口号为123。代码虽然有点长,但是也很好理解,就是标准的UDP通信例子。这里得到响应后,取出时间数据。最后回到NetworkTimeUpdateService中的onPollNetworkTimeUnderWakeLock方法设置时间,在调用完mTime.forceRefresh()后,也刷新了缓存时间,所以if (mTime.getCacheAge() < mPollingIntervalMs)的判断得以执行,又合并到了缓存原本有效的逻辑中,可见源码写的还是很巧妙的,并没有分别去执行设置时间的逻辑。最后设置一个下次更新时间的Alarm。
到这里自动更新时间的逻辑就分析完了,这个机制在Android中算是比较简单的一个,由于需要访问外部网络,所以也比较特殊,感兴趣的可以详细阅读源码。