編輯:關於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時間同步,一旦發起成功的時間同步,時間數據會存在內存中,並根據當前機器運行時間來設定最新的時間。
計時器來計算每個運動員所消耗的時間等,而在Android系統之中,這種計時的功能就可以使用Chronometer組件,此類的繼承結構如下所示: 2java.lang.Ob
一、onWindowFocusChanged有時我們需要測量一個Activity多長時間才能顯示出來,那麼在代碼中打點計時的時機選在哪兒呢?在onCreate和onRes
本文為那些不錯的Android開源項目第三篇——優秀項目篇,主要介紹那些還不錯的完整Android項目。記錄的項目主要依據是項目有意思或項目分層規
<uses-permission android:name="android.permission.CALL_PHONE"/><us