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()的时候,就会重新获取系统时区。

//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);
    }
}

由以上代码可知,最终会调用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

在时间处理中有几个关键的概念:

在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
*/

results matching ""

    No results matching ""