Android的屬性Property系統

來源:互聯網
上載者:User

一直想研究一下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讀入指令檔,並且進行解析。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.