美文网首页
Android自动设置时间源码学习

Android自动设置时间源码学习

作者: 留给时光吧 | 来源:发表于2018-05-07 20:49 被阅读0次

    最近遇到了一些手机不能自动更新时间的问题,所以便梳理了一遍时间更新的流程,这里做一下记录,本文基于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自动设置时间源码学习

          本文链接:https://www.haomeiwen.com/subject/nrlmrftx.html