標籤:
Systemproperties類在android.os下,但這個類是隱藏的,上層程式開發無法直接使用,用Java的反射機制就可以了。
Java代碼中建立與修改android屬性用Systemproperties.set(name, value),擷取android屬性用Systemproperties.get(name),
Native代碼中通過property_get(const char *key, char *value, const char *default_value)/property_set(const char *name, const char *value)來讀取和設定屬性。需要注意的是android屬性的名稱是有一定的格式要求的,首碼必須用system\core\init\property_service.c中定義的首碼(後面會詳細列出),進行系統屬性設定的程式也必須有system或root許可權,如何將android程式的許可權提升到system許可權呢?方法是這個樣子滴:
1、在AndroidManifest.xml中,加入android:sharedUserId="android.uid.system"。
2、在Android.mk中,設定LOCAL_CERTIFICATE := platform。
注意:使用property_get/property_set這兩個API必須要包含標頭檔cutils/properties.h和連結libcutils庫。
Shell指令碼中Android提供了命令列工具setprop和getprop來設定和擷取屬性。用法舉例:getprop sys.settingkeys.disabled,setprop sys.settingkeys.disabled 1
用法就講完了,怎麼樣,很簡單吧?下面我們具體來看一下讀取和設定系統屬性的流程:
讀取屬性:
native_get()[SystemProperties.java]
property_get()[android_os_SystemProperties.cpp]-->__system_property_read()[system_properties.c]
\bionic\libc\bionic\system_properties.c中:
int __system_property_get(const char *name, char *value){ //資料已經儲存在共用記憶體中,通過__system_property_area__ 即可擷取到,之後等待讀取完返回 const prop_info *pi = __system_property_find(name); return __system_property_read(pi, 0, value);//阻塞式讀取}設定屬性(用戶端):
native_set()[SystemProperties.java]
__system_property_set()[system_properties.c ]-->send_prop_msg()[system_properties.c ]
int __system_property_set(const char *key, const char *value){ msg.cmd = PROP_MSG_SETPROP; strlcpy(msg.name, key, sizeof msg.name); strlcpy(msg.value, value, sizeof msg.value); err = send_prop_msg(&msg);}//發送訊息通知property_service去啟動服務或者設定屬性static int send_prop_msg(prop_msg *msg){ //與/dev/socket/property_service通訊 s = socket(AF_LOCAL, SOCK_STREAM, 0); connect(s, (struct sockaddr *) &addr, alen) send(s, msg, sizeof(prop_msg), 0) close(s);}可以看出讀取屬性比較簡單,就是通過__system_property_read直接讀取共用記憶體中的資料即可,而設定屬性則是通過用戶端發送socket訊息讓property_service去啟動服務或者設定屬性。
伺服器端:
\system\core\init\Init.c
int main(int argc, char **argv){ //加入到action queue隊列 queue_builtin_action(property_service_init_action, "property_service_init");...//將屬性系統初始化函數加入action queue queue_builtin_action(property_init_action, "property_init");... for(;;) //執行action queue隊列 //接收通過socket向property service發送的資料 nr = poll(ufds, fd_count, timeout); …… handle_property_set_fd();}static int property_service_init_action(int nargs, char **args){ start_property_service();}void property_changed(const char *name, const char *value){ if (property_triggers_enabled) { queue_property_triggers(name, value); drain_action_queue(); }}\system\core\init\property_service.c:
void property_init(bool load_defaults){ //初始化共用記憶體空間 init_property_area(); //載入屬性檔案 load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);}
\bionic\libc\bionic\system_properties.c
static int init_property_area(void){ //建立匿名記憶體空間PA_SIZE = 32768 init_workspace(&pa_workspace, PA_SIZE) //將記憶體地區分成兩部分:屬性系統基本資料和屬性索引值對 pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); //初始化屬性系統資訊 pa = pa_workspace.data; memset(pa, 0, PA_SIZE); pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* plug into the lib property services 每個進程都會使用此變數,指向系統屬性共用記憶體地區,訪問系統屬性,很重要*/ __system_property_area__ = pa;}static int init_workspace(workspace *w, size_t size){ //dev is a tmpfs是一種虛擬記憶體檔案系統 int fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600); //將檔案對應為共用進程空間記憶體 使其可以與操作記憶體方式一致 void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); //刪除檔案 fd = open("/dev/__properties__", O_RDONLY); unlink("/dev/__properties__"); //儲存fd size 將作為環境變數傳遞給每個進程 w->data = data; w->size = size; w->fd = fd;}static void load_properties_from_file(const char *fn){ //讀取系統屬性索引值對資料寫入到共用記憶體中 data = read_file(fn, &sz); load_properties(data);}void start_property_service(void){ //載入屬性設定檔,載入的屬性將覆蓋原先的值。這些屬性載入之後,最後載入的屬性會被保持在/data/property中。 load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE); load_persistent_properties(); //建立socket資源 並綁定 fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); //監聽 listen(fd, 8);}void handle_property_set_fd(){ //等待建立通訊 s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size); //擷取通訊端相關資訊 uid gid getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size); //接收屬性佈建要求訊息 recv(s, &msg, sizeof(msg), 0); //處理訊息 switch(msg.cmd) { case PROP_MSG_SETPROP: //通過設定系統屬性 處理ctl.開頭訊息 if(memcmp(msg.name,"ctl.",4) == 0) { //許可權檢測 if (check_control_perms(msg.value, cr.uid, cr.gid)) { handle_control_message((char*) msg.name + 4, (char*) msg.value); } } else { //更改系統屬性值 if (check_perms(msg.name, cr.uid, cr.gid)) { property_set((char*) msg.name, (char*) msg.value); } } break; } close(s);}void handle_control_message(const char *msg, const char *arg){ if (!strcmp(msg,"start")) { msg_start(arg); } else if (!strcmp(msg,"stop")) { msg_stop(arg); } else if (!strcmp(msg,"restart")) { msg_stop(arg); msg_start(arg); } }static void msg_start(const char *name){ service_start(svc, args);}void service_start(struct service *svc, const char *dynamic_args){ //建立進程 pid = fork(); if (pid == 0) { if (properties_inited()) { //擷取系統屬性空間檔案描述 get_property_workspace(&fd, &sz); //dup最小的可用檔案描述符 sprintf(tmp, "%d,%d", dup(fd), sz); //加入ANDROID_PROPERTY_WORKSPACE環境變數到ENV //包含共用記憶體fd add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); } //執行程式 傳遞環境變數ENV execve(svc->args[0], (char**) svc->args, (char**) ENV) //設定Service系統屬性 notify_service_state(svc->name, "running"); }}void get_property_workspace(int *fd, int *sz){ *fd = pa_workspace.fd; *sz = pa_workspace.size;}
在bionic\libc\bionic\libc_init_dynamic.c中:
//將系統屬性記憶體空間映射到當前進程虛擬空間,進程在啟動時,會載入動態庫bionic libc庫:void __attribute__((constructor)) __libc_preinit(void); void __libc_preinit(void){ __libc_init_common(elfdata);}void __libc_init_common(uintptr_t *elfdata){ __system_properties_init();}int __system_properties_init(void){ prop_area *pa; int s, fd; unsigned sz; char *env; //擷取環境變數ANDROID_PROPERTY_WORKSPACE //與上面init進程中設定對應 env = getenv("ANDROID_PROPERTY_WORKSPACE"); //共用記憶體檔案描述符 記憶體大小 fd = atoi(env); sz = atoi(env + 1); //將檔案描述符映射到當前進程虛擬空間記憶體,實現共用記憶體 pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0); //全域變數指向共用系統屬性記憶體首地址 __system_property_area__ = pa;}
static int check_perms(const char *name, unsigned int uid, unsigned int gid){ //進行許可權檢測 for (i = 0; property_perms[i].prefix; i++) { int tmp; if (strncmp(property_perms[i].prefix, name, strlen(property_perms[i].prefix)) == 0) { if ((uid && property_perms[i].uid == uid) || (gid && property_perms[i].gid == gid)) { return 1; } } } return 0;}property_perms[] = { { "net.rmnet0.", AID_RADIO, 0 }, { "net.gprs.", AID_RADIO, 0 }, { "net.ppp", AID_RADIO, 0 }, { "ril.", AID_RADIO, 0 }, { "gsm.", AID_RADIO, 0 }, { "persist.radio", AID_RADIO, 0 }, { "net.dns", AID_RADIO, 0 }, { "net.", AID_SYSTEM, 0 }, { "dev.", AID_SYSTEM, 0 }, { "runtime.", AID_SYSTEM, 0 }, { "hw.", AID_SYSTEM, 0 }, { "sys.", AID_SYSTEM, 0 }, { "service.", AID_SYSTEM, 0 }, { "wlan.", AID_SYSTEM, 0 }, { "dhcp.", AID_SYSTEM, 0 }, { "dhcp.", AID_DHCP, 0 }, { "vpn.", AID_SYSTEM, 0 }, { "vpn.", AID_VPN, 0 }, { "debug.", AID_SHELL, 0 }, { "log.", AID_SHELL, 0 }, { "service.adb.root", AID_SHELL, 0 }, { "persist.sys.", AID_SYSTEM, 0 }, { "persist.service.", AID_SYSTEM, 0 }, { "persist.security.", AID_SYSTEM, 0 }, { NULL, 0, 0 }};int property_set(const char *name, const char *value){property_changed(name, value);return 0;}以上使用到的相關的宏定義在這裡:
bionic/libc/include/sys/_system_properties.h
#define PROP_SERVICE_NAME "property_service"#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
還有這裡 system/core/include/private/android_filesystem_config.h
#define AID_SYSTEM 1000 /* system server */#define AID_RADIO 1001 /* telephony subsystem, RIL */#define AID_DHCP 1014 /* dhcp client */#define AID_VPN 1016 /* vpn system */#define AID_SHELL 2000 /* adb and debug shell user */
代碼有點長,具體來解釋下這段代碼都做了神馬~
Init main()
1、property_init
初始化共用記憶體空間,即:將檔案(/dev/__properties__)映射為共用進程空間記憶體,使其可以與操作記憶體方式一致,將__system_property_area__指向系統屬性共用記憶體地區,每個進程都會使用此變數。
載入屬性設定檔(/default.prop),並寫入到共用記憶體中。
2、start_property_service
載入屬性設定檔,載入的屬性將覆蓋原先的值,這些屬性載入之後,最後載入的屬性會被保持在/data/property中,還會寫到共用記憶體中。
順序是:
/system/build.prop
/system/default.prop
/data/local.prop
接著建立socket property_service,並且監聽用戶端的訊息。
3、handle_property_set_fd
main()裡面有個死迴圈,不停的去取出訊息,然後使用handle_property_set_fd處理,該函數做的事情是:如果接收到PROP_MSG_SETPROP訊息,
如果是以ctl.開頭訊息,那麼就是用來啟動和關閉service的,關閉/啟動服務是在新的進程中進行的,會把共用記憶體的fd加入到環境變數中。
例如:
// start boot animation
property_set("ctl.start", "bootanim");
當然在init.rc中表明服務是否在開機時啟動也是可以的,如:
service adbd /sbin/adbd
class core
disabled //不自動啟動
如果不是以ctl.開頭的訊息,那就是設定系統屬性值了。
為了方便理解,上一張圖片:
(圖片來自網路)
我們可以看出Android屬性系統由有三個進程,一組屬性檔案和一塊共用記憶體組成。這塊共用記憶體儲存著系統中所有的屬性記錄,只有Property service能寫這塊共用記憶體,並且Property service負責將屬性檔案中的屬性記錄載入到共用記憶體中。
屬性讀取進程(property consumer)把這塊共用記憶體映射到自己的進程空間,然後直接讀取它。
屬性設定進程(property setter)也載入這塊共用到他的進程空間,但是他不能直接寫這塊共用記憶體。當他需要增加或者修改屬性的時候,通過Unix Socket發生屬性給Property service,Property service將代表設定進程寫入共用記憶體和屬性檔案。
Property service運行於init進程中。init進程首先建立一塊共用記憶體,並把他的控制代碼fd存放在這塊記憶體中,init進程通過mmap帶MAP_SHARE標誌的系統調用,把這塊記憶體映射到他的虛擬空間中,最終這塊記憶體所有的更新將會被所有映射這塊共用記憶體的進程看到。共用記憶體控制代碼fd和共用記憶體大小儲存在系統內容變數“ANDROID_PROPERTY_WORKSPACE”中,所有的進程包括屬性設定進程和屬性讀取進程都將通過這個系統內容變數獲得共用記憶體的控制代碼fd和大小,然後把這塊記憶體映射到他們自己的虛擬空間。然後,init進程將會從以下檔案中載入屬性:
/default.prop
/system/build.prop
/system/default.prop
/data/local.prop
下一步是啟動Property service。這步中,將會建立一個Unix Socket伺服器,這個Socket有一個聞名的名稱“/dev/socket/property_service”。最後init進入死迴圈,等待socket的串連請求。
在讀取進程中,當它初始化libc庫的時候,將會獲得屬性系統共用記憶體的控制代碼和大小(bionic/libc/bionic/libc_init_common.c __libc_init_common函數)。並把這塊共用記憶體映射到自己的進程虛擬空間中(bionic/libc/bionic/system_properties.c __system_properties_init函數)。這樣讀取進程將會向訪問普通記憶體一樣訪問屬性系統的共用記憶體了。
最後,來一個擴充:
如果屬性名稱以“ro.”開頭,那麼這個屬性被視為唯讀屬性。一旦設定,屬性值不能改變。
如果屬性名稱以“persist.”開頭,當設定這個屬性時,其值也將寫入/data/property 。
如果屬性名稱以“net.”開頭,當設定這個屬性時,“net.change”屬性將會自動化佈建,以加入到最後修改的屬性名稱。(這是很巧妙的。 netresolve模組的使用這個屬性來追蹤在net.*屬性上的任何變化。)
屬性“ ctrl.start ”和“ ctrl.stop ”是用來啟動和停止服務。
參考文檔:
http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html
http://blog.csdn.net/ameyume/article/details/8056492
Android SystemProperties系統屬性詳解