Android 时间更新机制研究

最近因工作的需要,开始接触到Android系统时间网络同步更新的问题。遇到的实际问题如下:1、手机恢复出厂设置后,系统时间没有及时更新。2、手机使用当中时间同步更新后,时间快了几分钟。3、手机状态栏的时间的分钟显示没有及时更新等。
鉴于各个项目问题的重复出现,有很多地方不是太明白,导致解决问题的效率比较低,正想研究一下,所以根据网上相关的资源介绍,对照Android 6.0的源码进行分析,写个文档记录一下。

一、概述
现在android通过网络同步时间有两种方式:一种是走运营商协议的NITZ,另一种是走网络时钟的NTP;
NITZ和NTP,它们使用的条件不同,可以获取的信息也不一样;勾选这个功能后,手机首先会尝试NITZ方式,若获取时间失败,则使用NTP方式。
1.NITZ(network identity and time zone)同步时间
NITZ是一种GSM/WCDMA基地台方式,必须插入SIM卡,且需要operator支持;可以提供时间和时区信息

2.NTP(network time protocol)同步时间
NTP在无SIM卡或operator不支持NITZ时使用,单纯通过网络(GPRS/WIFI)获取时间,只提供时间信息,没有时区信息(因此在不支持NITZ的地区,自动获取时区功能实际上是无效的)NTP还有一种缓存机制:当前成功获取的时间会保存下来,当用户下次开启自动更新时间功能时会结合手机clock来进行时间更新。这也是没有任何网络时手机却能自动更新时间的原因。此外,因为NTP是通过对时的server获取时间,当同步时间失败时,可以检查一下对时的server是否有效,并替换为其他server试一下。

3.如何判断手机通过哪种方式更新时间
设置一个错误的时区,查看时区是否有被更新正确,若时间和时区都有更新正确,那么就是GSM网路有送NITZ消息上来;
若只有时间更新,而时区没有变化,就是NTP方式,即它通过网络(GPRS/WIFI)连接到server去获取时间。

二、源码目录位置列表

frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
frameworks/base/core/java/android/app/AlarmManager.java
frameworks/base/services/core/java/com/android/server/AlarmManagerService.java
frameworks/base/core/java/android/net/SntpClient.java
frameworks/base/core/java/android/util/NtpTrustedTime.java

三、Android自动更新时间源码分析
1、首先选中自动更新。设置》高级设置》日期和时间》(自动确定日期和时间、自动确定时区)从Android4.0开始时间和时区是否自动更新可以分开设置。我们以时间自动更新为例:

源码路径:/packages_mtk_6750_mp/apps/Settings/src/com/android/settings/DateTimeSettings.java

    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
        if (key.equals(KEY_AUTO_TIME)) {
            // /M: modify as MTK add GPS time Sync feature @{
            String value = mAutoTimePref.getValue();
            int index = mAutoTimePref.findIndexOfValue(value);
            mAutoTimePref.setSummary(value);
            boolean autoEnabled = true;
            if (index == AUTO_TIME_NETWORK_INDEX) {
                Settings.Global.putInt(getContentResolver(),
                        Settings.Global.AUTO_TIME, 1);
                Settings.Global.putInt(getContentResolver(),
                        Settings.System.AUTO_TIME_GPS, 0);
            } else if (index == AUTO_TIME_GPS_INDEX) {
                showDialog(DIALOG_GPS_CONFIRM);
                setOnCancelListener(this);
            } else {
                Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME, 0);
                Settings.Global.putInt(getContentResolver(), Settings.System.AUTO_TIME_GPS, 0);
                autoEnabled = false;
            }
            // /@}
            mTimePref.setEnabled(!autoEnabled);
            mDatePref.setEnabled(!autoEnabled);
        } else if (key.equals(KEY_AUTO_TIME_ZONE)) {
            boolean autoZoneEnabled = preferences.getBoolean(key, true);
            Settings.Global.putInt(
                    getContentResolver(), Settings.Global.AUTO_TIME_ZONE, autoZoneEnabled ? 1 : 0);
            mTimeZone.setEnabled(!autoZoneEnabled);
        }
    }
//注释:这是一个监听,当点击自动checkbox后,会触发此监听事件,其中设置了Settings.Global.AUTO_TIME
//和Settings.Global.AUTO_TIME_ZONE的值,disable/enable了时间、日期、区域三个选择项。

2、后台监听处理:
见GsmServiceStateTracker.java

    public GsmServiceStateTracker(GSMPhone phone) {
        super(phone, phone.mCi, new CellInfoGsm());
        ......
        mCr.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                mAutoTimeObserver);
        mCr.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
                mAutoTimeZoneObserver);
        ......
        }
    private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            Rlog.i("GsmServiceStateTracker", "Auto time state changed");
            revertToNitzTime();
        }
    };

    private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            Rlog.i("GsmServiceStateTracker", "Auto time zone state changed");
            revertToNitzTimeZone();
        }
    };

注释:GSM方式和CDMA方式原理是一样的,只是不同的运营商的支持不同而已,接下来的分析我们以CDMA为例。
见CdmaServiceStateTracker.java

//设置Settings.Global.AUTO_TIME监听
protected CdmaServiceStateTracker(CDMAPhone phone, CellInfo cellInfo) {
        super(phone, phone.mCi, cellInfo);
        ......
        mCr.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                mAutoTimeObserver);
        mCr.registerContentObserver(
            Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
            mAutoTimeZoneObserver);
        ......
}
//Settings.Global.AUTO_TIME的监听处理函数
private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            if (DBG) log("Auto time state changed");
            revertToNitzTime();
        }
    };

    private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            if (DBG) log("Auto time zone state changed");
            revertToNitzTimeZone();
        }
    };

    private void revertToNitzTime() {
        if (Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME, 0) == 0) {
            return;
        }
        if (DBG) {
            log("revertToNitzTime: mSavedTime=" + mSavedTime + " mSavedAtTime=" + mSavedAtTime);
        }
        //Settings.Global.AUTO_TIME真正的处理函数,其取得从gsm/cdma取得的时间参数,
        //对系统时间已经区域进行更新,并发送广播消息。
        if (mSavedTime != 0 && mSavedAtTime != 0) {
            setAndBroadcastNetworkSetTime(mSavedTime
                    + (SystemClock.elapsedRealtime() - mSavedAtTime));
        }
    }
//这个函数做的事情比较简单,就是判断了下有没有选中自动更新,没有,就返回。有,那再继续判断,
//mSavedTime和mSavedAtTime为不为0(这两个变量后面再讲),都不为0,那么发送广播。

    private void setAndBroadcastNetworkSetTime(long time) {
        if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
        /*此处进行了一次系统时间的设置,这里简单介绍一下流程:
        通过SystemClock类的setCurrentTimeMillis方法设置时间,
        在SystemClock类中拿到AlarmManagerService的代理端AlarmManager的引用,
        通过Binder机制将值传至AlarmManagerService,再通过JNI传至底层。*/
        SystemClock.setCurrentTimeMillis(time);
        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
        intent.putExtra("time", time);
        mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    }
//发送广播如上,广播接收的地方是在NetworkTimeUpdateService.java

注释:以上是从设置里面点击触发一次时间同步,那么系统又是如何自动进行时间同步的呢,当选择自动设置以后
系统时间同步,是靠ril层(见Reference-ril.c)发消息RIL_UNSOL_NITZ_TIME_RECEIVED来进行驱动的,这里就不详细介绍此消息是如何发生的了,其主要由gsm/cdma模块来进行驱动的,想更深入的了了解,需要对RIL、MUX、AT、运营商协议 以及代码进行综合的分析,在这里就不做详细介绍。

