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