网络时间同步机制
系统版本:android7.1
涉及类
//监控网络时间并更新系统时间策略类
NetworkTimeUpdateService.java
//与远程 NTP 服务器连接,获取网络时间
NtpTrustedTime.java
//真正接受网络时间的类
SntpClient.java
NetworkTimeUpdateService解析
NetworkTimeUpdateService启动流程
NetworkTimeUpdateService是在SystemServer.java里面的startOtherServices方法中创建的,分别调用NetworkTimeUpdateService的构造方法和systemReady方法,最终网络同步系统时间服务启动完成。
private void startOtherServices() {
...
NetworkTimeUpdateService networkTimeUpdater = null;
...
//初始化时间同步服务
networkTimeUpdater = new NetworkTimeUpdateService(context);
//添加到binder管理
ServiceManager.addService("network_time_update_service", networkTimeUpdater);
...
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
mActivityManagerService.systemReady(new Runnable() {
...
try {
if (networkStatsF != null) networkStatsF.systemReady();
} catch (Throwable e) {
reportWtf("making Network Stats Service ready", e);
}
...
}
}
NetworkTimeUpdateService构造方法解析
主要的工作内容
- 创建NtpTrustedTime对象,该对象用于获取网络时间。
- 设置更新时间的广播Intent
- 获取framework/base/core/res/res/value/config.xml中的配置信息
//继承Binder,这样就可以添加到ServiceManager中
public class NetworkTimeUpdateService extends Binder {
public NetworkTimeUpdateService(Context context) {
mContext = context;
//用于连接ntp服务器,获取网络时间
mTime = NtpTrustedTime.getInstance(context);
//获取AlarmManager对象,用来设置系统时间
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
//com.android.server.NetworkTimeUpdateService.action.POLL广播
Intent pollIntent = new Intent(ACTION_POLL, null);
//设置请求更新时间广播
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
//24小时
mPollingIntervalMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingInterval);
//1分钟
mPollingIntervalShorterMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingIntervalShorter);
//3次
mTryAgainTimesMax = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpRetry);
//5秒
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);
//持有唤醒锁
mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
}
systemReady解析
主要的工作内容
- 创建sim同步运营商同步时间监听。
- 创建com.android.server.NetworkTimeUpdateService.action.POLL广播监听。
- 创建网络变化广播监听
- 创建带handler的子线程,用于后面获取网络时间和同步时间到系统中
- 发送同步时间广播,执行开机后第一次时间同步
- 设置SettingsObserver对象监听Settings.Global.AUTO_TIME属性变化,就是系统设置里面那个是否开启同步时间的设置项修改的值。
/** Initialize the receivers and initiate the first NTP request */
public void systemRunning() {
//注册sim同步时间监听
registerForTelephonyIntents();
//注册com.android.server.NetworkTimeUpdateService.action.POLL广播
registerForAlarms();
//注册网络变化广播
registerForConnectivityIntents();
//创建带handler的子线程
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();
//监听Settings.Global.AUTO_TIME变化
mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
mSettingsObserver.observe(mContext);
}
其中registerForTelephonyIntents()要说明下,该方法监听的广播收到会改变mNitzTimeSetTime的值,但是不是更新系统时间,该ACTION_NETWORK_SET_TIME和ACTION_NETWORK_SET_TIMEZONE广播是sim卡同步运营商时间时,设置系统时间后再发出来的,故发送广播之前就同步时间,感兴趣可以在系统源码中搜索下ACTION_NETWORK_SET_TIME广播是哪里发的就可以找到同步的地方了。
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);
}
/** 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();
}
}
};
除了sim广播外,其他监听触发后都是给handler发送消息同步时间。先看下NetworkTimeUpdateService中的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:
//msg.what表示触发导致的同步时间
onPollNetworkTime(msg.what);
break;
}
}
}
最终调用的的onPollNetworkTime方法
onPollNetworkTime方法的主要工作内容:
- 判断Settings.Global.AUTO_TIME属性是否不为零,即自动同步时间开关是否打开,若没打开就停止同步流程。
- 调用onPollNetworkTimeUnderWakeLock,执行同步时间策略。
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();
}
}
onPollNetworkTimeUnderWakeLock的主要工作内容如下:
- 判断是否sim同步过时间,并且同步的时间间隔不超过24h,是的话停止同步,等待24小时候再同步,防止多次同步。
- ntp没有同步过时间或者距离上次同步时间超过24小时,或者Settings.Global.AUTO_TIME改变,则开始时间同步,否则再等24小时同步时间。
- 调用NtpTrustedTime的forceRefresh方法获取ntp时间。
- 调用NtpTrustedTime的currentTimeMillis方法获取从ntp服务上获取的时间。
- 调用SystemClock.setCurrentTimeMillis将同步的时间设置到系统中。
- 若forceRefresh获取时间失败会另外有3次机会从ntp上获取时间,间隔5秒,三次都失败了则再过24小时同步时间。
private void onPollNetworkTimeUnderWakeLock(int event) {
final long refTime = SystemClock.elapsedRealtime();
//sim卡时间同步过,并且同步时时间不到24小时
// 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);
//没有同步过时间,或者上次时间同步超过24小时,或者Settings.Global.AUTO_TIME属性改变引起的
// Get the NTP time
if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED) {
if (DBG) Log.d(TAG, "Before Ntp fetch");
//距上次同步时间过了24小时获取没有同步过
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs) {
//重新获取网络时间
mTime.forceRefresh();
}
//距离上次时间小于24小时,说明同步时间成功,24小时内同步过一次
// only update when NTP time is fresh
if (mTime.getCacheAge() < mPollingIntervalMs) {
//获取ntp时间
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属性
mLastNtpFetchTime = SystemClock.elapsedRealtime();
} else {
//说明同步时失败,没5秒同步一次,同步三次,三次后再过24小时
// Try again shortly
mTryAgainCounter++;
if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
resetAlarm(mPollingIntervalShorterMs);
} else {
// Try much later
mTryAgainCounter = 0;
resetAlarm(mPollingIntervalMs);
}
return;
}
}
resetAlarm(mPollingIntervalMs);
}
NtpTrustedTime与SntpClient解析
前面已经研究完同步策略了,下面讲下,如何系统是如何与ntp服务器联系并且获取到网络时间的。先讲下NtpTrustedTime,该类是个单例,所以从它的getInstance方法讲起。主要做如下工作:
- 获取ntp服务器的地址和连接超时时间。
- 创建NtpTrustedTime对象,将ntp服务器的地址和连接超时时间和地址传入进去。
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服务器地址 2.android.pool.ntp.org
final String defaultServer = res.getString(
com.android.internal.R.string.config_ntpServer);
//5秒
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;
}
解析获取网络时间方法forceRefresh,主要工作如下:
- 判断设备是否有网。
- 创建SntpClient对象,用于获取网络时间的。
- 向ntp请求网络时间。
- 获取网络请求的时间进行保存。
public boolean forceRefresh() {
...
//设备是否有网
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");
//创建SntpClient对象,用于获取网络时间的
final SntpClient client = new SntpClient();
//向ntp请求网络时间
if (client.requestTime(mServer, (int) mTimeout)) {
//表示有缓冲
mHasCache = true;
//将SntpClient获取的网络时间同步过来
mCachedNtpTime = client.getNtpTime();
//记录相应ntp后的机器开机后的时间
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else {
return false;
}
}
NetworkTimeUpdateService中有调用NtpTrustedTime的getCacheAge和currentTimeMillis,我们依次解析下
public long getCacheAge() {
//mHasCache为false表示之前没有同步过时间
if (mHasCache) {
//获取距离上次同步时间的间隔
return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
} else {
return Long.MAX_VALUE;
}
}
public long currentTimeMillis() {
if (!mHasCache) {
throw new IllegalStateException("Missing authoritative time source");
}
if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
//计算出当前时间放回给调用者。
// current time is age after the last ntp cache; callers who
// want fresh values will hit makeAuthoritative() first.
return mCachedNtpTime + getCacheAge();
}
最后解析下SntpClient对象,真正与ntp服务其对接的逻辑都在这个类里面,才有socket通信。该类构造方法是默认构造方法,没有自己定义,我们直接解析它的requestTime方法。主要内容
- 根据host地址创建InetAddress对象。
- 创建DatagramSocket对象,用于ntp服务器通讯。
- 创建请求对象DatagramPacket的类对象request。
- 调用DatagramSocket对象的send方法,将请求传递给ntp服务器。
- 创建接收ntp服务器信息的DatagramPacket的类对象response。
- 调用socket.receive方法,将ntp服务器里的信息传递给response。
- response里面的信息赋值給SntpClient各个参数,供NtpTrustedTime调用。
public boolean requestTime(String host, int timeout) {
InetAddress address = null;
try {
//根据host创建网络地址
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对象
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;
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;
}