当Ril.java接收到RIL_UNSOL_NITZ_TIME_RECEIVED消息,会将消息转发为EVENT_NITZ_TIME,
同时在CdmaServiceStateTracker中handleMessage会接收到EVENT_NITZ_TIME消息了并进行相关处理,详细代码见下:

    public void handleMessage (Message msg) {
        AsyncResult ar;
        int[] ints;
        String[] strings;

        if (!mPhone.mIsTheCurrentActivePhone) {
            loge("Received message " + msg + "[" + msg.what + "]" +
                    " while being destroyed. Ignoring.");
            return;
        }

        switch (msg.what) {
        ......
        case EVENT_NITZ_TIME:
          ar = (AsyncResult) msg.obj;

            String nitzString = (String)((Object[])ar.result)[0];
            long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
            //EVENT_NITZ_TIME消息处理,获取gsm/cdma发送过来的时间参数,
            //并且调用setTimeFromNITZString进行处理
            setTimeFromNITZString(nitzString, nitzReceiveTime);
            break;
        ......
        }
    }

    private
    void setTimeFromNITZString (String nitz, long nitzReceiveTime)
    {
        // "yy/mm/dd,hh:mm:ss(+/-)tz"
        // tz is in number of quarter-hours

        long start = SystemClock.elapsedRealtime();
        ......
        if (getAutoTime()) {
            /**
             * Update system time automatically
             */
            long gained = c.getTimeInMillis() - System.currentTimeMillis();
            long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
            int nitzUpdateSpacing = Settings.Global.getInt(mCr,
                    Settings.Global.NITZ_UPDATE_SPACING, mNitzUpdateSpacing);
            int nitzUpdateDiff = Settings.Global.getInt(mCr,
                    Settings.Global.NITZ_UPDATE_DIFF, mNitzUpdateDiff);

            if ((mSavedAtTime == 0) || (timeSinceLastUpdate > nitzUpdateSpacing)
                    || (Math.abs(gained) > nitzUpdateDiff)) {
                if (DBG) {
                    log("NITZ: Auto updating time of day to " + c.getTime()
                        + " NITZ receive delay=" + millisSinceNitzReceived
                        + "ms gained=" + gained + "ms from " + nitz);
                }
                setAndBroadcastNetworkSetTime(c.getTimeInMillis());
            } else {
                if (DBG) {
                    log("NITZ: ignore, a previous update was "
                        + timeSinceLastUpdate + "ms ago and gained=" + gained + "ms");
                }
                return;
            }
        }
        ......
    }

    private boolean getAutoTime() {
        try {
            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME) > 0;
        } catch (SettingNotFoundException snfe) {
            return true;
        }
    }

setTimeFromNITZString时间更新处理函数,当自动选择框为true,当获取的时间值和系统的时间值大于一定时,
调用setAndBroadcastNetworkSetTime(c.getTimeInMillis())对系统时间进行更新,
并且发送广播消息TelephonyIntents.ACTION_NETWORK_SET_TIME。此广播消息被NetWorkTimeupdataService接收到,
并且进行了相关的时间同步处理。接下来看NetworkTimeUpdateService.java

    private void registerForTelephonyIntents() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
        mContext.registerReceiver(mNitzReceiver, intentFilter);
    }
    //前面讲到广播是从CdmaServiceStateTracker.java中发出,找到它的receiver,函数只是赋值了两个变量。
    /** Receiver for Nitz time events */
    private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
                if (DBG) Log.d(TAG, "mNitzReceiver Receive ACTION_NETWORK_SET_TIME");
                mNitzTimeSetTime = SystemClock.elapsedRealtime();
            } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
                if (DBG) Log.d(TAG, "mNitzReceiver Receive ACTION_NETWORK_SET_TIMEZONE");
                mNitzZoneSetTime = SystemClock.elapsedRealtime();
            }
        }
    };

真正更新时间的地方在哪儿?
还是在NetworkTimeupdateService.java中,它也注册了ContentObserver。

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

    /** Initialize the receivers and initiate the first NTP request */
    //开机后,会调用该类的systemRunning方法
    public void systemRunning() {
        //registerForTelephonyIntents该方法,注册监听来自Telephony Ril相关的广播。
        registerForTelephonyIntents();
        //registerForAlarms此方法,是配合构造函数中的mPendingPollIntent 来工作的,
        //主要作用是构造handler Message并再次发起时间同步请求。
        registerForAlarms();
        //此方法监听移动数据连接,移动网络连接后,收到信息,发起时间同步请求。
        registerForConnectivityIntents();
        //构建Message,发起时间同步请求。
        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();
        //构建监听数据库的Observer,监听来自设置等发起的时间同步请求。
        //在SettingsObserver中构建handler Message请求,发起时间同步。
        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
        mSettingsObserver.observe(mContext);
        ......
    }
    //上面我们讲到了接收的来自Telephony相关的广播,或者数据库变化,
    //我们都会发送Message给Handler,我们的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) {
                //接收请求类型:EVENT_AUTO_TIME_CHANGED、EVENT_POLL_NETWORK_TIME、
                //EVENT_NETWORK_CONNECTED,这些请求逻辑,我们都会发起onPollNetworkTime
                //来进行相关逻辑处理。也就是说,onPollNetworkTime方法就是我们时间同步的主要关注对象。
                case EVENT_AUTO_TIME_CHANGED:
                case EVENT_POLL_NETWORK_TIME:
                case EVENT_NETWORK_CHANGED:
                    if (DBG) Log.d(TAG, "MyHandler::handleMessage what = " + msg.what);
                    onPollNetworkTime(msg.what);
                    break;

                /// M: comment @{ add GPS Time Sync Service
                case EVENT_GPS_TIME_SYNC_CHANGED:
                    boolean gpsTimeSyncStatus = getGpsTimeSyncState();;
                    Log.d(TAG, "GPS Time sync is changed to " + gpsTimeSyncStatus);
                    onGpsTimeChanged(gpsTimeSyncStatus);
                    break;
                /// @}
            }
        }
    }

