> 文档中心 > 网络时间同步机制

网络时间同步机制


网络时间同步机制

系统版本: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 = 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()  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 <> 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;    }