Android“时区”的那些事儿(1)

来源:互联网 发布:大数据产品经理面试 编辑:程序博客网 时间:2024/06/10 16:42

1. 问题与试验

Android官方API提供了一个获取当前系统时间的方法:

System.currentTimeMillis()

官方文档的说明:

long currentTimeMillis ()

Returns the current time in milliseconds. Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the underlying operating system and may be larger. For example, many operating systems measure time in units of tens of milliseconds.

See the description of the class Date for a discussion of slight discrepancies that may arise between “computer time” and coordinated universal time (UTC).

这个API被广泛用于获取时间戳计时。同时,Android支持多时区。那么就引出一个问题,在切换时区的时候(随网络自动切换或者手动切换,同时不手动改变手机时间),这个时间戳的值是否是不受影响地持续自增?时区变化会导致当前系统时间随时差而变,这个变化是如何实现的?

先拿一个最简单的测试程序来试验:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button button = (Button) findViewById(R.id.button);        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                test();            }        });    }    private void test() {        Log.i("TEST_ZONE", "" + System.currentTimeMillis());    }}

点击Button取时间戳打log出来。两次取时间戳,中间切换一次时区:

08-10 09:52:56.087 21629 21629 I TEST_ZONE: 150232997608708-10 03:53:19.125 21629 21629 I TEST_ZONE: 1502329999125

可以看到时间戳是在正常地增长。看起来应该是在标准化时间的计算上处理的不同时区时间转换。

2. 系统如何处理时区

代码分析基于Android 7.1.1

2.1 设置更改时区的逻辑

在系统应用设置中,处理时区设置的逻辑位于packages/apps/Settings/src/com/android/settings/ZonePicker.java

/** * The class displaying a list of time zones that match a filter string * such as "Africa", "Europe", etc. Choosing an item from the list will set * the time zone. Pressing Back without choosing from the list will not * result in a change in the time zone setting. */public class ZonePicker extends ListFragment

设置系统时区的代码片段:

    @Override    public void onListItemClick(ListView listView, View v, int position, long id) {        // Ignore extra clicks        if (!isResumed()) return;        final Map<?, ?> map = (Map<?, ?>)listView.getItemAtPosition(position);        final String tzId = (String) map.get(ZoneGetter.KEY_ID);        // Update the system timezone value        final Activity activity = getActivity();        final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);        alarm.setTimeZone(tzId);        final TimeZone tz = TimeZone.getTimeZone(tzId);        if (mListener != null) {            mListener.onZoneSelected(tz);        } else {            getActivity().onBackPressed();        }    }

看到设置时区使用的API为 AlarmManager.setTimeZone(),官方文档的解释为:

void setTimeZone (String timeZone)

Sets the system’s persistent default time zone. This is the time zone for all apps, even after a reboot. Use setDefault(TimeZone) if you just want to change the time zone within your app, and even then prefer to pass an explicit TimeZone to APIs that require it rather than changing the time zone for all threads.

On android M and above, it is an error to pass in a non-Olson timezone to this function. Note that this is a bad idea on all Android releases because POSIX and the TimeZone class have opposite interpretations of ‘+’ and ‘-’ in the same non-Olson ID.

这是对外的Manager,找到它对应的系统服务,继续分析:

AlarmManagerService.java

        @Override        public void setTimeZone(String tz) {            getContext().enforceCallingOrSelfPermission(                    "android.permission.SET_TIME_ZONE",                    "setTimeZone");            final long oldId = Binder.clearCallingIdentity();            try {                setTimeZoneImpl(tz);            } finally {                Binder.restoreCallingIdentity(oldId);            }        }
    void setTimeZoneImpl(String tz) {        if (TextUtils.isEmpty(tz)) {            return;        }        TimeZone zone = TimeZone.getTimeZone(tz);        // Prevent reentrant calls from stepping on each other when writing        // the time zone property        boolean timeZoneWasChanged = false;        synchronized (this) {            String current = SystemProperties.get(TIMEZONE_PROPERTY);            if (current == null || !current.equals(zone.getID())) {                if (localLOGV) {                    Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());                }                timeZoneWasChanged = true;                SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());            }            // Update the kernel timezone information            // Kernel tracks time offsets as 'minutes west of GMT'            int gmtOffset = zone.getOffset(System.currentTimeMillis());            setKernelTimezone(mNativeData, -(gmtOffset / 60000));        }        TimeZone.setDefault(null);        if (timeZoneWasChanged) {            Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);            intent.putExtra("time-zone", zone.getID());            getContext().sendBroadcastAsUser(intent, UserHandle.ALL);        }    }

setTimeZoneImpl()做三件事:
(1)更新时区对应的系统属性persist.sys.timezone。
(2)更新Linux Kernel时区信息。从注释上看到内核会监测与GMT(格林尼治时间)的时差。
(3)发出时区变更的广播。
并且可以看到这里的确没有对系统时间戳有更改。(1)和(3)比较简单,主要看一下(2):

            // Update the kernel timezone information            // Kernel tracks time offsets as 'minutes west of GMT'            int gmtOffset = zone.getOffset(System.currentTimeMillis());            setKernelTimezone(mNativeData, -(gmtOffset / 60000));

setKernelTimezone()是一个本地方法,还可以看到有另一个本地方法setKernelTime()的定义在一起:

private native int setKernelTime(long nativeData, long millis);private native int setKernelTimezone(long nativeData, int minuteswest);

对应的本地代码在base/services/core/jni/com_android_server_AlarmManagerService.cpp:

static jint android_server_AlarmManagerService_setKernelTime(JNIEnv*, jobject, jlong nativeData, jlong millis){    AlarmImpl *impl = reinterpret_cast<AlarmImpl *>(nativeData);    struct timeval tv;    int ret;    if (millis <= 0 || millis / 1000LL >= INT_MAX) {        return -1;    }    tv.tv_sec = (time_t) (millis / 1000LL);    tv.tv_usec = (suseconds_t) ((millis % 1000LL) * 1000LL);    ALOGD("Setting time of day to sec=%d\n", (int) tv.tv_sec);    ret = impl->setTime(&tv);    if(ret < 0) {        ALOGW("Unable to set rtc to %ld: %s\n", tv.tv_sec, strerror(errno));        ret = -1;    }    return ret;}static jint android_server_AlarmManagerService_setKernelTimezone(JNIEnv*, jobject, jlong, jint minswest){    struct timezone tz;    tz.tz_minuteswest = minswest;    tz.tz_dsttime = 0;    int result = settimeofday(NULL, &tz);    if (result < 0) {        ALOGE("Unable to set kernel timezone to %d: %s\n", minswest, strerror(errno));        return -1;    } else {        ALOGD("Kernel timezone updated to %d minutes west of GMT\n", minswest);    }    return 0;}
原创粉丝点击