SettingsObserver就是一个ContentObserver,以上是具体的代码,很简单。
好的,继续分析更改时间的地方,找到handleMessage里的回调函数,onPollNetworkTime(int event)。

在分析该函数之前我先简单介绍几个变量:
配置文件路径:frameworks/base/core/res/res/values/config.xml

    <string translatable="false" name="config_ntpServer">asia.pool.ntp.org</string>    
    <!-- Normal polling frequency in milliseconds -->
    <integer name="config_ntpPollingInterval">86400000</integer>
    <!-- Try-again polling interval in milliseconds, in case the network request failed -->
    <integer name="config_ntpPollingIntervalShorter">10000</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">4</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>
    // 正常轮询的频率 该值为86400000ms即24小时。多次尝试同步时间无果,24小时会再次发起时间同步请求
    private final long mPollingIntervalMs;
    // 网络请求失败时,再次请求轮询的间隔 10000ms即10秒。时间同步超时,再次发起时间同步请求。
    private final long mPollingIntervalShorterMs;
    // 再次轮询的最大次数 4
    private final int mTryAgainTimesMax;
    // 如果时间差大于门槛值5000ms即5秒,则更新时间
    private final int mTimeErrorThresholdMs;
    // 该值记录轮询的次数
    private int mTryAgainCounter;
    //时间同步服务器。此处可以多增加几个时间同步服务器,大陆、美国、台湾等多梯度配置。
    private static final String[] SERVERLIST =  new String[]{
                                             "1.cn.pool.ntp.org",
                                             "2.cn.pool.ntp.org",
                                             "3.cn.pool.ntp.org",
                                             "0.cn.pool.ntp.org"
                                             };
    ......
    public NetworkTimeUpdateService(Context context) {
        mContext = context;
        //初始化NtpTrustedTime对象。
        mTime = NtpTrustedTime.getInstance(context);
        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        Intent pollIntent = new Intent(ACTION_POLL, null);
        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);

        mPollingIntervalMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpPollingInterval);
        mPollingIntervalShorterMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
        mTryAgainTimesMax = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpRetry);
        mTimeErrorThresholdMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpThreshold);
        //设置默认NTP服务器
        mDefaultServer = ((NtpTrustedTime) mTime).getServer();
        mNtpServers.add(mDefaultServer);
        for (String str : SERVERLIST)
        {
           mNtpServers.add(str);
        }
        mTryAgainCounter = 0;
    }

好了,了解了这些变量后,我们再回到回调函数onPollNetworkTime()。

