NTP和NITZ的简介

NITZ:Network Identity and Time Zone(网络标识和时区),NITZ是一种GSM/WCDMA基地台方式,必须插入SIM卡,且需要运营商支持,从运营商处获取时间和时区信息。中国大陆运营商基本是不支持的。
NTP:Network Time Protocol(网络时间协议),用来同步网络中各个计算机的时间的协议。在Android设备中,NTP更新时间往往是通过网络(GPRS或WIFI)向NTP服务器获取时间(不包含时区信息)。

NITZ更新时间流程

NITZ更新时间依赖运营商,当运营商基站发出更新时间的消息,基站附近的手机接收到对应消息后,会通过RIL层上报UNSOL_NITZ_TIME_RECEIVED事件,此时ServiceStateTracker便会处理相关时间更新流程,相关时序图如下:

android ui同步更新 android系统同步在哪_java

由于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

NTP时间更新流程

NTP时间更新是通过网络(即GPRS或WIFI)去获取时间,在NetworkTimeUpdateService实现的,整体流程如下:

android ui同步更新 android系统同步在哪_运营商_02

在SystemServer中, 会启动NetworkTimeUpdateService服务:

//frameworks/base/services/java/com/android/server/SystemServer.java
    private void startOtherServices() {
        try {
            // ......
            if (!disableNetwork && !disableNetworkTime) {
                try {
                    Slog.i(TAG, "NetworkTimeUpdateService");
                    networkTimeUpdater = new NetworkTimeUpdateService(context);
                } catch (Throwable e) {
                    reportWtf("starting NetworkTimeUpdate service", e);
                }
            }
            // ......
        }       
        // ......

        mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
                // ......
                try {
                    if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
                } catch (Throwable e) {
                    reportWtf("Notifying NetworkTimeService running", e);
                }
                // ......
            }
        }

即SystemServer中会调用networkTimeUpdaterF.systemRunning()注册各种监听, 如下:

//frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

    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中监听什么 ?
在registerForConnectivityIntents中监听网络状态改变(CONNECTIVITY_ACTION)的广播.
在SettingsObserver里监听Settings.Global.AUTO_TIME值的改变, 在这个值发生变化时, 发EVENT_AUTO_TIME_CHANGE消息

在监听到上述动作/广播后, 会在MyHandler中得到处理, 即调用onPollNetworkTime(), 如下:

private void onPollNetworkTime(int event) {
        // If Automatic time is not set, don't bother.
        if (!isAutomaticTimeRequested()) return;

        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) {
                /* M: For multiple NTP server retry */
                // mTime.forceRefresh();
                int index = mTryAgainCounter % mNtpServers.size();
                if (DBG) Log.d(TAG, "mTryAgainCounter = " + mTryAgainCounter + ";mNtpServers.size() = " + mNtpServers.size() + ";index = " + index+ ";mNtpServers = " + mNtpServers.get(index));
                if (mTime instanceof NtpTrustedTime) {
                    ((NtpTrustedTime)mTime).setServer(mNtpServers.get(index));
                    mTime.forceRefresh();
                    ((NtpTrustedTime)mTime).setServer(mDefaultServer);
                } else {
                    mTime.forceRefresh();
                }
                /* M: For multiple NTP server retry */
            }

            // 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);
    }

没有太复杂的逻辑, 就是网络获取时间, 并与本机时间比较, 如果时间差大于mTimeErrorThresholdMs值, 就更新本机时间.

这里涉及到几个阈值:
mPollingIntervalMs: 当NTP时间获取成功后,再次请求NTP时间的间隔
mPollingIntervalShorterMs: 当NTP时间获取失败后,再次请求NTP时间的间隔
mTimeErrorThresholdMs: 当NTP时间和系统时间不相同时,如果时间差大于此阀值,则更新系统时间
mTryAgainCounter: Retry的最大次数

这几个值都是在系统资源的config.xml配置的:

//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>
    <!-- Normal polling frequency in milliseconds -->
    <integer name="config_ntpPollingInterval">864000000</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. -->
    <integer name="config_ntpTimeout">20000</integer>

更新时间的关键是调用 mTime.forceRefresh(), 即NtpTrustedTime类的forceRefresh()方法, 如下:

//frameworks/base/core/java/android/util/NtpTrustedTime.java
    public boolean forceRefresh() {
        // ......
        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;
        }
    }

即调用SntpClient, 通过UDP方式向NTP Server获取时间:

//frameworks/base/core/java/android/net/SntpClient.java
    public boolean requestTime(String host, int timeout) {
        DatagramSocket socket = null;
        try {
            socket = new DatagramSocket();
            socket.setSoTimeout(timeout);
            InetAddress address = InetAddress.getByName(host);
            byte[] buffer = new byte[NTP_PACKET_SIZE];
            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_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
            long requestTime = System.currentTimeMillis();
            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);
            long responseTicks = SystemClock.elapsedRealtime();
            long responseTime = requestTime + (responseTicks - requestTicks);

            // extract the results
            long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
            long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
            long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
            long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
            long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;


            // 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 (false) Log.d(TAG, "request time failed: " + e);
            return false;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }

        return true;
    }

上面requestTime()代码涉及到NTP取时的校正问题, 请参考NTP协议与计算

如果遇到NTP无法更新的问题, 需要分析logcat, 看看是否有SocketException或unknown host之类的网络错误. 可能的原因:
- 网络不可用
- NTP Server连接不上

总结

  1. NITZ的优先级要高于NTP的优先级,当NITZ更新系统时间后,NTP即使触发更新条件,也会检查NITZ更新时间距今是否超过864000000毫秒(10天,config_ntpPollingInterval),若不满10天,则重设Alarm并取消此次NTP更新请求。
  2. NITZ主要依赖于运营商上报,NTP则主要依赖于网络环境,NITZ通过被动接收获取时间,NTP通过访问NTP Server主动获取网络时间,最后都是通过调用SystemClock.setCurrentTimeMillis更新本机时间。