原文地址:http://blog.csdn.net/lindir/article/details/7973700


最近几天,一直纠结于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. publicvoidonSharedPreferenceChanged(SharedPreferencespreferences,Stringkey){
  3. if(key.equals(KEY_DATE_FORMAT)){
  4. Stringformat=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. }elseif(key.equals(KEY_AUTO_TIME)){
  10. booleanautoEnabled=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. }elseif(key.equals(KEY_AUTO_TIME_ZONE)){
  16. booleanautoZoneEnabled=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
  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. privateContentObservermAutoTimeObserver=newContentObserver(newHandler()){
  2. @Override
  3. publicvoidonChange(booleanselfChange){
  4. Log.i("GsmServiceStateTracker","Autotimestatechanged");
  5. revertToNitzTime();
  6. }
  7. };
  8. privateContentObservermAutoTimeZoneObserver=newContentObserver(newHandler()){
  9. @Override
  10. publicvoidonChange(booleanselfChange){
  11. Log.i("GsmServiceStateTracker","Autotimezonestatechanged");
  12. revertToNitzTimeZone();
  13. }
  14. };

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

3.更新时间和时区

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

来分析下revertToNitzTime()函数。

[java] view plain copy
  1. privatevoidrevertToNitzTime(){
  2. if(Settings.System.getInt(phone.getContext().getContentResolver(),
  3. Settings.System.AUTO_TIME,0)==0){
  4. return;
  5. }
  6. if(DBG){
  7. log("RevertingtoNITZTime: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
  1. privatevoidsetAndBroadcastNetworkSetTime(longtime){
  2. SystemClock.setCurrentTimeMillis(time);
  3. Intentintent=newIntent(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. publicvoidonReceive(Contextcontext,Intentintent){
  3. Stringaction=intent.getAction();
  4. if(TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)){
  5. mNitzTimeSetTime=SystemClock.elapsedRealtime();
  6. }elseif(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)){
  7. mNitzZoneSetTime=SystemClock.elapsedRealtime();
  8. }
  9. }

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

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

[java] view plain copy
  1. mSettingsObserver=newSettingsObserver(mHandler,EVENT_AUTO_TIME_CHANGED);
  2. mSettingsObserver.observe(mContext);


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

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

[java] view plain copy
  1. //IfNITZtimewasreceivedlessthanPOLLING_INTERVAL_MStimeago,
  2. //noneedtosynctoNTP.
  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. finallongntp=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. Stringiso=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. //Wedon'thaveavalidisocountrycode.Thisis
  10. //mostlikelybecausewe'reonatestnetworkthat's
  11. //usingabogusMCC(eg,"001"),sogetaTimeZone
  12. //basedonlyontheNITZparameters.
  13. zone=getNitzTimeZone(tzOffset,(dst!=0),c.getTimeInMillis());
  14. }
  15. }
  16. }
  17. if(zone==null){
  18. //Wegotthetimebeforethecountry,sowedon'tknow
  19. //howtoidentifytheDSTrulesyet.Savetheinformation
  20. //andhopetofixituplater.
  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定时关机
  2. 手动更新Android(安卓)SDK
  3. android 线程中的ui问题 Handler的基本使用 关于获取动态时间在u
  4. Android(安卓)Handler详解
  5. android datepickerdialog 时间上下限问题
  6. Android的子线程能更新UI吗?
  7. Android动画详细探究
  8. Android(安卓)倒计时功能,完美解决系统时间更改后,倒计时不准问题
  9. Android(安卓)Permission check的一点认知更新

随机推荐

  1. How to find IP address or Location of
  2. 输入删除时,javascript div消失
  3. 将循环的每次迭代延迟一定时间
  4. 请问解决整数,货币,INT,DOUBLE,等类型的JavaS
  5. 如何使用Angular的$资源取消正在进行的RE
  6. JS将字符串转换为数组
  7. 希望日期开始结束在一个数组中的while循
  8. 试着在我的javascript代码中理解“this”
  9. res.jwt不是函数 - NodeJS Express
  10. 我应该从MooTools转换为jQuery吗?