最近几天,一直纠结于android的时间的自动更新,先简要说下android自己原有的更新机制,android原有的更新机制很简单,是采用NITZ(Network identity and Time Zone)的方式更新的,这应该是一种运营商的可选服务,简单的来说,就是运营商通知CP主动上报时间信息,CP上报后上层更新相应的时间。CDMA制式估计上报时间比较频繁,更新比较给力,因此CDMA制式的时间只能自动更新,而不会让用户手动设置的(原因仅仅为个人猜测)。WCDMA和GSM的就比较悲催了,如果你等主动上报的更新消息,可能得等N长时间还是不能更新。

但是,也不是说没有办法让时间自动更新,采用SNTP方式的话,我们也能自动更新时间,但是,采用SNTP的话,是不能更新时区的。

下面以4.0的代码为例(2.3的基本类似且更简单,并且2.3中没有自带SNTP的更新方式)来分析下android的自动更新时间原理。

1.选中自动更新

其实就是在数据库中设置了一个值,2.3中只有一个选项-同步,就是会同步时区和时间日期,4.0中把他们分成了两项,时区和日期时间能分别进行自动更新,其实原理都是一样,都是在数据库中设置了一个值。

代码路径:packages/apps/Settings/src/com/android/settings/DateTimeSettings.java

[java] view plain copy print ?
  1. @Override
  2. public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
  3. if (key.equals(KEY_DATE_FORMAT)) {
  4. String format = preferences.getString(key,
  5. getResources().getString(R.string.default_date_format));
  6. Settings.System.putString(getContentResolver(),
  7. Settings.System.DATE_FORMAT, format);
  8. updateTimeAndDateDisplay(getActivity());
  9. } else if (key.equals(KEY_AUTO_TIME)) {
  10. boolean autoEnabled = preferences.getBoolean(key, true);
  11. Settings.System.putInt(getContentResolver(), Settings.System.AUTO_TIME,
  12. autoEnabled ? 1 : 0);
  13. mTimePref.setEnabled(!autoEnabled);
  14. mDatePref.setEnabled(!autoEnabled);
  15. } else if (key.equals(KEY_AUTO_TIME_ZONE)) {
  16. boolean autoZoneEnabled = preferences.getBoolean(key, true);
  17. Settings.System.putInt(
  18. getContentResolver(), Settings.System.AUTO_TIME_ZONE, autoZoneEnabled ? 1 : 0);
  19. mTimeZone.setEnabled(!autoZoneEnabled);
  20. }
  21. }

KEY_AUTO_TIME就是更新时间的CheckBoxPreference,下面的KEY_AUTO_TIME_ZONE就是更新时区的KEY_AUTO_TIME_ZONE,这两个操作仅仅只是设置了两个值下去而已,那么真正干事情的地方在哪儿呢?

2.真正做事的地方

我们选中CheckBoxPreference,仅仅只是更改了数据库的值,但是时间和时区还是得更新的,那么更新它们的地方在哪儿呢?在GsmServiceStateTracker.java中。

代码路径为framworks/base/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java

里面有两个ContentObserver来监听数据库内容的变化,让我们来看下这两个ContentObserver的定义。

[java] view plain copy print ?
  1. cr = phone.getContext().getContentResolver();
  2. cr.registerContentObserver(
  3. Settings.System.getUriFor(Settings.System.AUTO_TIME), true,
  4. mAutoTimeObserver);
  5. cr.registerContentObserver(
  6. Settings.System.getUriFor(Settings.System.AUTO_TIME_ZONE), true,
  7. mAutoTimeZoneObserver);

可以很明显的看到两个监听的和我们设置的URI是一样的。那我们再来看下这两个ContentObserver干了啥事

[java] view plain copy print ?
  1. private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
  2. @Override
  3. public void onChange(boolean selfChange) {
  4. Log.i("GsmServiceStateTracker", "Auto time state changed");
  5. revertToNitzTime();
  6. }
  7. };
  8. private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {
  9. @Override
  10. public void onChange(boolean selfChange) {
  11. Log.i("GsmServiceStateTracker", "Auto time zone state changed");
  12. revertToNitzTimeZone();
  13. }
  14. };

OK,真相出来了,正是他们更新了时间和时区。好的,下一步,我们继续分析时间和时区究竟是如何更新的。

3.更新时间和时区

先来分析更新时间。可能大家用过4.0的非CDMA制式手机就会有这样的感觉,选择自动更新时间,很快就自动更新了,选择自动更新时区,发现有时候过了很久很久都没有更新。OK,让我们先来分析下,时间是怎么自动更新的。

来分析下revertToNitzTime()函数。

[java] view plain copy print ?
  1. private void revertToNitzTime() {
  2. if (Settings.System.getInt(phone.getContext().getContentResolver(),
  3. Settings.System.AUTO_TIME, 0) == 0) {
  4. return;
  5. }
  6. if (DBG) {
  7. log("Reverting to NITZ Time: mSavedTime=" + mSavedTime
  8. + " mSavedAtTime=" + mSavedAtTime);
  9. }
  10. if (mSavedTime != 0 && mSavedAtTime != 0) {
  11. setAndBroadcastNetworkSetTime(mSavedTime
  12. + (SystemClock.elapsedRealtime() - mSavedAtTime));
  13. }
  14. }

这个函数做的事情好简单啊,就是判断了下有没有选中自动更新啊,没有,那么返回。有,那再继续判断,mSavedTime和mSavedAtTime为不为0啊(这两个变量后面再讲),都不为0,那么就要发广播了。

