网络时间同步机制

网络时间同步机制

系统版本: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构造方法解析

主要的工作内容

  1. 创建NtpTrustedTime对象,该对象用于获取网络时间。
  2. 设置更新时间的广播Intent
  3. 获取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解析

主要的工作内容

  1. 创建sim同步运营商同步时间监听。
  2. 创建com.android.server.NetworkTimeUpdateService.action.POLL广播监听。
  3. 创建网络变化广播监听
  4. 创建带handler的子线程,用于后面获取网络时间和同步时间到系统中
  5. 发送同步时间广播,执行开机后第一次时间同步
  6. 设置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方法的主要工作内容:

  1. 判断Settings.Global.AUTO_TIME属性是否不为零,即自动同步时间开关是否打开,若没打开就停止同步流程。
  2. 调用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的主要工作内容如下:

  1. 判断是否sim同步过时间,并且同步的时间间隔不超过24h,是的话停止同步,等待24小时候再同步,防止多次同步。
  2. ntp没有同步过时间或者距离上次同步时间超过24小时,或者Settings.Global.AUTO_TIME改变,则开始时间同步,否则再等24小时同步时间。
  3. 调用NtpTrustedTime的forceRefresh方法获取ntp时间。
  4. 调用NtpTrustedTime的currentTimeMillis方法获取从ntp服务上获取的时间。
  5. 调用SystemClock.setCurrentTimeMillis将同步的时间设置到系统中。
  6. 若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方法讲起。主要做如下工作:

  1. 获取ntp服务器的地址和连接超时时间。
  2. 创建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,主要工作如下:

  1. 判断设备是否有网。
  2. 创建SntpClient对象,用于获取网络时间的。
  3. 向ntp请求网络时间。
  4. 获取网络请求的时间进行保存。
 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方法。主要内容

  1. 根据host地址创建InetAddress对象。
  2. 创建DatagramSocket对象,用于ntp服务器通讯。
  3. 创建请求对象DatagramPacket的类对象request。
  4. 调用DatagramSocket对象的send方法,将请求传递给ntp服务器。
  5. 创建接收ntp服务器信息的DatagramPacket的类对象response。
  6. 调用socket.receive方法,将ntp服务器里的信息传递给response。
  7. 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;
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值