Android系统更新时间的方式有两种:分别是NITZ和NTP。
NITZ:网络标识和时区(英语:Network Identity and Time Zone,缩写NITZ)是一种通过无线网络向移动设备提供本地日期和时间、时区、夏时制偏移,以及网络提供商身份信息的机制,这通常用于移动电话自动更新系统时间。NITZ技术自GSM 阶段2 Release 96版本开始成为官方标准的可选部分之一。
NTP:网络时间协议,英文名称:Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒),且可介由加密确认的方式来防止恶毒的协议攻击。NTP的目的是在无序的Internet环境中提供精确和健壮的时间服务。
NTP
Android系统使用NTP自动更新系统时间的触发机制有两种:
- 监听数据库字段AUTO_TIME,当这个字段发生变化的时候,会立即触发一次时间同步
- 网络连接发生变化,当网络接通,会触发一次时间检查和同步
- 定时更新机制,当预定的时间到了,会触发一次时间检查和同步
Android系统的使用NTP更新系统时间是在NetworkTimeUpdateService
服务里面实现的,首先看一下服务的初始化过程。
NetworkTimeUpdateService初始化
这是一个others服务,即是一个可选服务。这个服务运行在system_server进程里面,是在SystemServer
里面启动的。
frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
final Context context = mSystemContext;
NetworkTimeUpdateService networkTimeUpdater = null;
try {
// 展开1:实例化,创建服务,并添加为系统服务
networkTimeUpdater = new NetworkTimeUpdateService(context);
ServiceManager.addService("network_time_update_service", networkTimeUpdater);
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
try {
// 展开2:启动服务
if (networkTimeUpdaterF != null)
networkTimeUpdaterF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying NetworkTimeService running", e);
}
}
展开1:实例化,创建服务
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
public NetworkTimeUpdateService(Context context) {
mContext = context;
// 创建NTP实例,这个就是使用NTP协议取得时间的类
mTime = NtpTrustedTime.getInstance(context);
// 取得AlarmManager实例
mAlarmManager = mContext.getSystemService(AlarmManager.class);
// 取得网络连接管理类的实例
mCM = mContext.getSystemService(ConnectivityManager.class);
Intent pollIntent = new Intent(ACTION_POLL, null);
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
// 从系统配置文件config.ini取得NTP时间参数
// 默认是86400000ms,即1天
mPollingIntervalMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingInterval);
// 由于网络原因,时间同步失败后,retry的时间间隔,默认是60000ms,即60s
mPollingIntervalShorterMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpPollingIntervalShorter);
// retry的次数,默认是3次
mTryAgainTimesMax = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpRetry);
// 时间误差,默认是5000ms,当时间误差超过5s,会更新系统时间
mTimeErrorThresholdMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpThreshold);
mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
展开2:启动服务network_time_update_service
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
public void systemRunning() {
// 接收来自NITZ更新系统时间后的广播
registerForTelephonyIntents();
// 注册Alarm,用于定时更新系统时间
registerForAlarms();
HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new MyHandler(thread.getLooper());
// 注册网络回调,当网络发生变化的时候会回调这个callback
mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
// 创建ContentObserver,监听AUTO_TIME数据库字段的变化
mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
mSettingsObserver.observe(mContext);
}
监听AUTO_TIME更新系统时间
接着上面,分析一下SettingsObserver的工作原理。这块需要用到ContentObserver的知识。SettingsObserver
继承ContentObserver
,把需要监听的数据库字段传下去,
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
/** 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();
}
}
ContentObserver
的工作原理不在这里展开,后面会专门开一篇文章来写ContentObserver。当Settings.Global.AUTO_TIME
的值发生变化的时候,onChange()
方法会被回调,即发送消息EVENT_AUTO_TIME_CHANGED
。接受消息,更新时间的地方最后展开。
监听网络连接变化更新系统时间
private class NetworkTimeUpdateCallback extends NetworkCallback {
@Override
public void onAvailable(Network network) {
Log.d(TAG, String.format("New default network %s; checking time.", network));
mDefaultNetwork = network;
// Running on mHandler so invoke directly.
onPollNetworkTime(EVENT_NETWORK_CHANGED);
}
@Override
public void onLost(Network network) {
if (network.equals(mDefaultNetwork)) mDefaultNetwork = null;
}
}
当网络连接建立的时候,会回调onAvailable()
方法,即调用方法onPollNetworkTime()
更新系统时间。
定时更新系统时间机制
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));
}
接收广播ACTION_POLL
,然后发送消息EVENT_POLL_NETWORK_TIME
来更新系统时间。
更新系统时间
/** 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: // 接前面,AUTO_TIME数据库字段变化了
case EVENT_POLL_NETWORK_TIME: // 接前面,轮询时间到
case EVENT_NETWORK_CHANGED: // 接前面,新网络连接建立
onPollNetworkTime(msg.what);
break;
}
}
}
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.
if (mDefaultNetwork == null) return;
mWakeLock.acquire();
try {
onPollNetworkTimeUnderWakeLock(event);
} finally {
mWakeLock.release();
}
}
private void onPollNetworkTimeUnderWakeLock(int event) {
// Force an NTP fix when outdated
// 距离上次更新时间大于等于1天,会重新从ntp server获取时间
if (mTime.getCacheAge() >= mPollingIntervalMs) {
if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
mTime.forceRefresh();
}
// 如果成功从ntp server获取到时间,则重置定时器,并更新系统时间
if (mTime.getCacheAge() < mPollingIntervalMs) {
// Obtained fresh fix; schedule next normal update
resetAlarm(mPollingIntervalMs);
if (isAutomaticTimeRequested()) {
updateSystemClock(event);
}
// 否则,进入retry环节
} else {
// No fresh fix; schedule retry
mTryAgainCounter++;
// retry次数没用完,则用60s重置定时器
if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
resetAlarm(mPollingIntervalShorterMs);
// 否则,用1天重置定时器
} else {
// Try much later
mTryAgainCounter = 0;
resetAlarm(mPollingIntervalMs);
}
}
}
private void updateSystemClock(int event) {
// 如果是用户打开自动更新时间的设置,则是force update
final boolean forceUpdate = (event == EVENT_AUTO_TIME_CHANGED);
if (!forceUpdate) {
// 如果一天之内通过NITZ更新过系统时间,则不再更新
if (getNitzAge() < mPollingIntervalMs) {
if (DBG) Log.d(TAG, "Ignoring NTP update due to recent NITZ");
return;
}
final long skew = Math.abs(mTime.currentTimeMillis() - System.currentTimeMillis());
// 当ntp server的时间和系统时间误差小于5s时,不再更新系统时间
if (skew < mTimeErrorThresholdMs) {
if (DBG) Log.d(TAG, "Ignoring NTP update due to low skew");
return;
}
}
// 更新系统时间,实际是通过AlarmManagerService更新系统时间,这里不再展开
SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis());
}
NtpTrustedTime
想要进一步查看Android系统ntp的实现代码,可以查看下面的源文件。
frameworks\base\core\java\android\util\TrustedTime.java
frameworks\base\core\java\android\util\NtpTrustedTime.java
frameworks\base\core\java\android\net\SntpClient.java
NITZ
NITZ相关的代码在telephony模块,在如下位置:
frameworks/opt/telephony
延伸学习
本文主要分享Android系统通过NTP自动更新系统时间的原理和实现过程。这里面用到了一些技术,大家可以延伸学习:
- ContentObserver
- PendingIntent
- Alarm Manager/Service