一、NTP含义
NTP(Network Time Protocol)提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC。 NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。这样就有了准确而可靠的时间源。时间按NTP服务器的等级传播。与NITZ不同的是,NTP需要从专门的NTP服务器来获取时间,只要手机连接上网络,都可以实现时间的更新。
二、Android NTP 机制分析
- NTP服务创建
//frameworks/base/services/java/com/android/server/SystemServer.java
//是否为手表设备
boolean isWatch = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WATCH);
//通过属性控制是否需要创建NTP服务
boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime",
false);
...
if (!isWatch && !disableNetworkTime) {
traceBeginAndSlog("StartNetworkTimeUpdateService");
try {
if (useNewTimeServices) {
networkTimeUpdater = new NewNetworkTimeUpdateService(context);
} else {
networkTimeUpdater = new OldNetworkTimeUpdateService(context);
}
Slog.d(TAG, "Using networkTimeUpdater class=" + networkTimeUpdater.getClass());
ServiceManager.addService("network_time_update_service", networkTimeUpdater);
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
traceEnd();
}
从上面代码分析,如果是
watch
设备,则不创建NTP服务,其次,如果配置系统属性config.disable_networktime
属性为true
,同样不创建NTP服务。手机设备一般情况下是通过NewNetworkTimeUpdateService
创建NTP服务。下面着重分析NewNetworkTimeUpdateService
- NewNetworkTimeUpdateService分析
1、重要属性说明
//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
// Normal polling frequency
//正常请求间隔
private final long mPollingIntervalMs;
// Try-again polling interval, in case the network request failed
//请求失败重新请求间隔
private final long mPollingIntervalShorterMs;
// Number of times to try again
//请求失败重试次数
private final int mTryAgainTimesMax;
// If the time difference is greater than this threshold, then update the time.
//如果时间误差大于mTimeErrorThresholdMs,忽略缓存时间强制更新时间
private final int mTimeErrorThresholdMs;
//NTP时间同步实现类
private final NtpTrustedTime mTime;
2、属性初始化
//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java ## NetworkTimeUpdateService()
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);
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();
//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);
//Settings.Global.NTP_SERVER默认为null,可以通过更改xml文件或者指令更改NTP服务器
final String secureServer = Settings.Global.getString(
resolver, Settings.Global.NTP_SERVER);
//Settings.Global.NTP_TIMEOUT默认为null,可以通过更改xml文件或者指令更改请求超时时间
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;
});
3、更改属性配置,自定义NTP请求参数
- 通过framework-res.apk配置
//frameworks/base/core/res/res/values/config.xml
<!-- Remote server that can provide NTP responses. -->
<string translatable="false" name="config_ntpServer">time.android.com</string>
<!-- Normal polling frequency in milliseconds -->
<integer name="config_ntpPollingInterval">86400000</integer>
<!-- Try-again polling interval in milliseconds, in case the network request failed -->
<integer name="config_ntpPollingIntervalShorter">60000</integer>
<!-- Number of times to try again with the shorter interval, before backing
off until the normal polling interval. A value < 0 indicates infinite. -->
<integer name="config_ntpRetry">3</integer>
<!-- If the time difference is greater than this threshold in milliseconds,
then update the time. -->
<integer name="config_ntpThreshold">5000</integer>
<!-- Timeout to wait for NTP server response in milliseconds. -->
<integer name="config_ntpTimeout">5000</integer>
- 通过SettingProvider.apk配置
//frameworks/base/packages/SettingsProvider/res/values/defaults.xml 增加/修改ntp_server value值
<string name="ntp_server" translatable="false">cn.pool.ntp.org</string>
//将自定义ntp服务器插入到数据库
//frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
private void loadSecureSettings(SQLiteDatabase db) {
......
//add by lrh
loadStringSetting(stmt, Settings.Secure.NTP_SERVER,R.string.ntp_server);
//end
}
//更改请求超时时间与上面方法相同,不再赘述!
- 通过指令动态配置(调试使用)
settings [--user NUM] get namespace key
settings [--user NUM] put namespace key value
(namespace is one of {system, secure, global})
//串口输入(也可以用adb方式)
settings put global ntp_server ntp.test1.com
查看数据库
4、NTP请求与缓存
//frameworks/base/core/java/android/util/NtpTrustedTime.java
public boolean forceRefresh(Network network) {
//检查ntp服务器是否为null
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 = sContext.getSystemService(ConnectivityManager.class);
}
}
//检查网络是否连接
final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
if (ni == null || !ni.isConnected()) {
if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
return false;
}
//借助SntpClient进行NTP请求
if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
final SntpClient client = new SntpClient();
if (client.requestTime(mServer, (int) mTimeout, network)) {
//是否有缓存
mHasCache = true;
//
mCachedNtpTime = client.getNtpTime();
mCachedNtpElapsedRealtime = client.getNtpTimeReference();
mCachedNtpCertainty = client.getRoundTripTime() / 2;
return true;
} else {
return false;
}
}
NTP请求(请求端口为123
,通讯方式为TCP
通信)
public boolean requestTime(String host, int timeout, Network network) {
// This flag only affects DNS resolution and not other socket semantics,
// therefore it's safe to set unilaterally rather than take more
// defensive measures like making a copy.
network.setPrivateDnsBypass(true);
InetAddress address = null;
try {
address = network.getByName(host);
} catch (Exception e) {
EventLogTags.writeNtpFailure(host, e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
}
//NTP_PORT = 123
return requestTime(address, NTP_PORT, timeout, network);
}
public boolean requestTime(InetAddress address, int port, int timeout, Network network) {
DatagramSocket socket = null;
final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_NTP);
try {
socket = new DatagramSocket();
network.bindSocket(socket);
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;
EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
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) {
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;
}
以上涉及到的时间转换与计算请结合代码与协议进行分析
至此,NTP更新服务分析完成…
如有错误,敬请指正~~