Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 時間更新機制之網絡更新時間

Android 時間更新機制之網絡更新時間

編輯:關於Android編程

 

綜述:Android網絡時間更新,大體分兩類。1、moderm相關更新,2、網絡更新。本次主要介紹網路更新時間,主要涉及到NetworkTimeUpdateService,該類運行在SystemServer(ActivityManagerService)進程中。它有點特殊,從名字來看,其實Service,其實它和WifiService、ConnectivityManagerService等系統Service不同。
SystemServer.java

try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
            Slog.e(System, ******************************************);
            Slog.e(System, ************ Failure starting system services, ex);
            throw ex;
        }

startOtherServices方法中,會初始化該類實例:

networkTimeUpdater = new NetworkTimeUpdateService(context);

在ActivityManagerService的systemReady方法中,初始化時間更新環境。

mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
            try {
                    if (networkManagementF != null)       networkManagementF.systemReady();
                } catch (Throwable e) {
                    reportWtf(making Network Managment Service ready, e);
                }
            }
}

涉及代碼路徑如下:
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
frameworks/base/core/java/android/util/NtpTrustedTime.java
frameworks/base/core/java/android/net/SntpClient.java

一、NetworkTimeUpdateService實例

public NetworkTimeUpdateService(Context context) {
        mContext = context;
        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);//時間同步有可能超時,使用該PendingIntent進行(間隔再次發起)時間同步。

        mPollingIntervalMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpPollingInterval);//10天
        mPollingIntervalShorterMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpPollingIntervalShorter);//30秒
        mTryAgainTimesMax = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpRetry);
        mTimeErrorThresholdMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_ntpThreshold);

        //LEUI-START [BUG][MOBILEP-6067] [System time sync added
        mDefaultServer = ((NtpTrustedTime) mTime).getServer();
        mNtpServers.add(mDefaultServer);
        for (String str : SERVERLIST)
        {
           mNtpServers.add(str);
        }
        mTryAgainCounter = 0;
        //LEUI-END [BUG][MOBILEP-6067] [System time sync added
    }

在該構造上,有幾個重要的變量:
1、mPollingIntervalMs:多次嘗試同步時間無果,10天會再次發起時間同步請求
2、mPollingIntervalShorterMs :時間同步超時,再次發起時間同步請求。
3、SERVERLIST:時間同步服務器。此處建議多增加幾個時間同步服務器,大陸、美國、台灣等多梯度配置。
4、初始化NtpTrustedTime對象。

mTime = NtpTrustedTime.getInstance(context);

一、NetworkTimeUpdateService初始化時間同步環境
開機後,會調用該類的systemRunning方法,在該方法中:

public void systemRunning() {
        registerForTelephonyIntents();
        registerForAlarms();
        registerForConnectivityIntents();

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

        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
        mSettingsObserver.observe(mContext);
    }

1、registerForTelephonyIntents該方法,注冊監聽來自Telephony Ril相關的廣播。此部分會在moderm相關同步時間中介紹。

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

2、registerForAlarms此方法,是配合第“一”中介紹的mPendingPollIntent 來工作的,主要作用是構造handler Message並再次發起時間同步請求。
3、registerForConnectivityIntents此方法監聽移動數據連接,移動網絡連接後,收到信息,發起時間同步請求。此部分會在moderm相關同步時間中介紹。

private void registerForConnectivityIntents() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        mContext.registerReceiver(mConnectivityReceiver, intentFilter);
    }

4、構建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();

5、構建監聽數據庫的Observer,監聽來自設置等發起的時間同步請求。在SettingsObserver中構建handler Message請求,發起時間同步。

mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
        mSettingsObserver.observe(mContext);

我們的第二部分,很多地方都會主動或者被動發送Handler Message請求,在我們Handler中,我們是如何處理的那?

三、時間同步請求處理邏輯。
在第二部分,我們講到了接收的來自Telephony相關的廣播,或者數據庫變化,我們都會發送Message給Handler,我們的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_CONNECTED:
                    onPollNetworkTime(msg.what);
                    break;
            }
        }
    }

接收請求類型:EVENT_AUTO_TIME_CHANGED、EVENT_POLL_NETWORK_TIME、
EVENT_NETWORK_CONNECTED,這些請求邏輯,我們都會發起onPollNetworkTime來進行相關邏輯處理。
也就是說,onPollNetworkTime方法就是我們時間同步的主要關注對象。
1、onPollNetworkTime:

private void onPollNetworkTime(int event) {
//1、是否勾選自動同步時間配置
        // If Automatic time is not set, don't bother.
        if (!isAutomaticTimeRequested()) return;
//2、mNitzTimeSetTime 來自Moderm,如果當前時間剛通過moderm更新不久,則不進行時間同步。
        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;
        }
        //3、如果機器剛啟動,或者機器運行時間大於mPollingIntervalMs,即10天,或者設置等發起的主動更新時間請求,則發起網絡時間同步請求。否則,10天後再進行時間同步。
        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) {
                //LEUI-START [BUG][MOBILEP-6067] [System time sync added
                //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();
                }
                //LEUI-END [BUG][MOBILEP-6067] [System time sync added
            }
//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即25秒,則進行時間設定。否則認定新同步時間與當前時間差別不大,不覆蓋當前時間。
                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) {
                        SystemClock.setCurrentTimeMillis(ntp);
                    }
                } else {
                    if (DBG) Log.d(TAG, Ntp time is close enough =  + ntp);
                }
                mLastNtpFetchTime = SystemClock.elapsedRealtime();
            } else {
                // Try again shortly
                //3.3 如果不大於最大同步次數,30秒後進行時間同步,否則,10天後更新。
                mTryAgainCounter++;
                if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                    resetAlarm(mPollingIntervalShorterMs);
                } else {
                    // Try much later
                    mTryAgainCounter = 0;
                    resetAlarm(mPollingIntervalMs);
                }
                return;
            }
        }
        //4、如果剛更新時間不久,則10天後再發起時間同步請求。
        resetAlarm(mPollingIntervalMs);
    }

四、三中介紹了時間獲取的相關邏輯,我們接下來介紹下時間是如何發起同步的,這個方法的主角為:NtpTrustedTime
在該類中通過forceRefresh方法來更新獲取服務器時間。

public boolean forceRefresh() {
        if (mServer == null) {
            // missing server, so no trusted time available
            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來封裝請求。
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
            long 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傳入的服務器地址以及請求超時時間,向host服務器發起請求,並將相應結果按照編解碼規則封裝進二進制數組。

總結:NetworkTimeUpdateService時間同步,一旦發起成功的時間同步,時間數據會存在內存中,並根據當前機器運行時間來設定最新的時間。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved