好久没更新博客了,这一年来实在是太忙,上班、洗衣、做饭、看书、奶爸,忙确实是忙。最近工作中刚好研究了下Android8.1系统的自动同步时间机制,也学到了一点东西,leader也叫分享下,趁着这个机会就简单分享下我对Android8.1自动同步时间机制的理解:
公司的测试妹子提了个问题:链接上wifi,无法自动更新时间与日期,导致wifi无法正常使用,看了这个问题吓了一跳,wifi不能使用,这个会涉及到Android系统wifi模块的,这个模块可大了,研究起来不是一时半会能解决的,此时我的心情是这样的:
后来跟测试妹子沟通了,其实跟wifi模块没多大关系,wifi不能使用是由于Android系统时间没有更新导致的,当把系统时间调整到当前时间或者重启设备开机,发现设置里的自动同步日期和时间生效了,所以刚才那个问题缩小到是无法自动更新时间和日期这个原因。我们知道Android设备刷机后,设备需要去同步时间,也即是去请求谷歌服务器同步到最新的当前时间,如果同步失败,那么Android设备显示的时间就会与实际时间不同,感觉一个在中国,一个在美国。Android设备开机后并且设备wifi连上了,这时设备会去请求同步时间,偷偷地截图如下:
接下来我借助代码与日志来分析一波:
自动同步日期和时间功能路径:设置>通用>日期和时间>自动同步日期与时间,定位到相应代码模块如下:
上面的代码在Settings应用里只是修改了系统的一个属性:Settings.Global.AUTO_TIME,其值为"auto_time“,当我们打开或关闭自动同步日期与时间功能时,它将会把auto_time的值相应地修改为1或者0。此刻ContengProvider值发生变化了,其他地方对它有监听的都会发生相应变化,这时发现想解决问题还是得从framework层去定位解决。
通过对源码的搜索,找到了相应的地方,其路径如下:
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
在NetworkTimeUpdateService中我发现了如下代码:
/** 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();
Log.d("Andrew", "NetworkTimeUpdateService SettingsObserver onChange...");
}
}
上面代码块是对Settings.Global.AUTO_TIME监听,只要其值发生了变化,就会回调onChange方法,而onChange方法里用了一个Handler,找到它一看究竟,其代码定义如下:
/** 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;
}
}
}
当Settings.Global.AUTO_TIME发生变化时,发送EVENT_AUTO_CHANGED给handler处理,最终执行的函数是onPollNetworkTime方法,我们一起来看下:
private void onPollNetworkTime(int event) {
// If Automatic time is not set, don't bother. Similarly, if we don't
// have any default network, don't bother.
Log.d("Andrew", "onPollNetworkTime >>"+(!isAutomaticTimeRequested() || mDefaultNetwork == null));
if (!isAutomaticTimeRequested() || mDefaultNetwork == null) return;
mWakeLock.acquire();
try {
onPollNetworkTimeUnderWakeLock(event);
} finally {
mWakeLock.release();
}
}
方法中第一个if条件语句是用来判断当前自动同步时间是否打开,如果是关闭的,也就是说Settings.Global.AUTO_TIME为0时,方法会返回,后面的代码不再执行。换一种说法就是只有当Settings.Global.AUTO_TIME为1时,后面才会执行。好的,我们接着分析onPollNetworkTimeUnderWakeLock方法,这个方法比较长,其代码如下:
private void onPollNetworkTimeUnderWakeLock(int event) {
//mNitzTimeSetTime 手机设置运营商时间的时刻
//refTime 手机当前时刻
//POLLING_INTERVAL_MS 24L * 60 * 60 * 1000; // 24 hrs
//所以在24小时内已经设置了运营商时间了,那么不设置网络时间
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
Log.d("Andrew", "onPollNetworkTimeUnderWakeLock1 >> "+(mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED));
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
Log.d("Andrew", "onPollNetworkTimeUnderWakeLock2 >> "+(mTime.getCacheAge() >= mPollingIntervalMs));
//如果网络时间的缓存超过了一天,那么重新获取网络时间,具体的怎么获取在介绍完这个函数再谈。
//if (mTime.getCacheAge() >= mPollingIntervalMs) {
mTime.forceRefresh();
//}
// only update when NTP time is fresh
// 抽出了 if 语句中的核心代码,ntp 为网络时间缓存中的时间,当 ntp 和本地时间的误差大于 TIME_ERROR_THRESHOLD_MS = 5000 时,设置时间。
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);
}
在上面我们看到了获取网络时间的关键代码是mTime.forceRefresh()
,我们找到mTime
的定义mTime = NtpTrustedTime.getInstance(context)
,顺藤摸瓜,找NtpTrustedTime
。我们就直接跳到网络请求的关键代码吧。
其路径为:frameworks/base/core/java/android/util/NtpTrustedTime.java
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();
String mTargetServer = mServer;
if (true == getBackupmode()) {
this.setBackupmode(false);
mTargetServer = mBackupServer;
}
if (LOGD) Log.d(TAG, "Ntp Server to access at:" + mTargetServer);
final String backServer = "ntp1.aliyun.com";
if (client.requestTime(mTargetServer, (int) mTimeout)) {
Log.d("Andrew", "request mTargetServer success...");
mHasCache = true;
mCachedNtpTime = client.getNtpTime();
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else if(client.requestTime(backServer, (int) mTimeout)){
Log.d("Andrew", "request backServer success...");
mHasCache = true;
mCachedNtpTime = client.getNtpTime();
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else {
Log.d("Andrew", "request failed...");
countInBackupmode();
return false;
}
}
SNTP
,是一种简单的NTP
协议,误差要比NTP
大一点,Android
帮我们封装了SntpClient
,所以我们要自己获取网络时间的话,就可以直接拿来用了。这边我比较好奇的是Android
从哪个服务器获取的时间。defaultServer = res.getString(com.android.internal.R.string.config_ntpServer)
,我们再找对应的字符串config.xml。
其代码路径为:frameworks/base/core/res/res/values/config.xml
time.android.com
这样我们就知道谷歌的时间服务器地址是time.android.com,谷歌也设置了一个备份的服务器:0.pool.ntp.org,我们可以从下面代码看出:
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;
}
不过我现在还没找到这个0.pool.ntp.org在哪定义的?谷歌暂时用到服务器暂时只发现这两个,可能还有其他的,当然还有国内的NTP服务器,如:
cn.ntp.org.cn #中国
edu.ntp.org.cn #中国教育网
ntp1.aliyun.com #阿里云
ntp2.aliyun.com #阿里云
cn.pool.ntp.org #最常用的国内NTP服务器
这次的问题解决我在forceRefresh方法中增加了阿里云NTP服务器的请求,并去掉了网络缓存时间的判断直接去请求服务器的时间。
1、本次分享只做了NTP服务器请求流程的简单分析。
2、遗留问题,leader所说的每次请求失败重新尝试连接的问题。
3、更深层次分析