編輯:關於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
asia.pool.ntp.org 86400000 10000 4 5000 20000
// 正常輪詢的頻率 該值為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時間同步,一旦發起成功的時間同步,時間數據會存在內存中,並根據當前機器運行時間來設定最新的時間。
ScheduledThreadPoolExecutor可以添加定時任務的線程池,包括添加周期性定時任務。在前一篇文章Android Java 線程池 ThreadPool
當然現在不用這個工具也可,目前可以直接在布局中非常直觀地觀察到布局當中的錯誤和警告。不過有eclipse難免會報錯的時候,多知道一點沒什麼不好 以前這個工具不叫lint而
Android 自定義雙向滑動SeekBar ,一些需要價格區間選擇的App可能需要用到1. 自定義MySeekBar 繼承 View,先給一張效果圖。2.原理:自定義a
昨天有人問我Android怎麼連接mysql數據庫,和對數據庫的操作呀,我想把,給他說說json通信,可是他並不知道怎麼弄,哎算了吧,直接叫他用ksoap吧,給他說了大半