private void onPollNetworkTime(int event) {
   if (DBG) Log.d(TAG, "onPollNetworkTime start");
   //1、是否勾选自动同步时间配置
   // If Automatic time is not set, don't bother.
   if (!isAutomaticTimeRequested()) return;
   if (DBG) Log.d(TAG, "isAutomaticTimeRequested() = True");

   final long refTime = SystemClock.elapsedRealtime();
   // If NITZ time was received less than mPollingIntervalMs time ago,
   // no need to sync to NTP.
   if (DBG) Log.d(TAG, "mNitzTimeSetTime: " + mNitzTimeSetTime + ",refTime: " + refTime);
   //2、mNitzTimeSetTime 来自Moderm,如果当前时间刚通过moderm更新不久,则不进行时间同步。
   if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
       resetAlarm(mPollingIntervalMs);
       return;
   }
   //3、如果机器刚启动,或者机器运行时间大于mPollingIntervalMs,即24小时,
   //或者设置等发起的主动更新时间请求,则发起网络时间同步请求。否则,24小时后再进行时间同步。
   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");
        //3.1、是否含有时间缓冲,如无,发起时间同步,
       // 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));
           //3.1.1、遍历时间服务器,发起时间同步
           if (mTime instanceof NtpTrustedTime)
           {
               ((NtpTrustedTime) mTime).setServer(mNtpServers.get(index));
               mTime.forceRefresh();
               ((NtpTrustedTime) mTime).setServer(mDefaultServer);
           }
           else
           {
               mTime.forceRefresh();
           }
       }
        //3.2、获取最新同步的时间缓冲数据,如无,则再次发起时间同步,
        //间隔时间为mPollingIntervalShorterMs,即30秒。
       // 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.
           //3.2.1、如果开机第一次同步或者最新时间与当前时间差别超过mTimeErrorThresholdMs即5秒,
           //则进行时间设定。否则认定新同步时间与当前时间差别不大,不覆盖当前时间。
           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
               //3.2.2、设定同步时间
               if (ntp / 1000 < Integer.MAX_VALUE) {
                   /*关于设置系统时间流程在CdmaServiceStateTracker类的
                   setAndBroadcastNetworkSetTime方法中有做过简单介绍。*/
                   SystemClock.setCurrentTimeMillis(ntp);
               }
           } else {
               if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
           }
           Amigo_notifyStatus(ntp, currentTime);       
           mLastNtpFetchTime = SystemClock.elapsedRealtime();
       } else {
           Log.d(TAG, "fail : mTryAgainCounter  " + mTryAgainCounter);

           if (isNetworkConnected()) {
               if (DBG) Log.d(TAG, "isNetworkConnected() = true");
               //3.3 如果不大于最大同步次数,10秒后进行时间同步,否则,24小时后更新。
               mTryAgainCounter++;
               if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                   // Try again shortly
                   resetAlarm(mPollingIntervalShorterMs);
               } else {
                   // Try much later
                   if (DBG) Log.d(TAG, "mNitzTimeSetTime: " + mNitzTimeSetTime + ",refTime: " + refTime);
                   if (mNitzTimeSetTime == NOT_SET || refTime - mNitzTimeSetTime >= mPollingIntervalMs) {
                       Amigo_notifyStatusFalil();
                   }
                   mTryAgainCounter = 0;
                   resetAlarm(mPollingIntervalMs);
               }
           } else {
               if (DBG) Log.d(TAG, "isNetworkConnected() = false");
               mTryAgainCounter = 0;
               resetAlarm(mPollingIntervalMs);
           }

           return;
       }
   }
   //4、如果刚更新时间不久,则24小时后再发起时间同步请求。
   resetAlarm(mPollingIntervalMs);
}

以上介绍了时间获取的相关逻辑,我们接下来看下时间是如何发起同步的,这个方法的主角为:NtpTrustedTime
在该类中通过forceRefresh方法来更新获取服务器时间。

    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();
        if (client.requestTime(mServer, (int) mTimeout)) {
            mHasCache = true;
            mCachedNtpTime = client.getNtpTime();
            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
            mCachedNtpCertainty = client.getRoundTripTime() / 2;
            return true;
        } else {
            return false;
        }
    }

在该方法逻辑中,通过SntpClient来封装请求。
我们传入在NetworkTimeUpdateService传入的服务器地址以及请求超时时间(20秒,见配置文件config_ntpTimeout),
向host服务器发起请求,并将相应结果按照编解码规则封装进二进制数组。见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);
            // receiveTime = originateTime + transit + skew
            // responseTime = transmitTime + transit - skew
            // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
            //             = ((originateTime + transit + skew - originateTime) +
            //                (transmitTime - (transmitTime + transit - skew)))/2
            //             = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
            //             = (transit + skew - transit + skew)/2
            //             = (2 * skew)/2 = skew
            ///M: ALPS00657881 bug fixed @{
            long clockOffset = 0;

            if (originateTime <= 0) {
                Log.d(TAG, "originateTime: " + originateTime);
                clockOffset = ((receiveTime - requestTime) + (transmitTime - responseTime)) / 2;
            } else {
                clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
            }
            ///@}
            // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
            // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");

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

总结:NetworkTimeUpdateService时间同步,一旦发起成功的时间同步,时间数据会存在内存中,并根据当前机器运行时间来设定最新的时间。

猜你喜欢

转载自blog.csdn.net/pengtgimust/article/details/52469016