Android平台微型技术博客4

最近几天,一直纠结于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

  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中。

扫描二维码关注公众号,回复: 3672062 查看本文章

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

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

[java] view plain copy

  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

  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.   
  9.    private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {  
  10.        @Override  
  11.        public void onChange(boolean selfChange) {  
  12.            Log.i("GsmServiceStateTracker""Auto time zone state changed");  
  13.            revertToNitzTimeZone();  
  14.        }  
  15.    };  

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

3.更新时间和时区

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

来分析下revertToNitzTime()函数。

[java] view plain copy

  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.     }  
  15.    

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

[java] view plain copy

  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

  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

  1. mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);  
  2. mSettingsObserver.observe(mContext);  


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

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

[java] view plain copy

  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

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

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

猜你喜欢

转载自blog.csdn.net/IamFiora/article/details/80746435