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);
-
-
- 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));
- }
-
-
- mLastNITZTimeInfo = result;
- }
- break;
mNITZTimeRegistrant的注册监听方法:
- @Override
- public void setOnNITZTime(Handler h, int what, Object obj) {
- super.setOnNITZTime(h, what, obj);
-
- if (mLastNITZTimeInfo != null) {
- mNITZTimeRegistrant
- .notifyRegistrant(
- new AsyncResult (null, mLastNITZTimeInfo, null));
- }
- }
2、mNITZTimeRegistrant 注册及 EVENT_NITZ_TIME 接收处理ServiceStateTracker在系统启动时,会调用setOnNITZTime将Tracher中的Handler与RIL中的上报消息绑定在一起,即收到上报消息,就回调Handler中的某些方法,以GsmServiceStateTracker 为例,代码分析如下:frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java调用setOnNITZTime 进行mNITZTimeRegistrant 注册:- 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);
-
- mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
- …….
- }
接收到EVENT_NITZ_TIME后,调用 setTimeFromNITZString去设置时间和时区- 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)并进行时间和时区的设置
-
-
-
- private void setTimeFromNITZString (String nitz, long nitzReceiveTime) {
-
-
-
- long start = SystemClock.elapsedRealtime();
- if (DBG) {log("NITZ: " + nitz + "," + nitzReceiveTime +
- " start=" + start + " delay=" + (start - nitzReceiveTime));
- }
-
-
- try {
-
-
- 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);
-
-
- 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;
-
-
-
-
-
-
-
-
- tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
-
- TimeZone zone = null;
-
-
-
-
-
- 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 {
-
-
-
-
- zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
- }
- }
- }
-
- if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
-
-
-
-
- mNeedFixZoneAfterNitz = true;
- 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) {
-
- if (DBG) {
- log("NITZ: not setting time, clock has rolled "
- + "backwards since NITZ time was received, "
- + nitz);
- }
- return;
- }
-
- if (millisSinceNitzReceived > Integer.MAX_VALUE) {
-
- if (DBG) {
- log("NITZ: not setting time, processing has taken "
- + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
- + " days");
- }
- return;
- }
-
-
- 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");
- }
-
- 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);
- }
- }
从代码中可以看出只有在数据库对自动同步网络时间/时区为勾选状态时,才会调用setAndBroadcastNetworkSetTime和 setAndBroadcastNetworkSetTimeZone设置当前NITZ解析的时间及时区,并发送广播进行最终的系统时间/时区维护。这里相关数据库的勾选状态获取方法如下,主要判断 Settings.Global.AUTO_TIME 及 Settings.Global.AUTO_TIME_ZONE 存储值- 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);
- }
- .......
- }
2、数据库变化的监听处理frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java追踪代码,发现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);
- .…..
- }
接着再看看这两个 ContentObserver,我们发现两个关于NITZ的revert函数 ,到底是不是它们更新了时间/时区呢- 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();
- }
接着看代码,终于发现了我们熟悉的调用 setAndBroadcastNetworkSetTime 和 setAndBroadcastNetworkSetTimeZone,至此,我们也就和上节的讨论关联到了一起- 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);
- }
- }
仔细看下代码,我们发现这里有几个关键值 mSavedTime、 mSavedAtTime 和 mSavedTimeZone 影响着上述两个调用的执行,那么他们究竟从哪里来的呢?追踪一下,如下两个函数进行了设置,具体调用在上节(Framework 对Modem主动上报消息的处理及时间更新)中与 setAndBroadcastNetworkSetTime 、setAndBroadcastNetworkSetTimeZone 有同步处理- 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 进行时间与时区的更新维护,那么这两个函数到底做了什么呢,下面我们具体看下代码
-
-
-
-
-
-
- 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);
- }
- }
-
-
-
-
-
-
-
- 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);
- }
找到相应的 Receiver ,这里只对mNitzTimeSetTime、mNitzZoneSetTime两个变量进行了赋值,那么这样做的目的是什么呢?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();
- }
- }
- };
继续查找这两个赋值的使用,我们发现其使用场景为 onPollNetworkTimeUnderWakeLock <- onPollNetworkTime <- handleMessage (EVENT_AUTO_TIME_CHANGED)。看到这里是不是有种似曾相识的感觉呢?对的,正如你所想的,NetworkTimeUpdateService 同样注册了对数据库 Settings.Global.AUTO_TIME 的监听SettingsObserver,在数据库变化时通过EVENT_AUTO_TIME_CHANGED 回调来进行最终时间的维护,这也解释了上节中我们的疑问。- 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 (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);
-
- if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
- || event == EVENT_AUTO_TIME_CHANGED) {
- if (DBG) Log.i(TAG, "Before Ntp fetch");
-
-
- if (mTime.getCacheAge() >= mPollingIntervalMs && isNetworkOk()) {
- if (DBG) Log.i(TAG, "onPollNetworkTime force refresh NTP cache when outdated ...");
-
- 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();
- }
- }
-
-
- 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 (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
- || mLastNtpFetchTime == NOT_SET) {
-
- 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);
-
- 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 {
-
- 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 ...");
-
- mTryAgainCounter = 0;
- resetAlarm(mPollingIntervalMs);
- }
- return;
- }
- }
- if (DBG) Log.i(TAG, "onPollNetworkTime final resetAlarm ...");
- resetAlarm(mPollingIntervalMs);
- }
四、ServiceState 注册状态变化时触发的时间/时区更新frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java对于SS 从非注册状态变成注册状态过程,pollStateDone 会发起时间/时区的新一轮更新处理。大概总结下,在保证当前获取的 operatorNumeric != null情况下,进行时间/时区更新的场景主要分如下几类:1、 根据解析出的 mcc 获取有效国家码(ios)2、 插卡且mcc 变化3、 mNeedFixZoneAfterNitz = true,即解析出时间信息时国家码和时区还没变化(具体参考setTimeFromNITZString)- 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)) {
-
-
- boolean testOneUniqueOffsetPath = SystemProperties.getBoolean(
- TelephonyProperties.PROPERTY_IGNORE_NITZ, false) &&
- ((SystemClock.uptimeMillis() & 1) == 0);
-
- ArrayList<TimeZone> 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)) {
-
-
-
- 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) {
-
-
- zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
- if (DBG) log("pollStateDone: using NITZ TimeZone");
- } else
-
-
-
-
-
-
- if ((mZoneOffset == 0) && (mZoneDst == false) &&
- (zoneName != null) && (zoneName.length() > 0) &&
- (Arrays.binarySearch(GMT_COUNTRY_CODES, iso) < 0)) {
- zone = TimeZone.getDefault();
- if (mNeedFixZoneAfterNitz) {
-
-
- 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 {
-
- 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) {
-
-
-
-
-
-
- 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;
- }
-
-
-
- int prevMcc;
- try {
- prevMcc = Integer.parseInt(prevOperatorNumeric.substring(0, 3));
- } catch (Exception e) {
- prevMcc = mcc + 1;
- }
-
-
- boolean iccCardExist = false;
- if (mUiccApplcation != null) {
- iccCardExist = mUiccApplcation.getState() != AppState.APPSTATE_UNKNOWN;
- }
-
-
- 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
【解决方案】:在网络注册成功后,将通过网络MCC查询出来的时区记忆下来,等待后续时区开关动作时使用
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试一下。