浅谈Android8.1自动同步时间机制

好久没更新博客了,这一年来实在是太忙,上班、洗衣、做饭、看书、奶爸,忙确实是忙。最近工作中刚好研究了下Android8.1系统的自动同步时间机制,也学到了一点东西,leader也叫分享下,趁着这个机会就简单分享下我对Android8.1自动同步时间机制的理解:

                                       浅谈Android8.1自动同步时间机制_第1张图片
一、问题前沿

公司的测试妹子提了个问题:链接上wifi,无法自动更新时间与日期,导致wifi无法正常使用,看了这个问题吓了一跳,wifi不能使用,这个会涉及到Android系统wifi模块的,这个模块可大了,研究起来不是一时半会能解决的,此时我的心情是这样的:

                                                            浅谈Android8.1自动同步时间机制_第2张图片

后来跟测试妹子沟通了,其实跟wifi模块没多大关系,wifi不能使用是由于Android系统时间没有更新导致的,当把系统时间调整到当前时间或者重启设备开机,发现设置里的自动同步日期和时间生效了,所以刚才那个问题缩小到是无法自动更新时间和日期这个原因。我们知道Android设备刷机后,设备需要去同步时间,也即是去请求谷歌服务器同步到最新的当前时间,如果同步失败,那么Android设备显示的时间就会与实际时间不同,感觉一个在中国,一个在美国。Android设备开机后并且设备wifi连上了,这时设备会去请求同步时间,偷偷地截图如下:

浅谈Android8.1自动同步时间机制_第3张图片

接下来我借助代码与日志来分析一波:

二、问题分析与定位

1、先从系统应用Settings入手

自动同步日期和时间功能路径:设置>通用>日期和时间>自动同步日期与时间,定位到相应代码模块如下:

上面的代码在Settings应用里只是修改了系统的一个属性:Settings.Global.AUTO_TIME,其值为"auto_time“,当我们打开或关闭自动同步日期与时间功能时,它将会把auto_time的值相应地修改为1或者0。此刻ContengProvider值发生变化了,其他地方对它有监听的都会发生相应变化,这时发现想解决问题还是得从framework层去定位解决。

2、源码分析

通过对源码的搜索,找到了相应的地方,其路径如下:

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

在NetworkTimeUpdateService中我发现了如下代码:

/** Observer to watch for changes to the AUTO_TIME setting */
    private static class SettingsObserver extends ContentObserver {

        private int mMsg;
        private Handler mHandler;

        SettingsObserver(Handler handler, int msg) {
            super(handler);
            mHandler = handler;
            mMsg = msg;
        }

        void observe(Context context) {
            ContentResolver resolver = context.getContentResolver();
            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
                    false, this);
        }

        @Override
        public void onChange(boolean selfChange) {
            mHandler.obtainMessage(mMsg).sendToTarget();
            Log.d("Andrew", "NetworkTimeUpdateService SettingsObserver onChange...");
        }
    }

上面代码块是对Settings.Global.AUTO_TIME监听,只要其值发生了变化,就会回调onChange方法,而onChange方法里用了一个Handler,找到它一看究竟,其代码定义如下:

/** Handler to do the network accesses on */
    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;
            }
        }
    }

当Settings.Global.AUTO_TIME发生变化时,发送EVENT_AUTO_CHANGED给handler处理,最终执行的函数是onPollNetworkTime方法,我们一起来看下:

private void onPollNetworkTime(int event) {
        // If Automatic time is not set, don't bother. Similarly, if we don't
        // have any default network, don't bother.
        Log.d("Andrew", "onPollNetworkTime >>"+(!isAutomaticTimeRequested() || mDefaultNetwork == null));
        if (!isAutomaticTimeRequested() || mDefaultNetwork == null) return;
        mWakeLock.acquire();
        try {
            onPollNetworkTimeUnderWakeLock(event);
        } finally {
            mWakeLock.release();
        }
    }

方法中第一个if条件语句是用来判断当前自动同步时间是否打开,如果是关闭的,也就是说Settings.Global.AUTO_TIME为0时,方法会返回,后面的代码不再执行。换一种说法就是只有当Settings.Global.AUTO_TIME为1时,后面才会执行。好的,我们接着分析onPollNetworkTimeUnderWakeLock方法,这个方法比较长,其代码如下:

private void onPollNetworkTimeUnderWakeLock(int event) {
        //mNitzTimeSetTime 手机设置运营商时间的时刻
        //refTime 手机当前时刻
        //POLLING_INTERVAL_MS 24L * 60 * 60 * 1000; // 24 hrs
        //所以在24小时内已经设置了运营商时间了,那么不设置网络时间

        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
        Log.d("Andrew", "onPollNetworkTimeUnderWakeLock1 >> "+(mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
                || event == EVENT_AUTO_TIME_CHANGED));
        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
            Log.d("Andrew", "onPollNetworkTimeUnderWakeLock2 >> "+(mTime.getCacheAge() >= mPollingIntervalMs));
            //如果网络时间的缓存超过了一天,那么重新获取网络时间,具体的怎么获取在介绍完这个函数再谈。
            //if (mTime.getCacheAge() >= mPollingIntervalMs) {
                mTime.forceRefresh();
            //}

            // only update when NTP time is fresh
            // 抽出了 if 语句中的核心代码,ntp 为网络时间缓存中的时间,当 ntp 和本地时间的误差大于 TIME_ERROR_THRESHOLD_MS = 5000 时,设置时间。
            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);
    }

在上面我们看到了获取网络时间的关键代码是mTime.forceRefresh(),我们找到mTime的定义mTime = NtpTrustedTime.getInstance(context),顺藤摸瓜,找NtpTrustedTime。我们就直接跳到网络请求的关键代码吧。

其路径为: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();

        String mTargetServer = mServer;
        if (true == getBackupmode()) {
            this.setBackupmode(false);
            mTargetServer = mBackupServer;
        }
        if (LOGD) Log.d(TAG, "Ntp Server to access at:" + mTargetServer);
        final String backServer = "ntp1.aliyun.com";
        if (client.requestTime(mTargetServer, (int) mTimeout)) {
            Log.d("Andrew", "request mTargetServer success...");
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else if(client.requestTime(backServer, (int) mTimeout)){
            Log.d("Andrew", "request backServer success...");
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            Log.d("Andrew", "request failed...");
            countInBackupmode();
            return false;
        }
    }

SNTP,是一种简单的NTP协议,误差要比NTP大一点,Android帮我们封装了SntpClient,所以我们要自己获取网络时间的话,就可以直接拿来用了。这边我比较好奇的是Android从哪个服务器获取的时间。defaultServer = res.getString(com.android.internal.R.string.config_ntpServer),我们再找对应的字符串config.xml。

其代码路径为:frameworks/base/core/res/res/values/config.xml


    time.android.com

这样我们就知道谷歌的时间服务器地址是time.android.com,谷歌也设置了一个备份的服务器:0.pool.ntp.org,我们可以从下面代码看出:

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;

            if (null != sSingleton) {
                final String backupServer = SystemProperties.get("persist.backup.ntpServer");

                sSingleton.mNtpRetriesMax = res.getInteger(
                    com.android.internal.R.integer.config_ntpRetry);

                if ((0 < sSingleton.mNtpRetriesMax) &&
                    (null != backupServer) &&
                    (0 != backupServer.length())) {
                    sSingleton.mBackupServer = (backupServer.trim()).replace("\"", "");
                } else {
                    sSingleton.mNtpRetriesMax = 0;
                    sSingleton.mBackupServer = "";
                }
            }
        }

        return sSingleton;
    }

不过我现在还没找到这个0.pool.ntp.org在哪定义的?谷歌暂时用到服务器暂时只发现这两个,可能还有其他的,当然还有国内的NTP服务器,如:

cn.ntp.org.cn #中国
edu.ntp.org.cn #中国教育网
ntp1.aliyun.com #阿里云
ntp2.aliyun.com #阿里云
cn.pool.ntp.org #最常用的国内NTP服务器

这次的问题解决我在forceRefresh方法中增加了阿里云NTP服务器的请求,并去掉了网络缓存时间的判断直接去请求服务器的时间。

三、小结

1、本次分享只做了NTP服务器请求流程的简单分析。

2、遗留问题,leader所说的每次请求失败重新尝试连接的问题。

3、更深层次分析

 

你可能感兴趣的:(Android系统)