Android自动设置时间源码学习

最近遇到了一些手机不能自动更新时间的问题,所以便梳理了一遍时间更新的流程,这里做一下记录,本文基于Android 8.0源码。

既然是系统更新就要借助外部网络环境,所以这个机制是Android系统中少有的需要和网络通信的服务,简单学习一下也是有必要的。首先Android系统中时间更新有两种方法:NITZ(Network Identity and Time Zone)和NTP(Network Time Protocol)。前者借助于运营商实现,后者借助于一般互联网。

由于我们事先并不清楚具体实现的地方,我们就从设置入口开始,一步一步深入。在设置中开关自动更新时间的代码位置及内容如下:

android\packages\apps\uniscope\Settings\src\com\android\settings\datetime\AutoTimePreferenceController.java
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
        boolean autoEnabled = (Boolean) newValue;
        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AUTO_TIME,
                autoEnabled ? 1 : 0);
        mCallback.updateTimeAndDateDisplay(mContext);
        return true;
}

可见是修改系统中Settings.Global.AUTO_TIME字段来确定是否开启自动更新时间的功能。了解源码的朋友应该清楚,修改一些系统变量也是实现跨进程通信的一种方法。不必直接通信,利用观察者模式,一方修改,另一方监听,就能根据变量变化及时做出响应。所以监听这个变量的地方大致就是自动更新时间逻辑执行的地方,我们可以全文搜索一下代码来一窥端倪。

通过分析我们发现主要在ServiceStateTracker和NetworkTimeUpdateService中监听了这个变量的变化,后者通过名字我们也基本能确定这里就是负责时间更新的。
前者就是时间更新中NITZ机制的主要实现,由于涉及到RIL,这是Android中负责通信功能的Telephony中间层,对这一部分还不是很熟悉,所以就不深入介绍,不过简单介绍一下大致流程:

首先ServiceStateTracker可以认为是和RIL的主要交互者,负责一些状态维护。它持有一个CommandsInterface对象,也就是RIL,首先注册了NITZ事件:

mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);

当RIL上报事件时开始设置时间:

case EVENT_NITZ_TIME:
         ar = (AsyncResult) msg.obj;
         String nitzString = (String)((Object[])ar.result)[0];
         long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
         setTimeFromNITZString(nitzString, nitzReceiveTime);
         break;

setTimeFromNITZString中就开始设置时间和时区,然后发送广播setAndBroadcastNetworkSetTime。在setTimeFromNITZString通过调用getAutoTime方法来判断是否需要自动更新时间。

刚才是RIL主动上报事件时通过判断来决定是否更新时间,而响应设置中开关的逻辑如下:

    private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            Rlog.i(LOG_TAG, "Auto time state changed");
            revertToNitzTime();
        }
    };

    private void revertToNitzTime() {
        if (Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME, 0) == 0) {
            return;
        }
        if (DBG) {
            log("Reverting to NITZ Time: mSavedTime=" + mSavedTime + " mSavedAtTime=" +
                    mSavedAtTime);
        }
        if (mSavedTime != 0 && mSavedAtTime != 0) {
            long currTime = SystemClock.elapsedRealtime();
            mTimeLog.log("Reverting to NITZ time, currTime=" + currTime
                    + " mSavedAtTime=" + mSavedAtTime + " mSavedTime=" + mSavedTime);
            setAndBroadcastNetworkSetTime(mSavedTime + (currTime - mSavedAtTime));
        }
    }

可见监听到变化时,首先判断是否需要更新时间,然后也是调用setAndBroadcastNetworkSetTime来更新时间和发广播。有人可能会问,当开关打开时RIL并没有上报事件,怎么设置时间?其实关键还是在setTimeFromNITZString方法中,不管是否更新时间,都会调用saveNitzTime方法将每次上报的时间存起来,所以才出现了在revertToNitzTime中设置时间时的时间计算公式为:mSavedTime + (currTime - mSavedAtTime)

讲完ServiceStateTracker之后我们再来看NetworkTimeUpdateService,这是一个系统服务,随着系统启动而启动:

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

if (!disableNetwork && !disableNetworkTime) {
        traceBeginAndSlog("StartNetworkTimeUpdateService");
        try {
              networkTimeUpdater = new NetworkTimeUpdateService(context);
              ServiceManager.addService("network_time_update_service", networkTimeUpdater);
        } catch (Throwable e) {
              reportWtf("starting NetworkTimeUpdate service", e);
        }
        traceEnd();
}

接下来我们主要看一下NetworkTimeUpdateService,源码位置:

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

我们依旧从观察者入手:

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

可见观察到值改变后交由Handler处理:

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

