NITZ - Network Identity and Time Zone,网络标识和时区,是一种用于自动配置本地时间和日期的机制,同时也通过无线网向移动设备提供运营商信息。NITZ经常被用来自动更新移动电话的系统时钟,Android原有的更新机制就是采用NITZ方式,这是一种运营商的可选服务。其基本原理简单的来说,就是UI根据 Modem主动上报的时间信息,更新终端系统的时间及时区。
一、Framework 对Modem主动上报消息的处理及时间更新
1、RIL_UNSOL_NITZ_TIME_RECEIVED 主动上报及通知
RIL在收到Modem主动上报的RIL_UNSOL_NITZ_TIME_RECEIVED消息后,调用mNITZTimeRegistrant.notifyRegistrant 通知注册者进行时间更新处理。
frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java
case RIL_UNSOL_NITZ_TIME_RECEIVED:
if (RILJ_LOGD) unsljLogRet(response, ret);
// has bonus long containing milliseconds since boot that the NITZ
// time was received
long nitzReceiveTime = p.readLong();
Object[] result = new Object[2];
result[0] = ret;
result[1] = Long.valueOf(nitzReceiveTime);
boolean ignoreNitz = SystemProperties.getBoolean(
TelephonyProperties.PROPERTY_IGNORE_NITZ, false);
if (ignoreNitz) {
if (RILJ_LOGD) riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
} else {
if (mNITZTimeRegistrant != null) {
mNITZTimeRegistrant
.notifyRegistrant(new AsyncResult (null, result, null)); // 通知注册接收方
}
// in case NITZ time registrant isn't registered yet, or a new registrant
// registers later
mLastNITZTimeInfo = result;
}
break;
mNITZTimeRegistrant的注册监听方法:
@Override
public void setOnNITZTime(Handler h, int what, Object obj) {
super.setOnNITZTime(h, what, obj);
// Send the last NITZ time if we have it
if (mLastNITZTimeInfo != null) {
mNITZTimeRegistrant
.notifyRegistrant(
new AsyncResult (null, mLastNITZTimeInfo, null));
}
}
public GsmServiceStateTracker(GSMPhone phone) {
super(phone, phone.mCi, new CellInfoGsm());
mPhone = phone;
……
mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
mCi.registerForVoiceNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED, null);
// 注册 NITZ 消息监听
mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
…….
}
case EVENT_NITZ_TIME:
ar = (AsyncResult) msg.obj;
String nitzString = (String)((Object[])ar.result)[0];
long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
setTimeFromNITZString(nitzString, nitzReceiveTime);
break;
setTimeFromNITZString 负责解析传过来字符串(nitzString)并进行时间和时区的设置
/**
* nitzReceiveTime is time_t that the NITZ time was posted
*/
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 (DBG) {log("NITZ: " + nitz + "," + nitzReceiveTime +
" start=" + start + " delay=" + (start - nitzReceiveTime));
}
// 解析从 modem 获取的时间字符串 nitzString
try {
/* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
* offset as well (which we won't worry about until later) */
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
c.clear();
c.set(Calendar.DST_OFFSET, 0);
String[] nitzSubs = nitz.split("[/:,+-]");
int year = 2000 + Integer.parseInt(nitzSubs[0]);
if (year > MAX_NITZ_YEAR) {
if (DBG) loge("NITZ year: " + year + " exceeds limit, skip NITZ time update");
return;
}
c.set(Calendar.YEAR, year);
// month is 0 based!
int month = Integer.parseInt(nitzSubs[1]) - 1;
c.set(Calendar.MONTH, month);
int date = Integer.parseInt(nitzSubs[2]);
c.set(Calendar.DATE, date);
int hour = Integer.parseInt(nitzSubs[3]);
c.set(Calendar.HOUR, hour);
int minute = Integer.parseInt(nitzSubs[4]);
c.set(Calendar.MINUTE, minute);
int second = Integer.parseInt(nitzSubs[5]);
c.set(Calendar.SECOND, second);
boolean sign = (nitz.indexOf('-') == -1);
int tzOffset = Integer.parseInt(nitzSubs[6]);
int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7])
: 0;
// The zone offset received from NITZ is for current local time,
// so DST correction is already applied. Don't add it again.
//
// tzOffset += dst * 4;
//
// We could unapply it if we wanted the raw offset.
tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
TimeZone zone = null;
// As a special extension, the Android emulator appends the name of
// the host computer's timezone to the nitz string. this is zoneinfo
// timezone name of the form Area!Location or Area!Location!SubLocation
// so we need to convert the ! into /
if (nitzSubs.length >= 9) {
String tzname = nitzSubs[8].replace('!','/');
zone = TimeZone.getTimeZone( tzname );
}
String iso = ((TelephonyManager) mPhone.getContext().
getSystemService(Context.TELEPHONY_SERVICE)).
getNetworkCountryIsoForPhone(mPhone.getPhoneId());
if (zone == null) {
if (mGotCountryCode) {
if (iso != null && iso.length() > 0) {
zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
c.getTimeInMillis(),
iso);
} else {
// We don't have a valid iso country code. This is
// most likely because we're on a test network that's
// using a bogus MCC (eg, "001"), so get a TimeZone
// based only on the NITZ parameters.
zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
}
}
}
if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
// We got the time before the country or the zone has changed
// so we don't know how to identify the DST rules yet. Save
// the information and hope to fix it up later.
mNeedFixZoneAfterNitz = true; // 重要标记,用于SS变化时是否进行时区更新判断
mZoneOffset = tzOffset;
mZoneDst = dst != 0;
mZoneTime = c.getTimeInMillis();
}
if (zone != null) {
if (getAutoTimeZone()) {
// 设置时区并发送广播
setAndBroadcastNetworkSetTimeZone(zone.getID());
}
// 保存当前设置时区值
saveNitzTimeZone(zone.getID());
}
String ignore = SystemProperties.get("gsm.ignore-nitz");
if (ignore != null && ignore.equals("yes")) {
log("NITZ: Not setting clock because gsm.ignore-nitz is set");
return;
}
try {
mWakeLock.acquire();
if (getAutoTime()) {
long millisSinceNitzReceived
= SystemClock.elapsedRealtime() - nitzReceiveTime;
if (millisSinceNitzReceived < 0) {
// Sanity check: something is wrong
if (DBG) {
log("NITZ: not setting time, clock has rolled "
+ "backwards since NITZ time was received, "
+ nitz);
}
return;
}
if (millisSinceNitzReceived > Integer.MAX_VALUE) {
// If the time is this far off, something is wrong > 24 days!
if (DBG) {
log("NITZ: not setting time, processing has taken "
+ (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
+ " days");
}
return;
}
// Note: with range checks above, cast to int is safe
c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
if (DBG) {
log("NITZ: Setting time of day to " + c.getTime()
+ " NITZ receive delay(ms): " + millisSinceNitzReceived
+ " gained(ms): "
+ (c.getTimeInMillis() - System.currentTimeMillis())
+ " from " + nitz);
}
// 设置系统时间并发送广播
setAndBroadcastNetworkSetTime(c.getTimeInMillis());
Rlog.i(LOG_TAG, "NITZ: after Setting time of day");
}
// 保存当前设置 NITZ 时间值并存储到系统属性
SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
saveNitzTime(c.getTimeInMillis());
if (VDBG) {
long end = SystemClock.elapsedRealtime();
log("NITZ: end=" + end + " dur=" + (end - start));
}
mNitzUpdatedTime = true;
} finally {
mWakeLock.release();
}
} catch (RuntimeException ex) {
loge("NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
}
}
private boolean getAutoTime() {
try {
return Settings.Global.getInt(mPhone.getContext().getContentResolver(),
Settings.Global.AUTO_TIME) > 0;
} catch (SettingNotFoundException snfe) {
return true;
}
}
private boolean getAutoTimeZone() {
try {
return Settings.Global.getInt(mPhone.getContext().getContentResolver(),
Settings.Global.AUTO_TIME_ZONE) > 0;
} catch (SettingNotFoundException snfe) {
return true;
}
}
3、Modem主动上报消息跟新流程
如上分析, framework 对 Modem 主动上报消息RIL_UNSOL_NITZ_TIME_RECEIVED 的处理流程及时间/时区更新逻辑,可简单总结流程如下。我们可以看到发送广播后,时间及时区的最终维护走到了 NetworkTimeUpdateService 中,具体该服务做了哪些处理,后面我们再对此作进一步解读。
二、UI层面时间更新的处理逻辑
接着,我们再从用户主动选择自动更新的角度,继续分析代码。
1、点击自动更新数据库
Android手机的自动更新时间选项都设置在时间和日期选项卡下,正常用户主动点击勾选自动更新后,会通过修改数据库value 触发时间/时区的自动更新。在2.3中只有一个选项-同步,会同时同步时区和时间日期,4.0中把他们分成了两项,时区和日期时间能分别进行自动更新,其实原理都是一样,都是在数据库中设置对应值,详细如下。
packages/apps/Settings/src/com/android/settings/DateTimeSettings.java
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key.equals(KEY_AUTO_TIME)) {
boolean autoEnabled = preferences.getBoolean(key, true);
Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME,
autoEnabled ? 1 : 0);
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);
}
}
对于一些时间和时区共用一个控件的处理,通常会同时修改两个数据库,如
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
Log.d(TAG,"DateTimeSettings DateTimeSettings key is :"+key+",value is:"+newValue);
if (key.equals(KEY_AUTO_TIME_AND_ZONE)){
boolean autoTimeZoneEnabled = (Boolean) newValue;
Settings.Global.putInt(
getContentResolver(), Settings.Global.AUTO_TIME_ZONE, autoTimeZoneEnabled ? 1 : 0);
mTimeZone.setEnabled(!autoTimeZoneEnabled);
Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME,
autoTimeZoneEnabled ? 1 : 0);
mTimePref.setEnabled(!autoTimeZoneEnabled);
mDatePref.setEnabled(!autoTimeZoneEnabled);
mDateTimePreference.setEnabled(!autoTimeZoneEnabled);
}
.......
}
追踪代码,发现GsmServiceStateTracker构造函数中注册了两个ContentObserver来监听数据库内容的变化
public GsmServiceStateTracker(GSMPhone phone) {
……
mCr = phone.getContext().getContentResolver();
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();
}
};
private void revertToNitzTime() {
if (Settings.Global.getInt(mPhone.getContext().getContentResolver(),
Settings.Global.AUTO_TIME, 0) == 0) {
return;
}
if (DBG) {
log("Reverting to NITZ Time: mSavedTime=" + mSavedTime
+ " mSavedAtTime=" + mSavedAtTime);
}
if (mSavedTime != 0 && mSavedAtTime != 0) {
setAndBroadcastNetworkSetTime(mSavedTime
+ (SystemClock.elapsedRealtime() - mSavedAtTime));
}
}
private void revertToNitzTimeZone() {
if (Settings.Global.getInt(mPhone.getContext().getContentResolver(),
Settings.Global.AUTO_TIME_ZONE, 0) == 0) {
return;
}
if (DBG) log("Reverting to NITZ TimeZone: tz='" + mSavedTimeZone);
if (mSavedTimeZone != null) {
setAndBroadcastNetworkSetTimeZone(mSavedTimeZone);
}
}
private void saveNitzTimeZone(String zoneId) {
mSavedTimeZone = zoneId;
}
private void saveNitzTime(long time) {
mSavedTime = time;
mSavedAtTime = SystemClock.elapsedRealtime();
}
既然找到了这些值的赋值处,是不是又有一个疑问呢?显然这里只有进行过 NITZ 时间设置才会调用 setAndBroadcastNetworkSetTime 、setAndBroadcastNetworkSetTimeZone 设置时间和时区,并发出广播进行最终维护。那么,如果从未进行过 NITZ 时间设置呢?显然这和我们实际遇到的情况是不一样的,那么必然还有其他的监听处理,带着这个问题我们继续看下最终维护时间的 NetworkTimeUpdateService
三、最终的时间维护服务 NetworkTimeUpdateService
从上面两节,发现他们时间设置最终都调到了setAndBroadcastNetworkSetTime 、setAndBroadcastNetworkSetTimeZone 进行时间与时区的更新维护,那么这两个函数到底做了什么呢,下面我们具体看下代码
/**
* Set the timezone and send out a sticky broadcast so the system can
* determine if the timezone was set by the carrier.
*
* @param zoneId timezone set by carrier
*/
private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
AlarmManager alarm =
(AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
// 设置时区
alarm.setTimeZone(zoneId);
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time-zone", zoneId);
// 发送广播
mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
if (DBG) {
log("setAndBroadcastNetworkSetTimeZone: call alarm.setTimeZone and broadcast zoneId=" +
zoneId);
}
}
/**
* Set the time and Send out a sticky broadcast so the system can determine
* if the time was set by the carrier.
*
* @param time time set by network
*/
private void setAndBroadcastNetworkSetTime(long time) {
if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
// 设置系统时间
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);
}
frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java
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)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
} else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
mNitzZoneSetTime = SystemClock.elapsedRealtime();
}
}
};
mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
mSettingsObserver.observe(mContext);
同样的,onPollNetworkTime只有在设置自动更新时间打开的情况下才会调用onPollNetworkTimeUnderWakeLock 进行时间的最终维护,简单看下代码,该函数主要是用在NITZ 没更新时间的情况下,通过 NTP 服务器来完成时间的同步
private void onPollNetworkTimeUnderWakeLock(int event) {
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) {
if (DBG) Log.i(TAG, "onPollNetworkTime nitz used mNitzTimeSetTime = " + mNitzTimeSetTime + "; mNitzTimeSetTime = " +mNitzTimeSetTime + "; refTime = " +refTime + " resetAlarm 1 ...");
resetAlarm(mPollingIntervalMs);
return;
}
final long currentTime = System.currentTimeMillis();
if (DBG) Log.i(TAG, "onPollNetworkTime after nitz logic System time = " + currentTime + ", mLastNtpFetchTime = " +mLastNtpFetchTime +", refTime = " +refTime
+", mLastNtpFetchTime = " +mLastNtpFetchTime + ", mPollingIntervalMs = " +mPollingIntervalMs);
// Get the NTP time
if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED) {
if (DBG) Log.i(TAG, "Before Ntp fetch");
// force refresh NTP cache when outdated
if (mTime.getCacheAge() >= mPollingIntervalMs && isNetworkOk()) {
if (DBG) Log.i(TAG, "onPollNetworkTime force refresh NTP cache when outdated ...");
//mTime.forceRefresh();
int index = mTryAgainCounter % mNtpServers.size();
if (DBG) Log.i(TAG, "mTryAgainCounter = " + mTryAgainCounter + ";mNtpServers.size() = " + mNtpServers.size() + ";index = " + index + ";mNtpServers = " + mNtpServers.get(index));
if (mTime instanceof NtpTrustedTime)
{
if (DBG) Log.i(TAG, "onPollNetworkTime start foceRefresh ...");
((NtpTrustedTime) mTime).setServer(mNtpServers.get(index));
mTime.forceRefresh();
((NtpTrustedTime) mTime).setServer(mDefaultServer);
if (DBG) Log.i(TAG, "onPollNetworkTime after foceRefresh ...");
}
else
{
if (DBG) Log.i(TAG, "onPollNetworkTime other TrustedTime instance ...");
mTime.forceRefresh();
}
}
// only update when NTP time is fresh
if (mTime.getCacheAge() < mPollingIntervalMs) {
if (DBG) Log.i(TAG, "onPollNetworkTime only update when NTP time is fresh ...");
final long ntp = mTime.currentTimeMillis();
mTryAgainCounter = 0;
if (DBG) Log.i(TAG, "onPollNetworkTime ntp = " + ntp + ", currentTime = " +currentTime + ", mLastNtpFetchTime = "+mLastNtpFetchTime);
// 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.
if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
|| mLastNtpFetchTime == NOT_SET) {
// Set the system time
if (DBG && mLastNtpFetchTime == NOT_SET
&& Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
Log.i(TAG, "For initial setup, rtc = " + currentTime);
}
if (DBG) Log.i(TAG, "Ntp time to be set = " + ntp);
// Make sure we don't overflow, since it's going to be converted to an int
if (ntp / 1000 < Integer.MAX_VALUE) {
if (DBG) Log.i(TAG, "onPollNetworkTime ******** SystemClock.setCurrentTimeMillis(ntp) ********");
SystemClock.setCurrentTimeMillis(ntp);
}
} else {
if (DBG) Log.i(TAG, "Ntp time is close enough = " + ntp);
}
mLastNtpFetchTime = SystemClock.elapsedRealtime();
} else {
// Try again shortly
if (DBG) Log.i(TAG, "onPollNetworkTime NTP time is not fresh... mTryAgainCounter = " + mTryAgainCounter + " mTryAgainTimesMax=" + mTryAgainTimesMax);
mTryAgainCounter++;
if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
if (DBG) Log.i(TAG, "onPollNetworkTime resetAlarm short ...");
resetAlarm(mPollingIntervalShorterMs);
} else {
if (DBG) Log.i(TAG, "onPollNetworkTime clear counter resetAlarm max ...");
// Try much later
mTryAgainCounter = 0;
resetAlarm(mPollingIntervalMs);
}
return;
}
}
if (DBG) Log.i(TAG, "onPollNetworkTime final resetAlarm ...");
resetAlarm(mPollingIntervalMs);
}
protected void pollStateDone() {
.......
boolean hasRegistered =
mSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE
&& mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE;
boolean hasChanged = !mNewSS.equals(mSS);
if (hasRegistered) {
mNetworkAttachedRegistrants.notifyRegistrants();
if (DBG) {
log("pollStateDone: registering current mNitzUpdatedTime=" +
mNitzUpdatedTime + " changing to false");
}
mNitzUpdatedTime = false;
}
if (hasChanged) {
String operatorNumeric;
updateSpnDisplay();
tm.setNetworkOperatorNameForPhone(mPhone.getPhoneId(), mSS.getOperatorAlphaLong());
String prevOperatorNumeric = tm.getNetworkOperatorForPhone(mPhone.getPhoneId());
operatorNumeric = mSS.getOperatorNumeric();
tm.setNetworkOperatorNumericForPhone(mPhone.getPhoneId(), operatorNumeric);
updateCarrierMccMncConfiguration(operatorNumeric,
prevOperatorNumeric, mPhone.getContext());
if (operatorNumeric == null) {
if (DBG) log("operatorNumeric is null");
tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), "");
mGotCountryCode = false;
mNitzUpdatedTime = false;
} else {
String iso = "";
String mcc = "";
try{
mcc = operatorNumeric.substring(0, 3);
iso = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
} catch ( NumberFormatException ex){
loge("pollStateDone: countryCodeForMcc error" + ex);
} catch ( StringIndexOutOfBoundsException ex) {
loge("pollStateDone: countryCodeForMcc error" + ex);
}
tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), iso);
mGotCountryCode = true;
TimeZone zone = null;
if (!mNitzUpdatedTime && !mcc.equals("000") && !TextUtils.isEmpty(iso)) {
// Test both paths if ignore nitz is true
boolean testOneUniqueOffsetPath = SystemProperties.getBoolean(
TelephonyProperties.PROPERTY_IGNORE_NITZ, false) &&
((SystemClock.uptimeMillis() & 1) == 0);
ArrayList uniqueZones = TimeUtils.getTimeZonesWithUniqueOffsets(iso);
if ((uniqueZones.size() == 1) || testOneUniqueOffsetPath) {
zone = uniqueZones.get(0);
if (DBG) {
log("pollStateDone: no nitz but one TZ for iso-cc=" + iso +
" with zone.getID=" + zone.getID() +
" testOneUniqueOffsetPath=" + testOneUniqueOffsetPath);
}
if (getAutoTimeZone()) {
setAndBroadcastNetworkSetTimeZone(zone.getID());
}
saveNitzTimeZone(zone.getID());
} else {
if (DBG) {
log("pollStateDone: there are " + uniqueZones.size() +
" unique offsets for iso-cc='" + iso +
" testOneUniqueOffsetPath=" + testOneUniqueOffsetPath +
"', do nothing");
}
}
}
if (shouldFixTimeZoneNow(mPhone, operatorNumeric, prevOperatorNumeric,
mNeedFixZoneAfterNitz)) {
// If the offset is (0, false) and the timezone property
// is set, use the timezone property rather than
// GMT.
String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
if (DBG) {
log("pollStateDone: fix time zone zoneName='" + zoneName +
"' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
" iso-cc='" + iso +
"' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
}
if (zone != null) {
log("pollStateDone: zone="+zone);
} else
if ("".equals(iso) && mNeedFixZoneAfterNitz) {
// Country code not found. This is likely a test network.
// Get a TimeZone based only on the NITZ parameters (best guess).
zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
if (DBG) log("pollStateDone: using NITZ TimeZone");
} else
// "(mZoneOffset == 0) && (mZoneDst == false) &&
// (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)"
// means that we received a NITZ string telling
// it is GMTin+0 w/ DST time zone
// BUT iso tells is NOT, e.g, a wrong NITZ reporting
// local time w/ 0 offset.
if ((mZoneOffset == 0) && (mZoneDst == false) &&
(zoneName != null) && (zoneName.length() > 0) &&
(Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
zone = TimeZone.getDefault();
if (mNeedFixZoneAfterNitz) {
// For wrong NITZ reporting local time w/ 0 offset,
// need adjust time to reflect default timezone setting
long ctm = System.currentTimeMillis();
long tzOffset = zone.getOffset(ctm);
if (DBG) {
log("pollStateDone: tzOffset=" + tzOffset + " ltod=" +
TimeUtils.logTimeOfDay(ctm));
}
if (getAutoTime()) {
long adj = ctm - tzOffset;
if (DBG) log("pollStateDone: adj ltod=" +
TimeUtils.logTimeOfDay(adj));
setAndBroadcastNetworkSetTime(adj);
} else {
// Adjust the saved NITZ time to account for tzOffset.
mSavedTime = mSavedTime - tzOffset;
}
}
if (DBG) log("pollStateDone: using default TimeZone");
} else {
zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, iso);
if (DBG) log("pollStateDone: using getTimeZone(off, dst, time, iso)");
}
mNeedFixZoneAfterNitz = false;
if (zone != null) {
log("pollStateDone: zone != null zone.getID=" + zone.getID());
if (getAutoTimeZone()) {
setAndBroadcastNetworkSetTimeZone(zone.getID());
}
saveNitzTimeZone(zone.getID());
} else {
log("pollStateDone: zone == null");
}
}
}
.......
}
下面看下 关键 函数 shouldFixTimeZoneNow
protected boolean shouldFixTimeZoneNow(PhoneBase phoneBase, String operatorNumeric,
String prevOperatorNumeric, boolean needToFixTimeZone) {
// Return false if the mcc isn't valid as we don't know where we are.
// Return true if we have an IccCard and the mcc changed or we
// need to fix it because when the NITZ time came in we didn't
// know the country code.
// If mcc is invalid then we'll return false
int mcc;
try {
mcc = Integer.parseInt(operatorNumeric.substring(0, 3));
} catch (Exception e) {
if (DBG) {
log("shouldFixTimeZoneNow: no mcc, operatorNumeric=" + operatorNumeric +
" retVal=false");
}
return false;
}
// If prevMcc is invalid will make it different from mcc
// so we'll return true if the card exists.
int prevMcc;
try {
prevMcc = Integer.parseInt(prevOperatorNumeric.substring(0, 3));
} catch (Exception e) {
prevMcc = mcc + 1;
}
// Determine if the Icc card exists
boolean iccCardExist = false;
if (mUiccApplcation != null) {
iccCardExist = mUiccApplcation.getState() != AppState.APPSTATE_UNKNOWN;
}
// Determine retVal
boolean retVal = ((iccCardExist && (mcc != prevMcc)) || needToFixTimeZone);
if (DBG) {
long ctm = System.currentTimeMillis();
log("shouldFixTimeZoneNow: retVal=" + retVal +
" iccCardExist=" + iccCardExist +
" operatorNumeric=" + operatorNumeric + " mcc=" + mcc +
" prevOperatorNumeric=" + prevOperatorNumeric + " prevMcc=" + prevMcc +
" needToFixTimeZone=" + needToFixTimeZone +
" ltod=" + TimeUtils.logTimeOfDay(ctm));
}
return retVal;
}
五、案例分析
[系统设置][必现]关闭自动确定时区,更改时区后,重启手机,开启自动确定时区,时区同步错误
【原因分析】:原生的设计逻辑是只有在网络发生变化时才可以自动调整正确的时区,但是本问题的出现时用户在网络稳定后操作时区开关,由于此时网络不发生变化,因此导致时区无法调整正确,具体日志如下
//开始的时候,注册到网络上,但是因为自动对时区是关闭的, 导致系统默认没有更新时区,使用了默认时区
03-09 22:35:10.392 3569 3569 D GsmSST : [GsmSST0] pollStateDone: registering current mNitzUpdatedTime=false changing to false
03-09 22:35:10.418 3569 3569 D GsmSST : [GsmSST0] pollStateDone: fix time zone zoneName='America/Sao_Paulo' mZoneOffset=0 mZoneDst=false iso-cc='cn' iso-cc-idx=-3
03-09 22:35:10.418 3569 3569 D GsmSST : [GsmSST0] pollStateDone: using default TimeZone
03-09 22:35:10.418 3569 3569 D GsmSST : [GsmSST0] pollStateDone: zone != null zone.getID=America/Sao_Paulo
//用户操作自动对时区的开关为开,但是由于网络状态没有发生变化,导致时区还是默认值
03-09 22:35:28.578 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:28.579 3569 3569 D GsmSST : [GsmSST1] Reverting to NITZ TimeZone: tz='null
03-09 22:35:28.579 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:28.579 3569 3569 D GsmSST : [GsmSST0] Reverting to NITZ TimeZone: tz='America/Sao_Paulo
//用户操作自动对时区的开关为关
03-09 22:35:29.790 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:29.791 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
//用户操作自动对时区的开关为开,但是由于网络状态没有发生变化,导致时区还是默认值
03-09 22:35:35.086 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:35.087 3569 3569 D GsmSST : [GsmSST1] Reverting to NITZ TimeZone: tz='null
03-09 22:35:35.087 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:35.087 3569 3569 D GsmSST : [GsmSST0] Reverting to NITZ TimeZone: tz='America/Sao_Paulo
//用户操作自动对时区的开关为关
03-09 22:35:38.159 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:38.161 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
//用户操作自动对时区的开关为开,但是由于网络状态没有发生变化,导致时区还是默认值
03-09 23:35:43.070 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 23:35:43.071 3569 3569 D GsmSST : [GsmSST1] Reverting to NITZ TimeZone: tz='null
03-09 23:35:43.071 3569 3569 I GsmServiceStateTracker: Auto time zone state changed
03-09 22:35:43.080 3569 3569 D GsmSST : [GsmSST0] Reverting to NITZ TimeZone: tz='America/Sao_Paulo
PS:NITZ 与 NTP 小结
现在Android通过网络同步时间有两种方式: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试一下。