Android NTP机制详解(基于Android 9.0)

一、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

查看数据库
手动配置ntp服务器
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更新服务分析完成…

如有错误,敬请指正~~

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值