Android的属性Property系统
一直想研究一下android的属性系统,刚好最近一个项目告一段落,可以开始研究一下相关代码。
按照我的理解,Android属性分为两个部分
1、一个部分是系统属性,一般与虚拟机相关的一些属性,
代码位置
dalvik/libcore/luni-kernel/src/main/java/java/lang/System.java
dalvik/libcore/luni/src/main/java/java/util/Properties.java
dalvik/vm/Properties.c
虚拟机有一些默认属性,例如os.arch,java.boot.class.path等,只加载一次。
来看一些这种属性的加载过程,以Settings.java中的VNC属性为例
private DialogInterface.OnClickListener mVncDisableListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { System.setProperty("vncserver.enable", "0"); System.setProperty("vncserver.password", ""); } };
看System.java的代码 public static String setProperty(String prop, String value) { if (prop.length() == 0) { throw new IllegalArgumentException(); } SecurityManager secMgr = System.getSecurityManager(); if (secMgr != null) { secMgr.checkPermission(new PropertyPermission(prop, "write")); } return (String)internalGetProperties().setProperty(prop, value); }
首先会对该线程执行写权限的检查,然后才设置属性
在internalGetProperties方法里面,会加载虚拟机默认属性。
static Properties internalGetProperties() { if (System.systemProperties == null) { SystemProperties props = new SystemProperties(); props.preInit(); props.postInit(); System.systemProperties = props; } return systemProperties; }
这里的SystemProperties只是内部类,跟android.os.SystemProperties不是同一个类。
class SystemProperties extends Properties { // Dummy, just to make the compiler happy. native void preInit(); native void postInit();}
它继承了Properties,两个JNI接口在dalvik/vm/native/java_lang_SystemProperties.c中注册,preInit调用本地到本地dvmCreateDefaultProperties函数,该函数就负责加载刚才说的虚拟机默认属性。
static void Dalvik_java_lang_SystemProperties_preInit(const u4* args, JValue* pResult){ dvmCreateDefaultProperties((Object*) args[0]); RETURN_VOID();}也就是说System.setProperty调用到Properties.setProperty,
public Object setProperty(String name, String value) { return put(name, value); }Properties是继承Hashtable的
public class Properties extends Hashtable<Object, Object>这样,就完成设置属性的动作,获取的动作类似,最后从哈希表中根据key拿到value,整个过程比较简单。
可以看到这套属性系统只适合一些不会变化,或者很少变的属性,如果你希望你的属性改变之后能触发某些实践,例如init.rc脚本中的动作,那就要用到另外一套属性系统了。
2、剩下一部分是常规属性。
它的实现原理跟刚才的hash表不一样,是讲属性保存在一块共享内存之中,该共享内存的大小由环境变量ANDROID_PROPERTY_WORKSPACE决定
代码位置:
frameworks/base/core/java/android/os/SystemProperties.java
frameworks/base/core/jni/android_os_SystemProperties.cpp
system/core/init/property_service.c
bionic/libc/bionic/system_properties.c
写属性的过程:
SystemProperties.java
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); }value值只支持String类型,而get重载了各种类型的value
这些方法调用jni
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);
这些jni在frameworks/base/core/jniandroid_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 },};
其中SystemProperties_set方法调用到property_service.c中的
int property_set(const char *name, const char *value)
在property_set中的流程是这样的
首相,通过
pi = (prop_info*) __system_property_find(name);
找到对应的键值对,prop_info在bionic/libc/include/sys/_system_properties.h有定义
struct prop_area { unsigned volatile count; unsigned volatile serial; unsigned magic; unsigned version; unsigned reserved[4]; unsigned toc[1];};#define SERIAL_VALUE_LEN(serial) ((serial) >> 24)#define SERIAL_DIRTY(serial) ((serial) & 1)struct prop_info { char name[PROP_NAME_MAX]; unsigned volatile serial; char value[PROP_VALUE_MAX];};
来看看__system_property_find的实现,该函数位于system_properties.c中
const prop_info *__system_property_find(const char *name){ prop_area *pa = __system_property_area__; unsigned count = pa->count; unsigned *toc = pa->toc; unsigned len = strlen(name); prop_info *pi; while(count--) { unsigned entry = *toc++; if(TOC_NAME_LEN(entry) != len) continue; pi = TOC_TO_INFO(pa, entry); if(memcmp(name, pi->name, len)) continue; return pi; } return 0;}
#define TOC_NAME_LEN(toc) ((toc) >> 24)#define TOC_TO_INFO(area, toc) ((prop_info*) (((char*) area) + ((toc) & 0xFFFFFF)))
因此toc的高8位保存的是属性名长度,低24位保存属性键值对的地址,
再看__system_property_area__了,这是个全局变量,在system_properties.c的__system_properties_init函数中初始化
该函数读取ANDROID_PROPERTY_WORKSPACE环境变量,格式为:fd,size
然后利用mmap将"fd"处的内容,映射"size"大小,赋给__system_property_area__。
如果匹配成功,看看property_set是怎么做的
if(pi != 0) { /* ro.* properties may NEVER be modified once set */ if(!strncmp(name, "ro.", 3)) return -1; pa = __system_property_area__; update_prop_info(pi, value, valuelen); pa->serial++; __futex_wake(&pa->serial, INT32_MAX); }
注意pa->serial++,它的修饰符包含一个volatile,这样做是确保每一次针对属性系统的改动都能得到处理。
看看update_prop_info
static void update_prop_info(prop_info *pi, const char *value, unsigned len){ pi->serial = pi->serial | 1; memcpy(pi->value, value, len + 1); pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff); __futex_wake(π->serial, INT32_MAX);}
首先讲针对该格式的修改序列号+1,然后保存属性值,最后调用__futex_wake触发一个系统调用,在atomics_x86.c中是这样写的
int __futex_wake(volatile void *ftx, int count){ int ret; asm volatile ( "int $0x80;" : "=a" (ret) : "0" (FUTEX_SYSCALL), "b" (ftx), "c" (FUTEX_WAKE), "d" (count) ); return ret;}
具体是什么意思待研究。
接下来,就是property_set执行如下语句
property_changed(name, value);property_changed在system/core/init/init.c中有定义
void property_changed(const char *name, const char *value){ if (property_triggers_enabled) { queue_property_triggers(name, value); drain_action_queue(); }}
property_triggers_enabled在执行main函数里面设定。
void queue_property_triggers(const char *name, const char *value){ struct listnode *node; struct action *act; list_for_each(node, &action_list) { act = node_to_item(node, struct action, alist); if (!strncmp(act->name, "property:", strlen("property:"))) { const char *test = act->name + strlen("property:"); int name_length = strlen(name); if (!strncmp(name, test, name_length) && test[name_length] == '=' && !strcmp(test + name_length + 1, value)) { action_add_queue_tail(act); } } }}
这个函数讲action_list中的所有关心该属性的动作都串到act中,action_list应该是在解析初始化脚本文件的时候生成的。
void drain_action_queue(void){ struct listnode *node; struct command *cmd; struct action *act; int ret; while ((act = action_remove_queue_head())) { INFO("processing action %p (%s)\n", act, act->name); list_for_each(node, &act->commands) { cmd = node_to_item(node, struct command, clist); ret = cmd->func(cmd->nargs, cmd->args); INFO("command '%s' r=%d\n", cmd->args[0], ret); } }}
这个函数负责触发各个回调函数。
脚本文件的解析由system/core/init/parser.c完成,来看init.c的main函数有如下语句
get_hardware_name(); snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); parse_config_file(tmp);
在parser.c里面
int parse_config_file(const char *fn){ char *data; data = read_file(fn, 0); if (!data) return -1; parse_config(fn, data); DUMP(); return 0;}parse_config_file读入脚本文件,并且进行解析。
更多相关文章
- Android 图形系统之图形缓冲区分配
- Android窗口系统第三篇---WindowManagerService中窗口的组织方式
- Android系统多媒体框架添加对.wma格式的支持
- [Hi3751V811][Android8.0]系统按键的转换 - android键值的映射
- 理解WebKit和Chromium: 调试Android系统上的Chromium