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


这个函数就是找出键值对,看看TOC_NAME_LEN和TOC_TO_INFO的定义,

#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读入脚本文件,并且进行解析。









  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值