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 receivedlong 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 latermLastNITZTimeInfo = 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 itif (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);// 注册 NITZ 消息监听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)并进行时间和时区的设置

    /*** 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-hourslong start = SystemClock.elapsedRealtime();if (DBG) {log("NITZ: " + nitz + "," + nitzReceiveTime +" start=" + start + " delay=" + (start - nitzReceiveTime));}// 解析从 modem 获取的时间字符串 nitzStringtry {/* 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 wrongif (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 safec.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);}}

从代码中可以看出只有在数据库对自动同步网络时间/时区为勾选状态时,才会调用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

    @Overridepublic 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);}}

对于一些时间和时区共用一个控件的处理,通常会同时修改两个数据库,如

    @Overridepublic 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()) {@Overridepublic void onChange(boolean selfChange) {Rlog.i("GsmServiceStateTracker", "Auto time state changed");revertToNitzTime();}};private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {@Overridepublic 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 进行时间与时区的更新维护,那么这两个函数到底做了什么呢,下面我们具体看下代码

    /*** 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);}

找到相应的 Receiver ,这里只对mNitzTimeSetTime、mNitzZoneSetTime两个变量进行了赋值,那么这样做的目的是什么呢?

frameworks/base/services/core/java/com/android/server/NetworkTimeUpdateService.java

    private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {@Overridepublic 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 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 timeif (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs|| event == EVENT_AUTO_TIME_CHANGED) {if (DBG) Log.i(TAG, "Before Ntp fetch");// force refresh NTP cache when outdatedif (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 freshif (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 timeif (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 intif (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 shortlyif (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 latermTryAgainCounter = 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)) {// Test both paths if ignore nitz is trueboolean 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)) {// 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);} elseif ("".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 settinglong 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 falseint 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 existsboolean iccCardExist = false;if (mUiccApplcation != null) {iccCardExist = mUiccApplcation.getState() != AppState.APPSTATE_UNKNOWN;}// Determine retValboolean 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试一下。

Android 本地时间/时区自动更新 -- NITZ相关推荐

  1. Android9.0 本地时区和本地时间的自动更新机制

    Android9.0 本地时区和本地时间的自动更新机制 简介 现在Android通过网络同步时间有两种方式:NITZ和NTP,它们使用的条件不同,可以获取的信息也不一样:勾选自动同步功能后,手机首先会 ...

  2. 注册表修改时间时区自动更新状态

    打开时间自动更新 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters](time) "Type&q ...

  3. 计算机无法自动更新,电脑时间不能自动更新怎么办?

    我们经常会遇到电脑时间不能自动更新,电脑时间不准确的现象,这个问题,可能对于很多电脑新手是个不小的麻烦,下面我就对此类问题分析,总结出几种原因,并给出电脑时间不能自动更新问题的解决办法,帮助大家解除此 ...

  4. android应用程序的自动更新升级(自身升级,通过tomcat),[SaltStack] Minion-conf自动更新...

    minion-conf配置文件自动更新, 加载 minion-conf是每个minion自身以来的配置, 为了方便我们在中心管控机上(Master)统一配置, 然后下发文件, 进而使得Minion能够 ...

  5. android 系统(143)---Android实现App版本自动更新

    Android实现App版本自动更新 现在很多的App中都会有一个检查版本的功能.例如斗鱼TV App的设置界面下: 当我们点击检查更新的时候,就会向服务器发起版本检测的请求.一般的处理方式是:服务器 ...

  6. mysql 数据表 时间自动_MySQL数据库时间设置自动添加时间和自动更新时间

    MySQL字段中设置时间字段自动添加创建时间和自动更新时间设置, 设置字段类型为:timestamp 默认值设置为current_timestamp(), 更新时间字段字段类型为:timestamp ...

  7. html时间框自动更新,原生javascript实现自动更新的时间日期

    能够动态变化的事物总比静态的更能够吸引人,甚至更有实用效果,比如能够自动变化的时间日期效果就是如此,下面就通过代码实例介绍一下如何实现此效果,代码实例如下: 一.具体代码 脚本之家 var t = n ...

  8. java时间日期获得0点0分0秒(本地时间(时区)),获取当天零点零分时间(本地时间(时区))

    获取当天零点零分时间(本地时间(时区))返回LocalDateTime: LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).wit ...

  9. android自动更新nitz,手机时间、夏令时及Android时间更新方式

    一.时间 时间是指世界的时间,是对某一时刻的表示.手机系统的时间,通常是在状态栏.锁屏界面等表现给用户,让用户知道现在是什么时间.时间分为标准时间和夏令时时间,标准时间是指正常的时间,夏令时是指满足特 ...

最新文章

  1. php 多条查询结果插入新表,Mysql应用MySQL查询结果复制到新表的方法(更新、插入)...
  2. 向下钻取按钮位置设置
  3. gin post 数据参数_Gin 使用示例(四):绑定查询字符串或 POST 数据
  4. DLL入门浅析(2)——如何使用DLL
  5. MySQL(八)子查询和分组查询
  6. PHP 多参数方法的重构
  7. spring 通过id 查询数据_Spring 数据初始 H2 后进行数据查询提示 Schema not found 错误...
  8. linux系统安装并配置oracle客户端
  9. html5 websocket 手机,HTML5 WebSocket 示范
  10. 潘多拉固件设置ipv6_Phicomm-k2+pandorabox固件+PPPOE拨号+IPV6
  11. sql替换部分字符串,sql替换字符串中的某个字符方法
  12. 2021数学建模B题 空气质量二次模型
  13. 【收藏级教程】专业Finereport教程,帆软报表教程
  14. 测试开发面试题汇总(自用)
  15. 跨步电压和接触电压的区别及联系
  16. 会说话的汤姆猫2 Talking Tom 2(含数据包) v2.0.3
  17. sparc处理器开发工具_SPARC处理器启动代码的分析与编程
  18. 电缆故障测试仪的基本原理与组成——TFN DG15M电缆故障测试仪
  19. VMware vSphere 7 vCenter 7 ESXi 7 正式版下载地址
  20. 对于IT者的一些有价值的工作建议

热门文章

  1. 硅谷银行一夜破产!ChatGPT 之父撒钱救援,马斯克有意收购?
  2. 关于服务器ftp服务器设置基本步骤及注意要点
  3. TiKV源码分析(一)RaftKV层
  4. c++第一课 输出“Hello word”
  5. 据说程序员节 随手记录下matlab的tan和atan(反正切 arctan函数)
  6. dhu oj 题目列表
  7. 杭电2504又见GCD逆推最大公约数
  8. css3实现各种角度的三角形
  9. 用CSS实现三角形及其原理
  10. obsidian第三方插件无法加载