Android 时间同步机制及常见问题分析

近期Android设备多次遇到时间不准的问题,借此机会分析Android 时间同步更新机制。

此篇文章将分析Android 时间同步更新机制的构成及常见导致时间不准的情况。

一.Android 时间同步更新机制的构成:

1.由android启动流程可知,系统服务由SystemServer启动,在SystemServer查找到时间更新服务。

frameworks\base\services\java\com\android\server\SystemServer.java

SystemServer->startOtherServices->networkTimeUpdaterF.systemRunning();

 ​​​​

final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;

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

 2.NetworkTimeUpdateService启动后首先发出EVENT_POLL_NETWORK_TIME调用onPollNetworkTime()尝试更新时间,

 

    private void onPollNetworkTimeUnderWakeLock(int event) {
        // Force an NTP fix when outdated
        NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();
        if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {
            if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
            mTime.forceRefresh();
            cachedNtpResult = mTime.getCachedTimeResult();
        }

        if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {
            // Obtained fresh fix; schedule next normal update
            resetAlarm(mPollingIntervalMs);

            // Suggest the time to the time detector. It may choose use it to set the system clock.
            TimestampedValue<Long> timeSignal = new TimestampedValue<>(
                    cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
            NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
            timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateService. event=" + event);
            mTimeDetector.suggestNetworkTime(timeSuggestion);
        } else {
            // No fresh fix; schedule retry
            mTryAgainCounter++;
            if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                resetAlarm(mPollingIntervalShorterMs);
            } else {
                // Try much later
                mTryAgainCounter = 0;
                resetAlarm(mPollingIntervalMs);
            }
        }
    }

看样子onPollNetworkTimeUnderWakeLock()似乎是时间更新的核心,看看它干了什么。

private final NtpTrustedTime mTime;

frameworks\base\core\java\android\util\NtpTrustedTime.java

