Android 5.1.1 时间同步方式

来源:互联网 发布:国家承认网络教育 编辑:程序博客网 时间:2024/06/06 14:20

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便会处理相关时间更新流程,相关时序图如下:
这里写图片描述

由于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实现的,整体流程如下:
这里写图片描述

在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更新本机时间。

参考:
- Android中时间维护
- Android 7.1.1时间更新NITZ和NTP详解
- Android中通过NTP服务器获取时间功能源码分析