最近因工作的需要,开始接触到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.orgstring>
<integer name="config_ntpPollingInterval">86400000integer>
<integer name="config_ntpPollingIntervalShorter">10000integer>
<integer name="config_ntpRetry">4integer>
<integer name="config_ntpThreshold">5000integer>
<integer name="config_ntpTimeout">20000integer>
// 正常轮询的频率 该值为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时间同步,一旦发起成功的时间同步,时间数据会存在内存中,并根据当前机器运行时间来设定最新的时间。