//TimeZone.java
public static synchronized TimeZone getDefault() {
if (defaultTimeZone == null) {
defaultTimeZone = ZoneInfoDB.getSystemDefault();
}
return (TimeZone) defaultTimeZone.clone();
}
//ZoneInfoDB.java
public static TimeZone getSystemDefault() {
synchronized (LOCK) {
TimezoneGetter tzGetter = TimezoneGetter.getInstance();
String zoneName = tzGetter != null ? tzGetter.getId() : null;
if (zoneName != null) {
zoneName = zoneName.trim();
}
if (zoneName == null || zoneName.isEmpty()) {
// use localtime for the simulator
// TODO: what does that correspond to?
zoneName = "localtime";
}
return TimeZone.getTimeZone(zoneName);
}
}
Android TimeZone设置
1. TimeZone设置流程
-
AlarmManager.java中提供了
public void setTimeZone(String timeZone)
方法 -
上述方法在AlarmManagerService中实现.在AlarmManagerService的
public void setTimeZone(String tz)
方法,首先会设置persist.sys.timezone属性;然后设置kernel的时区偏移;之后将TimeZone的默认值设置为null.最后发送Intent.ACTION_TIMEZONE_CHANGED
1.1. TimeZone.setDefault(null)
在TimeZone变更后,AlarmManagerService会调用此方法。这个方法会将进程默认的TImeZone——defaultTimeZone设置为null,当用户再次调用TimeZone.getDeault()的时候,就会重新获取系统时区。
由以上代码可知,最终会调用TimezoneGetter的public abstract String getId()
方法获取系统时区。
这个TimezoneGetter是在虚拟机创建APK进程的时候,设置的:
//RuntimeInit.java private static final void commonInit()
TimezoneGetter.setInstance(new TimezoneGetter() {
@Override
public String getId() {
return SystemProperties.get("persist.sys.timezone");
}
});
所以设置TimeZone的默认值为空之后,导致的行为是:从persist.sys.timezone属性值中重新获取用户设置的时区字符串。
1.2. Intent.ACTION_TIMEZONE_CHANGED
AlarmManagerService时区设置成功后,会发送ACTION_TIMEZONE_CHANGED的Intent。在ActivityManagerService中对此Intent有额外的处理。
//private final int broadcastIntentLocked
/*
* If this is the time zone changed action, queue up a message that will reset the timezone
* of all currently running processes. This message will get queued up before the broadcast
* happens.
*/
if (intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
}
从注释也可以看出,当时区变化后,需要重置所有正在运行的apk进程的时区。 AMS工作线程对于UPDATE_TIME_ZONE消息的处理如下:
case UPDATE_TIME_ZONE: {
synchronized (ActivityManagerService.this) {
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = mLruProcesses.get(i);
if (r.thread != null) {
try {
r.thread.updateTimeZone();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update time zone for: " + r.info.processName);
}
}
}
}
} break;
调用所有进程的IApplicationThread对象的updateTimeZone方法,通知apk更新本进程内的时区信息。这个方法的实现在ActivityThread中:
public void updateTimeZone() {
TimeZone.setDefault(null);
}
1.3. 总结
用户设置时区之后,系统会设置时区属性,并将TimeZone的默认值设置为null,然后当用户调用TimeZone的getDefault方法获取 时区的时候,通过默认注册的TimezoneGetter从系统属性中获取最新的时区字符串。
2. TimeZone文件
时区文件位于目录:bionic/libc/zoneinfo
,在编译代码的时候,会把zoneinfo.dat,zoneinfo.idx,zoneinfo.version这三个文件打包到system.img的/system/usr/share/zoneinfo/目录下面。
zoneinfo.idx中存放着索引entry,每一个entry对应一个时区。entry的结果如下:
-
TZNAME:40个字节,表示时区的名字字符串,比如Africa/Abidjan;
-
byteOffsets:zoneinfo.dat文件的偏移量,每一个时区的相信信息,都存放在zoneinfo.dat中。格式遵循tzfile,zoneinfo.dat中存放的是一系列的tzfile entry。byteOffsets偏移量表示TZNAME所对应的时区在zoneinfo.dat中的位置;
-
length:TZNAME时区所对应的tzfile entry的大小;
-
rawUtcOffsets:时区值,以毫秒为单位。这个时区偏移值是不计算dst。
zoneinfo.dat中tzfile entry格式如下:
tzfile entry采取的是大端模式。关于tzfile的详情可以叄考http://gnu.wiki/man5/tzfile.5.php,以及https://github.com/google/cctz/blob/master/src/tzfile.h。
在时间处理中有几个关键的概念:
-
standard time:标准时间,非DST时间;https://www.timeanddate.com/time/standard-time.html,https://en.wikipedia.org/wiki/Standard_time
-
wall time:也称为消逝时间。https://en.wikipedia.org/wiki/Wall-clock_time
在tzfile.h中,对于如何解析tzfile格式也做了说明:
#define TZ_MAGIC "TZif"
struct tzhead {
char tzh_magic[4]; /* TZ_MAGIC */
char tzh_version[1]; /* '\0' or '2' as of 2005 */
char tzh_reserved[15]; /* reserved--must be zero */
char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
char tzh_leapcnt[4]; /* coded number of leap seconds */
char tzh_timecnt[4]; /* coded number of transition times */
char tzh_typecnt[4]; /* coded number of local time types */
char tzh_charcnt[4]; /* coded number of abbr. chars */
};
/*
** . . .followed by. . .
**
** tzh_timecnt (char [4])s coded transition times a la time(2)
** tzh_timecnt (unsigned char)s types of local time starting at above
** tzh_typecnt repetitions of
** one (char [4]) coded UTC offset in seconds
** one (unsigned char) used to set tm_isdst
** one (unsigned char) that's an abbreviation list index
** tzh_charcnt (char)s '\0'-terminated zone abbreviations
** tzh_leapcnt repetitions of
** one (char [4]) coded leap second transition times
** one (char [4]) total correction after above
** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition
** time is standard time, if FALSE,
** transition time is wall clock time
** if absent, transition times are
** assumed to be wall clock time
** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition
** time is UTC, if FALSE,
** transition time is local time
** if absent, transition times are
** assumed to be local time
*/