最近几天,一直纠结于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
[本地代码路径:packages/apps/AsusSettings/src/com/android/settings]
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key.equals(KEY_AUTO_TIME)) {
boolean autoEnabled = preferences.getBoolean(key, true);
Log.d(TAG, "Beibei test NITZ+ZONE autoEnabled= "+autoEnabled);//Enable=true;原始代码中没有LOG
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);
Log.d(TAG, "Beibei test NITZ+ZONE autoZoneEnabled= "+autoZoneEnabled);
Settings.Global.putInt(
getContentResolver(), Settings.Global.AUTO_TIME_ZONE, autoZoneEnabled ? 1 : 0);
mTimeZone.setEnabled(!autoZoneEnabled);
}
}
KEY_AUTO_TIME就是更新时间的CheckBoxPreference,下面的KEY_AUTO_TIME_ZONE就是更新时区的KEY_AUTO_TIME_ZONE,这两个操作仅仅只是设置了两个值下去而已,那么真正干事情的地方在哪儿呢?搜索关键字 Settings.Global.AUTO_TIME(点了自动更新会往数据库中写值,别的注册到这个observer的地方会在数据库值发生变化的时候被执行,直接搜哪些地方注册了Settings.Global.AUTO_TIME的observer
下午 05:21)
2.真正做事的地方
我们选中CheckBoxPreference,仅仅只是更改了数据库的值,但是时间和时区还是得更新的,那么更新它们的地方在哪儿呢?在GsmServiceStateTracker.java中。
代码路径为framworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
里面有两个ContentObserver来监听数据库内容的变化,让我们来看下这两个ContentObserver的定义。
// system setting property AIRPLANE_MODE_ON is set in Settings.
int airplaneMode = Settings.Global.getInt(
phone.getContext().getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0);///airplane mode
mDesiredPowerState = ! (airplaneMode > 0);
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);
可以很明显的看到两个监听的和我们设置的URI是一样的。那我们再来看下这两个ContentObserver干了啥事
private ContentObserver mAutoTimeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
Rlog.i("GsmServiceStateTracker", "Auto time state changed");
revertToNitzTime();
}
};
private ContentObserver mAutoTimeZoneObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
Rlog.i("GsmServiceStateTracker", "Auto time zone state changed");
revertToNitzTimeZone();
}
};
3.更新时间和时区
先来分析更新时间。可能大家用过4.0的非CDMA制式手机就会有这样的感觉,选择自动更新时间,很快就自动更新了,选择自动更新时区,发现有时候过了很久很久都没有更新。OK,让我们先来分析下,时间是怎么自动更新的。
来分析下revertToNitzTime()函数。
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));
}
}
这个函数做的事情好简单啊,就是判断了下有没有选中自动更新啊,没有,那么返回。有,那再继续判断,mSavedTime和mSavedAtTime为不为0啊(这两个变量后面再讲),都不为0,那么就要发广播了。
private void setAndBroadcastNetworkSetTime(long time) {
if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
SystemClock.setCurrentTimeMillis(time);
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);//关键字<span style="font-family: Arial;">ACTION_NETWORK_SET_TIME</span>
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("time", time);
mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
发送广播如上,找到广播接收的地方NetworkTimeUpdateService.java
路径如下:frameworks/base/services/java/com/android/server/NetworkTimeUpdateService.java,里面有registerForTelephonyIntents()进行注册。找到它的receiver,惊讶的发现,什么事都没干,就是赋值了两个变量。
/** Receiver for Nitz time events */
private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
@Override
public 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();
}
}
};
真正更新时间的地方在哪儿?继续搜索关键字 Settings.Global.AUTO_TIME。
答案还是在NetworkTimeupdateService.java中,它也注册了ContentObserver。
首先搜到的是注册的ContentObserver:
/** Observer to watch for changes to the AUTO_TIME setting */
private static class SettingsObserver extends ContentObserver {
private int mMsg;
private Handler mHandler;
SettingsObserver(Handler handler, int msg) {
super(handler);
mHandler = handler;
mMsg = msg;
}
void observe(Context context) {
ContentResolver resolver = context.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(<strong>Settings.Global.AUTO_TIME</strong>),
false, this);
}
@Override
public void onChange(boolean selfChange) {
mHandler.obtainMessage(mMsg).sendToTarget();//onChange<span lang="zh-cn"><span style="font-family:Microsoft YaHei;font-size:12px;color:#000000;">数据库值变化的时候执行的东西,mMsg一般在这个文件的handleMessage里面</span></span>
}
}
当前java文件接着搜索SettingsObserver
mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
mSettingsObserver.observe(mContext);
SettingsObserver就是一个ContentObserver,具体的代码我就不贴出来了,很简单,大家可以自己去看。
好的,继续分析更改时间的地方,找到handleMessage里的回调函数,onPollNetworkTime(),这个函数很长,我就简单的贴出部分关键代码
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_AUTO_TIME_CHANGED:
case EVENT_POLL_NETWORK_TIME:
case EVENT_NETWORK_CHANGED:
onPollNetworkTime(msg.what);
break;
}
}
}
private void onPollNetworkTime(int event) {
......
// If NITZ time was received less than mPollingIntervalMs time ago,
// no need to sync to NTP.
if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
resetAlarm(mPollingIntervalMs);
return;
}
........
// only update when NTP time is fresh
if (mTime.getCacheAge() < mPollingIntervalMs) {
final long ntp = mTime.currentTimeMillis();
mTryAgainCounter = 0;
OK,看注释,看到我们更新的NITZ时间不为NOT_SET(-1),且更新的时间小于mPollingIntervalMs ,那么就直接更新NITZ的时间,否则用SNTP更新时间。那么,NITZ的时间是从何而来的呢,我们进一步分析。
4.为什么不能更新时区,却能更新时间
看看NITZ的时间究竟从何而来,回到广播的发送处。GsmServiceStateTracker中,我们发送的是mSavedTime
+ (SystemClock.elapsedRealtime() - mSavedAtTime)。
看看这两个变量mSavedTime和mSavedAtTime在哪里赋值,在setTimeFromNITZString()中,
private void saveNitzTime(long time) {
mSavedTime = time;
mSavedAtTime = SystemClock.elapsedRealtime();
}
只有setTimeFromNITZString()中调用了saveNitzTime()-->saveNitzTime(c.getTimeInMillis());
那么我们往上追踪就会发现,这个函数是由RIL_UNSOL_NITZ_TIME_RECEIVED这个主动上报的消息激发的,至此,我们终于找到了时间更新原理。
handleMessage()中:
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;
搜索EVENT_NITZ_TIME
public GsmServiceStateTracker(GSMPhone phone) {
mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
搜索setOnNITZTime
在BaseCommands.java中注册:
@Override
public void setOnNITZTime(Handler h, int what, Object obj) {
mNITZTimeRegistrant = new Registrant (h, what, obj);
}
mNITZTimeRegistrant
case RIL_UNSOL_NITZ_TIME_RECEIVED:
if (RILJ_LOGD) unsljLogRet(response, ret);
.....
if (ignoreNitz) {
if (RILJ_LOGD) riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
} else {
if (mNITZTimeRegistrant != null) {
mNITZTimeRegistrant
.notifyRegistrant(new AsyncResult (null, result, null));
}
.....
}
先看有没有RIL的主动上报,如果有,那么就用这种主动上报的NITZ时间更新,如果没有,那么就选择用SNTP更新时间。
那么,时区为什么不能自动更新呢,那是因为,如果没有RIL的主动上报,时区就没有了初始值的,而SNTP不能更新时区。所以,时区只能用NITZ更新。
我们来用代码验证下。
先来看发广播的地方,广播仍然发到了NetworkTimeUpdateService,它是更改了mNitzZoneSetTime,但是,我们惊奇的发现,这个变量值一点作用都没有,而且在mNitzZoneSetTime中,我们也没有自动更新时区的监听。因此,时区完全被我们抛弃了。。。
那在GsmServiceStateTracker中呢,看看RIL主动上报干了些啥事
private void setTimeFromNITZString (String nitz, long nitzReceiveTime) {
String iso = 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 {
// 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) {
// We got the time before the country, so we don't know
// how to identify the DST rules yet. Save the information
// and hope to fix it up later.
mNeedFixZone = true;
mZoneOffset = tzOffset;
mZoneDst = dst != 0;
mZoneTime = c.getTimeInMillis();
}
if (zone != null) {
if (getAutoTimeZone()) {
setAndBroadcastNetworkSetTimeZone(zone.getID());
}
saveNitzTimeZone(zone.getID());
}
利用得到的国家码和偏移值算出时区,而这个算出来的时区会保存在变量中,当你选择自动更新的时候,会把这个变量赋值给上层完成更新。好了,大体上更新时间和时区就是这样,还有部分是天线状态改变的时候会触发时间和时区的更改,这里就不讨论了,大家有兴趣的可以看下,代码同样在GsmServiceStateTracker中。
原文地址:
http://blog.csdn.net/lindir/article/details/7973700