Android 時間更新NITZ和NTP詳解

1、NTP和NITZ簡介
最近在項目中遇到手機首次插上移動卡時不能自動更新時間的問題,就特意跟了下Android系統中手機時間更新有兩種方式NTP和NITZ,下面先來看看NTP和NITZ的簡介
NITZ:Network Identity and Time Zone(網絡標識和時區),是一種用於自動配置本地的時間和日期的機制,需要運營商支持,可從運營商獲取時間和時區具體信息。
NTP:Network Time Protocol(網絡時間協議),用來同步網絡中各個計算機的時間的協議。在手機中,NTP更新時間的方式是通過GPRS或wifi向特定服務器獲取時間信息(不包含時區信息)。
接着我們來看看兩種方式具體更新流程
2、NITZ更新時間流程
NITZ更新時間依賴運營商,當運營商基站發出更新時間的消息,基站附近的手機接收到對應消息後,會通過RIL層上報UNSOL_NITZ_TIME_RECEIVED事件,此時ServiceStateTracker便會處理相關時間更新流程,
相關時序圖如下:

    

由於NITZ主要依賴於運營商,但在國內移動和聯通貌似不怎麼好用,在這裏就不在詳細說了,簡單總結下如下:
1、在ServiceStateTracker構造方法裏調用setOnNITZTime註冊RIL事件RIL_UNSOL_NITZ_TIME_RECEIVED
2、RIL層上報RIL_UNSOL_NITZ_TIME_RECEIVED,在ServiceStateTracker的handleMessage裏處理
3、調用ServiceStateTracker的setTimeFromNITZString設置時間和時區,在setAndBroadcastNetworkSetTime裏調用setCurrentTimeMillis設置系統時間,併發送廣播通知NetworkTimeUpdateService

3、NTP時間更新流程
NTP時間更新主要依賴於GPRS和wifi,即通過網絡的方式去獲取時間,在NetworkTimeUpdateService中調用onPollNetworkTime訪問NtpServer獲取網絡時間,我們先來看看整體流程


當SystemServer啓動,會調用networkTimeUpdaterF.systemRunning()初始化各種NTP request監聽


    public void systemRunning() {
        registerForTelephonyIntents();
        registerForAlarms();
        registerForConnectivityIntents();
 
        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();
 
        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
        mSettingsObserver.observe(mContext);
    }
在registerForTelephonyIntents中主要是監聽ACTION_NETWORK_SET_TIME和ACTION_NETWORK_SET_TIMEZONE的廣播,registerForAlarms中監聽"com.android.server.NetworkTimeUpdateService.action.POLL"廣播,registerForConnectivityIntents監聽網絡狀態改變的廣播,SettingsObserver裏監聽Settings.Global.AUTO_TIME值的改變

//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
    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:
                    onPollNetworkTime(msg.what);
                    break;
            }
        }
    }
在這個MyHandler中可以看到,當上面某種監聽觸發時都會調用onPollNetworkTime,而這個方法裏主要調用了onPollNetworkTimeUnderWakeLock,接下來看看這個方法


//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
    private void onPollNetworkTimeUnderWakeLock(int event) {
        final long refTime = SystemClock.elapsedRealtime();
        // 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);
        // Get the NTP time
        if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
                || event == EVENT_AUTO_TIME_CHANGED) {
            if (DBG) Log.d(TAG, "Before Ntp fetch");
 
            // force refresh NTP cache when outdated
            if (mTime.getCacheAge() >= mPollingIntervalMs) {
                mTime.forceRefresh();
            }
 
            // only update when NTP time is fresh
            if (mTime.getCacheAge() < mPollingIntervalMs) {
                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 = SystemClock.elapsedRealtime();
            } else {
                // Try again shortly
                mTryAgainCounter++;
                if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                    resetAlarm(mPollingIntervalShorterMs);
                } else {
                    // Try much later
                    mTryAgainCounter = 0;
                    resetAlarm(mPollingIntervalMs);
                }
                return;
            }
        }
        resetAlarm(mPollingIntervalMs);
    }