[java] view plain copy print ?
  1. private void setAndBroadcastNetworkSetTime(long time) {
  2. SystemClock.setCurrentTimeMillis(time);
  3. Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
  4. intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
  5. intent.putExtra("time", time);
  6. phone.getContext().sendStickyBroadcast(intent);
  7. }

发送广播如上,找到广播接收的地方NetworkTimeUpdateService.java

路径如下:frameworks/base/services/java/com/android/server/NetworkTimeUpdateService.java

找到它的receiver,惊讶的发现,什么事都没干,就是赋值了两个变量。

[java] view plain copy print ?
  1. @Override
  2. public void onReceive(Context context, Intent intent) {
  3. String action = intent.getAction();
  4. if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
  5. mNitzTimeSetTime = SystemClock.elapsedRealtime();
  6. } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
  7. mNitzZoneSetTime = SystemClock.elapsedRealtime();
  8. }
  9. }

真正更新时间的地方在哪儿?

答案还是在NetworkTimeupdateService.java中,它也注册了ContentObserver。

[java] view plain copy print ?
  1. mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
  2. mSettingsObserver.observe(mContext);


SettingsObserver就是一个ContentObserver,具体的代码我就不贴出来了,很简单,大家可以自己去看。

好的,继续分析更改时间的地方,找到handleMessage里的回调函数,onPollNetworkTime(),这个函数很长,我就简单的贴出部分关键代码

[java] view plain copy print ?
  1. // If NITZ time was received less than POLLING_INTERVAL_MS time ago,
  2. // no need to sync to NTP.
  3. if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) {
  4. resetAlarm(POLLING_INTERVAL_MS);
  5. return;
  6. }
  7. .......
  8. if (mTime.getCacheAge() < POLLING_INTERVAL_MS) {
  9. final long ntp = mTime.currentTimeMillis();
  10. .....
  11. }


OK,看注释,看到我们更新的NITZ时间不为NOT_SET(-1),且更新的时间小于POLLING_INTERVAL_MS,那么就直接更新NITZ的时间,否则用SNTP更新时间。那么,NITZ的时间是从何而来的呢,我们进一步分析。

4.为什么不能更新时区,却能更新时间

看看NITZ的时间究竟从何而来,找到广播的发送处。GsmServiceStateTracker中,我们发送的是mSavedTime
+ (SystemClock.elapsedRealtime() - mSavedAtTime)。

看看这两个变量在哪里赋值,在setTimeFromNITZString()中,那么我们往上追踪就会发现,这个函数是由RIL_UNSOL_NITZ_TIME_RECEIVED这个主动上报的消息激发的,至此,我们终于找到了时间更新原理。先看有没有RIL的主动上报,如果有,那么就用这种主动上报的NITZ时间更新,如果没有,那么就选择用SNTP更新时间。

那么,时区为什么不能自动更新呢,那是因为,如果没有RIL的主动上报,时区就没有了初始值的,而SNTP不能更新时区。所以,时区只能用NITZ更新。

我们来用代码验证下。

先来看发广播的地方,广播仍然发到了NetworkTimeUpdateService,它是更改了mNitzZoneSetTime,但是,我们惊奇的发现,这个变量值一点作用都没有,而且在mNitzZoneSetTime中,我们也没有自动更新时区的监听。因此,时区完全被我们抛弃了。。。

那在GsmServiceStateTracker中呢,看看RIL主动上报干了些啥事

[java] view plain copy print ?
  1. String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
  2. if (zone == null) {
  3. if (mGotCountryCode) {
  4. if (iso != null && iso.length() > 0) {
  5. zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
  6. c.getTimeInMillis(),
  7. iso);
  8. } else {
  9. // We don't have a valid iso country code. This is
  10. // most likely because we're on a test network that's
  11. // using a bogus MCC (eg, "001"), so get a TimeZone
  12. // based only on the NITZ parameters.
  13. zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
  14. }
  15. }
  16. }
  17. if (zone == null) {
  18. // We got the time before the country, so we don't know
  19. // how to identify the DST rules yet. Save the information
  20. // and hope to fix it up later.
  21. mNeedFixZone = true;
  22. mZoneOffset = tzOffset;
  23. mZoneDst = dst != 0;
  24. mZoneTime = c.getTimeInMillis();
  25. }
  26. if (zone != null) {
  27. if (getAutoTimeZone()) {
  28. setAndBroadcastNetworkSetTimeZone(zone.getID());
  29. }
  30. saveNitzTimeZone(zone.getID());
  31. }

利用得到的国家码和偏移值算出时区,而这个算出来的时区会保存在变量中,当你选择自动更新的时候,会把这个变量赋值给上层完成更新。好了,大体上更新时间和时区就是这样,还有部分是天线状态改变的时候会触发时间和时区的更改,这里就不讨论了,大家有兴趣的可以看下,代码同样在GsmServiceStateTracker中。

更多相关文章

  1. Android HAL 层框架分析以及代码示例
  2. Android init源代码分析(1)概要分析
  3. Android设备之间通过Wifi通信的示例代码
  4. Android ActionBar的源代码分析(三)
  5. Android 设置合理的定时器隔一段时间执行某段程序
  6. Android代码开发性能指引

随机推荐

  1. Android 浅谈MatrixCursor
  2. Android(安卓)ProgressBar 进度条
  3. Android app实现重启
  4. Android开发环境搭建及入门相关
  5. Android移动端音视频的快速开发教程(五)
  6. 【转】Android中Webview使用经验总结
  7. ToggleButton控件
  8. 64位系统使用Android虚拟机问题
  9. Android Studio 编译 freso问题
  10. Android(Java) GMT时间转换