发现三种时间都走的一个方法,onPollNetworkTime中首先判断是否需打开了自动更新功能,然后调用了onPollNetworkTimeUnderWakeLock,这个方法是重点:

    private void onPollNetworkTimeUnderWakeLock(int event) {
        final long refTime = SystemClock.elapsedRealtime();

        if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
            resetAlarm(mPollingIntervalMs);
            return;
        }
        final long currentTime = System.currentTimeMillis();
        if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
                || event == EVENT_AUTO_TIME_CHANGED) {

            if (mTime.getCacheAge() >= mPollingIntervalMs) {
                mTime.forceRefresh();
            }

            if (mTime.getCacheAge() < mPollingIntervalMs) {
                final long ntp = mTime.currentTimeMillis();
                mTryAgainCounter = 0;
                if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
                        || mLastNtpFetchTime == NOT_SET) {
                    if (ntp / 1000 < Integer.MAX_VALUE) {
                        SystemClock.setCurrentTimeMillis(ntp);
                    }
                } 
                mLastNtpFetchTime = SystemClock.elapsedRealtime();
            } else {
                mTryAgainCounter++;
                if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                    resetAlarm(mPollingIntervalShorterMs);
                } else {
                    mTryAgainCounter = 0;
                    resetAlarm(mPollingIntervalMs);
                }
                return;
            }
        }
        resetAlarm(mPollingIntervalMs);
    }

第一个判断中,mNitzTimeSetTime在收到ServiceStateTracker发送的广播后被修改,否则就是初始值NOT_SET,也就是代表着是否经过NITZ更新。mPollingIntervalMs的值为86400000ms,就是24小时。这个判断就是如果时间已经更新过而且还不到下一个更新周期时,resetAlarm调用设置一个定时任务。

接下来看第二个判断,mLastNtpFetchTime 表示上次NTP更新时间,默认值也为NOT_SET。后面也是在判断是否在更新周期内,这个判断决定是否启用NTP机制开始更新时间。

接下来mTime是一个NtpTrustedTime类,这里运用的单例的方法:

mTime = NtpTrustedTime.getInstance(context);

这里是为了判断上次更新是否还有效,即24小时,相当于判断缓存有效时间,失效的话开始重新获得时间,否则就简单设置一下当前正确时间。下面我们看forceRefresh方法,不过先从那个单例说起:

android\frameworks\base\core\java\android\util\NtpTrustedTime.java
    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;
    }

相当于执行了初始化操作,设置了默认服务器地址:time.android.com。默认超时时间:5s。然后还有一个安全服务器和备用服务器根据具体情况进行设置。总之是为了构造一个NtpTrustedTime。然后看一下forceRefresh:

    @Override
    public boolean forceRefresh() {
        if (TextUtils.isEmpty(mServer)) {
            // missing server, so no trusted time available
            System.out.println("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);
        if (client.requestTime(mTargetServer, (int) mTimeout)) {
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            countInBackupmode();
            return false;
        }
    }

首先确保服务器不为空,在判断是否有网络链接,然后构造一个真正去访问网络的SntpClient 对象,然后请求时间requestTime:

android\frameworks\base\core\java\android\net\SntpClient.java
    public boolean requestTime(String host, int timeout) {
        InetAddress address = null;
        try {
            address = InetAddress.getByName(host);
        } catch (Exception e) {
            EventLogTags.writeNtpFailure(host, e.toString());
            if (DBG) Log.d(TAG, "request time failed: " + e);
            return false;
        }
        return requestTime(address, NTP_PORT, timeout);
    }

    public boolean requestTime(InetAddress address, int port, int timeout) {
        DatagramSocket socket = null;
        final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_NTP);
        try {
            socket = new DatagramSocket();
            socket.setSoTimeout(timeout);
            byte[] buffer = new byte[NTP_PACKET_SIZE];
            DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);

            buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

            final long requestTime = System.currentTimeMillis();
            final long requestTicks = SystemClock.elapsedRealtime();
            writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

            socket.send(request);

            DatagramPacket response = new DatagramPacket(buffer, buffer.length);
            socket.receive(response);
            final long responseTicks = SystemClock.elapsedRealtime();
            final long responseTime = requestTime + (responseTicks - requestTicks);

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

            checkValidServerReply(leap, mode, stratum, transmitTime);

            long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
            long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
            EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
            if (DBG) {
                Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
                        "clock offset: " + clockOffset + "ms");
            }

            mNtpTime = responseTime + clockOffset;
            mNtpTimeReference = responseTicks;
            mRoundTripTime = roundTripTime;
        } catch (Exception e) {
            EventLogTags.writeNtpFailure(address.toString(), e.toString());
            if (DBG) Log.d(TAG, "request time failed: " + e);
            return false;
        } finally {
            if (socket != null) {
                socket.close();
            }
            TrafficStats.setThreadStatsTag(oldTag);
        }

        return true;
    }

了解socket的朋友看了应该就知道,使用的是UDP协议访问,服务端端口号为123。代码虽然有点长,但是也很好理解,就是标准的UDP通信例子。这里得到响应后,取出时间数据。最后回到NetworkTimeUpdateService中的onPollNetworkTimeUnderWakeLock方法设置时间,在调用完mTime.forceRefresh()后,也刷新了缓存时间,所以if (mTime.getCacheAge() < mPollingIntervalMs)的判断得以执行,又合并到了缓存原本有效的逻辑中,可见源码写的还是很巧妙的,并没有分别去执行设置时间的逻辑。最后设置一个下次更新时间的Alarm。

到这里自动更新时间的逻辑就分析完了,这个机制在Android中算是比较简单的一个,由于需要访问外部网络,所以也比较特殊,感兴趣的可以详细阅读源码。

你可能感兴趣的:(Android自动设置时间源码学习)