Android中的时间自动更新
最近几天,一直纠结于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- @Override
- publicvoidonSharedPreferenceChanged(SharedPreferencespreferences,Stringkey){
- if(key.equals(KEY_DATE_FORMAT)){
- Stringformat=preferences.getString(key,
- getResources().getString(R.string.default_date_format));
- Settings.System.putString(getContentResolver(),
- Settings.System.DATE_FORMAT,format);
- updateTimeAndDateDisplay(getActivity());
- }elseif(key.equals(KEY_AUTO_TIME)){
- booleanautoEnabled=preferences.getBoolean(key,true);
- Settings.System.putInt(getContentResolver(),Settings.System.AUTO_TIME,
- autoEnabled?1:0);
- mTimePref.setEnabled(!autoEnabled);
- mDatePref.setEnabled(!autoEnabled);
- }elseif(key.equals(KEY_AUTO_TIME_ZONE)){
- booleanautoZoneEnabled=preferences.getBoolean(key,true);
- Settings.System.putInt(
- getContentResolver(),Settings.System.AUTO_TIME_ZONE,autoZoneEnabled?1:0);
- mTimeZone.setEnabled(!autoZoneEnabled);
- }
- }
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- cr=phone.getContext().getContentResolver();
- cr.registerContentObserver(
- Settings.System.getUriFor(Settings.System.AUTO_TIME),true,
- mAutoTimeObserver);
- cr.registerContentObserver(
- Settings.System.getUriFor(Settings.System.AUTO_TIME_ZONE),true,
- mAutoTimeZoneObserver);
可以很明显的看到两个监听的和我们设置的URI是一样的。那我们再来看下这两个ContentObserver干了啥事
- privateContentObservermAutoTimeObserver=newContentObserver(newHandler()){
- @Override
- publicvoidonChange(booleanselfChange){
- Log.i("GsmServiceStateTracker","Autotimestatechanged");
- revertToNitzTime();
- }
- };
- privateContentObservermAutoTimeZoneObserver=newContentObserver(newHandler()){
- @Override
- publicvoidonChange(booleanselfChange){
- Log.i("GsmServiceStateTracker","Autotimezonestatechanged");
- revertToNitzTimeZone();
- }
- };
OK,真相出来了,正是他们更新了时间和时区。好的,下一步,我们继续分析时间和时区究竟是如何更新的。
3.更新时间和时区
先来分析更新时间。可能大家用过4.0的非CDMA制式手机就会有这样的感觉,选择自动更新时间,很快就自动更新了,选择自动更新时区,发现有时候过了很久很久都没有更新。OK,让我们先来分析下,时间是怎么自动更新的。
来分析下revertToNitzTime()函数。
[java] view plain copy- privatevoidrevertToNitzTime(){
- if(Settings.System.getInt(phone.getContext().getContentResolver(),
- Settings.System.AUTO_TIME,0)==0){
- return;
- }
- if(DBG){
- log("RevertingtoNITZTime:mSavedTime="+mSavedTime
- +"mSavedAtTime="+mSavedAtTime);
- }
- if(mSavedTime!=0&&mSavedAtTime!=0){
- setAndBroadcastNetworkSetTime(mSavedTime
- +(SystemClock.elapsedRealtime()-mSavedAtTime));
- }
- }
这个函数做的事情好简单啊,就是判断了下有没有选中自动更新啊,没有,那么返回。有,那再继续判断,mSavedTime和mSavedAtTime为不为0啊(这两个变量后面再讲),都不为0,那么就要发广播了。
[java] view plain copy- privatevoidsetAndBroadcastNetworkSetTime(longtime){
- SystemClock.setCurrentTimeMillis(time);
- Intentintent=newIntent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time",time);
- phone.getContext().sendStickyBroadcast(intent);
- }
发送广播如上,找到广播接收的地方NetworkTimeUpdateService.java
路径如下:frameworks/base/services/java/com/android/server/NetworkTimeUpdateService.java
找到它的receiver,惊讶的发现,什么事都没干,就是赋值了两个变量。
[java] view plain copy- @Override
- publicvoidonReceive(Contextcontext,Intentintent){
- Stringaction=intent.getAction();
- if(TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)){
- mNitzTimeSetTime=SystemClock.elapsedRealtime();
- }elseif(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)){
- mNitzZoneSetTime=SystemClock.elapsedRealtime();
- }
- }
真正更新时间的地方在哪儿?
答案还是在NetworkTimeupdateService.java中,它也注册了ContentObserver。
[java] view plain copy- mSettingsObserver=newSettingsObserver(mHandler,EVENT_AUTO_TIME_CHANGED);
- mSettingsObserver.observe(mContext);
SettingsObserver就是一个ContentObserver,具体的代码我就不贴出来了,很简单,大家可以自己去看。
好的,继续分析更改时间的地方,找到handleMessage里的回调函数,onPollNetworkTime(),这个函数很长,我就简单的贴出部分关键代码
[java] view plain copy- //IfNITZtimewasreceivedlessthanPOLLING_INTERVAL_MStimeago,
- //noneedtosynctoNTP.
- if(mNitzTimeSetTime!=NOT_SET&&refTime-mNitzTimeSetTime<POLLING_INTERVAL_MS){
- resetAlarm(POLLING_INTERVAL_MS);
- return;
- }
- .......
- if(mTime.getCacheAge()<POLLING_INTERVAL_MS){
- finallongntp=mTime.currentTimeMillis();
- .....
- }
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- Stringiso=SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
- if(zone==null){
- if(mGotCountryCode){
- if(iso!=null&&iso.length()>0){
- zone=TimeUtils.getTimeZone(tzOffset,dst!=0,
- c.getTimeInMillis(),
- iso);
- }else{
- //Wedon'thaveavalidisocountrycode.Thisis
- //mostlikelybecausewe'reonatestnetworkthat's
- //usingabogusMCC(eg,"001"),sogetaTimeZone
- //basedonlyontheNITZparameters.
- zone=getNitzTimeZone(tzOffset,(dst!=0),c.getTimeInMillis());
- }
- }
- }
- if(zone==null){
- //Wegotthetimebeforethecountry,sowedon'tknow
- //howtoidentifytheDSTrulesyet.Savetheinformation
- //andhopetofixituplater.
- mNeedFixZone=true;
- mZoneOffset=tzOffset;
- mZoneDst=dst!=0;
- mZoneTime=c.getTimeInMillis();
- }
- if(zone!=null){
- if(getAutoTimeZone()){
- setAndBroadcastNetworkSetTimeZone(zone.getID());
- }
- saveNitzTimeZone(zone.getID());
- }
利用得到的国家码和偏移值算出时区,而这个算出来的时区会保存在变量中,当你选择自动更新的时候,会把这个变量赋值给上层完成更新。好了,大体上更新时间和时区就是这样,还有部分是天线状态改变的时候会触发时间和时区的更改,这里就不讨论了,大家有兴趣的可以看下,代码同样在GsmServiceStateTracker中。
更多相关文章
- Android(安卓)小小白入门学习详解(不喝脉动,不吃果冻,Android足以让
- Android(安卓)各版本 设置 USB 默认连接 MTP 模式 ( Android(安
- Android中的时间自动更新
- Android(安卓)App多个入口的实现方法
- Android(安卓)设置合理的定时器隔一段时间执行某段程序
- 我的2016,展望2017
- 我的Android音乐播放器
- Android之异步处理
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面