/**
     * Returns an object containing the latest NTP information available. Can return {@code null} if
     * no information is available.
     */
    @Nullable
    public TimeResult getCachedTimeResult() {
        return mTimeResult;
    }
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean forceRefresh() {
        synchronized (this) {
            NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
            if (connectionInfo == null) {
                // missing server config, so no trusted time available
                if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
                return false;
            }

            ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
            if (connectivityManager == null) {
                if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
                return false;
            }
            final Network network = connectivityManager.getActiveNetwork();
            final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
            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();
            final String serverName = connectionInfo.getServer();
            final int timeoutMillis = connectionInfo.getTimeoutMillis();
            if (client.requestTime(serverName, timeoutMillis, network)) {
                long ntpCertainty = client.getRoundTripTime() / 2;
                mTimeResult = new TimeResult(
                        client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
                return true;
            } else {
                return false;
            }
        }
    }

②处对距离上次时间更新的时间间隔有要求,mPollingIntervalMs从配置文件获取到时间间隔24h,也就是距离上次更新不到24h不会再次更新时间

此处mNitzTimeSetTime !=NOT_SET有什么作用,后面再表,先往下看。

③处,满足更新条件后mTime.forceRefresh()更新时间,往下看forceRefresh()

看样子是调用client.requestTime(mServer, (int)mTimeout)访问地址为mServer的服务器发出sntp请求,sntp通过什么方式请求呢?继续往下看:

到这里我们可以知道sntp实质是向host name 为mServer的主机发出UDP来更新时间,更新到的时间赋值给了mNtpTime,在onPollNetworkTime()通过④处SystemClock.setCurrentTimeMillis(ntp)设置系统时间,到这里时间更新完成。

就这么结束了吗?当然不是!如果时间同步失败,有重试机制吗?同步更新成功,下次什么时候更新?

第⑤处回答了我们,有重试机制,最大重试次数mTryAgainTimesMax=3,间隔时间mPollingIntervalShorterMs为1分钟;同步成功的话,间隔mPollingIntervalMs再次检查更新,也就是24h(当然了,如果重试3次仍然失败也会24h后尝试更新).

如果设备网络不通,最大重试次数mTryAgainTimesMax用完,就得24h后再更新???

当时不是,android替你想好了,回顾下systemRunning():

systemRunning()注册了网络状态监听,网络变化时发出EVENT_NETWORK_CHANGED->onPollNetworkTime()尝试更新时间.

到这里,我们已经知道Android 可以通过sntp从网络更新时间。Android时间更新似乎也就仅此而已。

等等,在②处有个疑问还没解开,mNitzTimeSetTime !=NOT_SET有什么用处?

回头看:

mNitzTimeSetTime 初始值为NOT_SET,我们自然可以通过sntp从网络更新时间,那么为什么要设置这个条件?

除非还有另一种时间更新机制,如果这种机制同步了时间,自然不必再通过sntp同步时间!

那就看看mNitzTimeSetTime 在哪里被赋值为!=NOT_SET

是谁发出了这个ACTION_NETWORK_SET_TIME广播告诉NetworkTimeUpdateService不必多此一举通过sntp更新时间?再看:

原来是GsmServiceStateTracker接收到EVENT_NITZ_TIME->setTimeFromNITZString()->setAndBroadcastNetworkSetTime()设置了时间并发出广播告知NetworkTimeUpdateService不必多次一举,弟弟已替你更新了时间,哥哥就不必徒劳了!

一探究竟,GsmServiceStateTracke实质只是个Handler,传递信息罢了,谁发送的消息呢?顺着EVENT_NITZ_TIME这一线索继续看。

找到CommandsInterface实现类BaseCommands

GsmServiceStateTracke把自身以及EVENT_NITZ_TIME这一Message设置进了BaseCommands包装成了Registrant对象mNITZTimeRegistrant,

那么好办了,找到mNITZTimeRegistrant哪里被调用就知道谁发出的消息。

原来是RIL processUnsolicited()接收到RIL_UNSOL_NITZ_TIME_RECEIVED事件发出,继续往下看:

现在很清晰了,RIL里启动了线程RILReceiver,RILReceiver通过socket 与rild通信,读出了时间值->processResponse()向上分发,这种时间同步机制称为NITZ(通过基站同步时间)。

RILD何物?

RILD:Radio Interface Layer  Deamon,承接Android 上层 (RILJ)与 Modem的通信。草图如下:

至此,知道了Android时间同步更新机制有SNTP和NITZ两种方式。

二.常见导致时间不准的情况及解决途径:

常遇到的问题是NITZ时间不准,导致系统时间不准。

从Android时间同步机制分析知道,如果NITZ同步了时间,SNTP在24h内不会被触发。如果NITZ同步了错误的时间,短期内设备仍旧是错误的时间直到SNTP被触发。怎么确认呢?

分析GsmServiceStateTracker setTimeFromNITZString()可知,更新NITZ时间后,会通过SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()))写入临时变量并输出日志"setAndBroadcastNetworkSetTime: time",与设备开机时的时间比较便知NITZ时间是否有误。

解决:NITZ时间不准,通常是由基站导致,可以通过如下任意一种方式解决:

1.通过配置关闭NITZ机制只保留SNTP解决:

分析GsmServiceStateTracker setTimeFromNITZString()可知设置gsm.ignore-nitz为true,就不会设置NITZ时间为系统时间,这样就从SNTP同步时间。

这么做需要个前提,就是系统开放了管理员权限,这样才可以把gsm.ignore-nitz=true配置固化(build.prop)。

2.写个系统应用:

关闭自动同步时间开关,这样就关闭了系统时间同步机制,同时借鉴SNTP机制,写一套sntp,在应用上更新时间。


---------------------
作者:GuangZhi-
来源:CSDN
原文:https://blog.csdn.net/u011360996/article/details/127629774
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

猜你喜欢

转载自blog.csdn.net/xiaowang_lj/article/details/130951006