在開發android系統設定的過程中會涉及許多内容。其中很簡單的時區設定就包含很多内容。前面分析的設定時間自動同步的相關内容,下面接着分析一下系統中時區設定的相關内容。
以Android 5.1.1 LMY48M這個版本為例說明:
在時區設定裡會調用到Settings\src\com\android\settings\ZonePicker.java這個檔案其中:
@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(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();
}
}
完成了時區的設定,這麼看來完成這件事情的是AlarmManager這個類,為了知其然更要知其是以然我們繼續跟進AlarmManager
/**
* Set the system default time zone.
* Requires the permission android.permission.SET_TIME_ZONE.
*
* @param timeZone in the format understood by {@link java.util.TimeZone}
*/
public void setTimeZone(String timeZone) {
try {
mService.setTimeZone(timeZone);
} catch (RemoteException ex) {
}
}
可見AlarmManager也是調用AlarmManagerService.java這個來實作的,繼續跟進,在這個服務中
public void setTimeZone(String tz) {
mContext.enforceCallingOrSelfPermission(
"android.permission.SET_TIME_ZONE",
"setTimeZone");
long oldId = Binder.clearCallingIdentity();
try {
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(mDescriptor, -(gmtOffset / ));
}
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());
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
其中 SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());是實作問題的關鍵。我們繼續檢視SystemProperties.java這個檔案,其中
private static native String native_get(String key);
private static native String native_get(String key, String def);
private static native int native_get_int(String key, int def);
private static native long native_get_long(String key, long def);
private static native boolean native_get_boolean(String key, boolean def);
private static native void native_set(String key, String def);
private static native void native_add_change_callback();
/**
* Set the value for the given key.
* @throws IllegalArgumentException if the key exceeds 32 characters
* @throws IllegalArgumentException if the value exceeds 92 characters
*/
public static void set(String key, String val) {
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
if (val != null && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("val.length > " +
PROP_VALUE_MAX);
}
native_set(key, val);
}
這就開始通過jni調到c 、cpp的代碼了。在master/core/jni/android_os_SystemProperties.cpp中
static JNINativeMethod method_table[] = {
{ "native_get", "(Ljava/lang/String;)Ljava/lang/String;",
(void*) SystemProperties_getS },
{ "native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
(void*) SystemProperties_getSS },
{ "native_get_int", "(Ljava/lang/String;I)I",
(void*) SystemProperties_get_int },
{ "native_get_long", "(Ljava/lang/String;J)J",
(void*) SystemProperties_get_long },
{ "native_get_boolean", "(Ljava/lang/String;Z)Z",
(void*) SystemProperties_get_boolean },
{ "native_set", "(Ljava/lang/String;Ljava/lang/String;)V",
(void*) SystemProperties_set },
};
static void SystemProperties_set(JNIEnv *env, jobject clazz,
jstring keyJ, jstring valJ)
{
int err;
const char* key;
const char* val;
if (keyJ == NULL) {
jniThrowNullPointerException(env, "key must not be null.");
return ;
}
key = env->GetStringUTFChars(keyJ, NULL);
if (valJ == NULL) {
val = ""; /* NULL pointer not allowed here */
} else {
val = env->GetStringUTFChars(valJ, NULL);
}
err = property_set(key, val);
env->ReleaseStringUTFChars(keyJ, key);
if (valJ != NULL) {
env->ReleaseStringUTFChars(valJ, val);
}
if (err < ) {
jniThrowException(env, "java/lang/RuntimeException",
"failed to set system property");
}
}
其中 err = property_set(key, val);這行代碼是真正實作該功能的。進去繼續檢視。這代碼聲明在#include “cutils/properties.h”這個檔案中。在說下面的代碼邏輯之前先再說一點關于宏的一些概念。衆所周知c語言中不存在重載的概念,更不能在同一個檔案中同時定義同名的函數,為了實作這個功能源碼中通過 宏來指定不同條件下的編譯選擇。是以在 properties.c的代碼中存在多份property_set。控制具體編譯那個的宏是在/build/core/combo/include/arch 下面的某個AndroidConfig.h檔案其中就有HAVE_LIBC_SYSTEM_PROPERTIES的定義,但是其中注意這個和在編譯系統時候選擇的編譯版本有關系,如果是darwin-x86或者windows下面的檔案就沒有這個定義。其實作就是另外一套邏輯了。就我手機裡的系統當時的編譯選項而說的話是包含這個定義的。是以
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
int property_set(const char *key, const char *value)
{
return __system_property_set(key, value);
}
其中在libc/bionic/system_properties.cpp
int __system_property_set(const char *key, const char *value)
{
if (key == ) return -;
if (value == ) value = "";
if (strlen(key) >= PROP_NAME_MAX) return -;
if (strlen(value) >= PROP_VALUE_MAX) return -;
prop_msg msg;
memset(&msg, , sizeof msg);
msg.cmd = PROP_MSG_SETPROP;
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
const int err = send_prop_msg(&msg);
if (err < ) {
return err;
}
return ;
}
static int send_prop_msg(const prop_msg *msg)
{
const int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, );
if (fd == -) {
return -;
}
const size_t namelen = strlen(property_service_socket);
sockaddr_un addr;
memset(&addr, , sizeof(addr));
strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));
addr.sun_family = AF_LOCAL;
socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + ;
if (TEMP_FAILURE_RETRY(connect(fd, reinterpret_cast<sockaddr*>(&addr), alen)) < ) {
close(fd);
return -;
}
const int num_bytes = TEMP_FAILURE_RETRY(send(fd, msg, sizeof(prop_msg), ));
int result = -;
if (num_bytes == sizeof(prop_msg)) {
// We successfully wrote to the property server but now we
// wait for the property server to finish its work. It
// acknowledges its completion by closing the socket so we
// poll here (on nothing), waiting for the socket to close.
// If you 'adb shell setprop foo bar' you'll see the POLLHUP
// once the socket closes. Out of paranoia we cap our poll
// at 250 ms.
pollfd pollfds[];
pollfds[].fd = fd;
pollfds[].events = ;
const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, , /* ms */));
if (poll_result == && (pollfds[].revents & POLLHUP) != ) {
result = ;
} else {
// Ignore the timeout and treat it like a success anyway.
// The init process is single-threaded and its property
// service is sometimes slow to respond (perhaps it's off
// starting a child process or something) and thus this
// times out and the caller thinks it failed, even though
// it's still getting around to it. So we fake it here,
// mostly for ctl.* properties, but we do try and wait 250
// ms so callers who do read-after-write can reliably see
// what they've written. Most of the time.
// TODO: fix the system properties design.
result = ;
}
}
close(fd);
return result;
}
其中send_prop_msg是發消息給property_service.c,這個檔案是整個過程的終結點。在這個類中會将資料寫入/data/property這個目錄下有 persist.sys.timezone 這個檔案中
ok整個過程分析完了,至于為什麼用socket通信到property_service,請看老羅的:
http://blog.csdn.net/Luoshengyang/article/details/38102011