這個方法中主要調用了TrustedTime實例的forceRefresh方法去獲取時間,獲取之後通過mTime.currentTimeMillis獲得獲取成功之後的時間ntp,最後調用 SystemClock.setCurrentTimeMillis(ntp)設置系統時間有幾個參數需要特別說下

    public NetworkTimeUpdateService(Context context) {
        mContext = context;
        mTime = NtpTrustedTime.getInstance(context);
        ...
        //正常的輪詢頻率
        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);
        ...
    }
對應參數的配置如下,可能存在overlay替換,此處是framework裏默認配置的值

//frameworks/base/core/res/res/values/config.xml
    <!-- 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>
上面講到主要通過TrustedTime實例的forceRefresh獲取時間,下面就來跟下這個方法

//frameworks/base/core/java/android/util/NtpTrustedTime.java
    public boolean forceRefresh() {
        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 = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
            }
        }
 
        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");
        final SntpClient client = new SntpClient();
        if (client.requestTime(mServer, (int) mTimeout)) {
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            return false;
        }
    }
上面方法中主要是通過SntpClient的requestTime根據傳入的mServer獲取時間,mServer是在調用NtpTrustedTime的getInstance中初始化的,具體如下

    public static synchronized NtpTrustedTime getInstance(Context context) {
        if (sSingleton == null) {
            final Resources res = context.getResources();
            final ContentResolver resolver = context.getContentResolver();
 
            final String defaultServer = res.getString(
                    com.android.internal.R.string.config_ntpServer);
            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;
    }
config_ntpServer和config_ntpTimeout也是在framework下res中配置的,Settings.Global.NTP_SERVER和Settings.Global.NTP_TIMEOUT是配置在SettingProvider中的,就不在具體說明了。從上面代碼可看出server是由secureServer和defaultServer決定的

//frameworks/base/core/res/res/values/config.xml
    <!-- Remote server that can provide NTP responses. -->
    <string translatable="false" name="config_ntpServer">2.android.pool.ntp.org</string>
    <!-- Timeout to wait for NTP server response in milliseconds. -->
    <integer name="config_ntpTimeout">5000</integer>
其中SntpClient主要是提供訪問Ntp server的一個類,在requestTime中主要通過DatagramSocket訪問傳入的server,來獲取時間,具體實現如下:

//frameworks/base/core/java/android/net/SntpClient.java
    public boolean requestTime(InetAddress address, int port, int timeout) {
        DatagramSocket socket = null;
        try {
            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;
    }
其實Ntp說的明白點,就是通過網絡去獲取時間然後更新系統時間,具體流程上面也簡單說了下,當遇到手機不能更新時間時,先要看看網絡是否可用,網絡可用的情況下,就得看看對應的NtpServer是否能夠訪問。曾經就遇到過移動的數據業務不能訪問NtpServer,導致手機不能更新時間。一般可以通過ntp等關鍵字在log中搜索,一般是SocketException或unknown host的錯誤。也可以直接adb shell命令進去手機,然後利用ping NtpServer 查看當前服務器是否可訪問。在這裏需要說的一點是,NtpServer一般都是對應的網址,訪問網絡時會根據當前的運營商網絡,找到對應的IP地址,再去訪問,有可能存在同一個NtpServer聯通網絡可以訪問而移動網絡不行的情況。
4、NITZ和NTP的總結
①NITZ的優先級要高於NTP的優先級,當NITZ更新系統時間後,NTP即使觸發更新條件,也會檢查NITZ更新時間距今是否超過864000000毫秒(10天,config_ntpPollingInterval),若不滿10天,則重設Alarm並取消此次NTP更新請求。
②NITZ主要依賴於運營商上報,NTP則主要依賴於網絡環境,NITZ通過被動接收穫取時間,NTP通過訪問NtpServer獲取網絡時間,最後都是通過調用SystemClock.setCurrentTimeMillis更新手機時間。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章