Android 7.1.1中SystemProperties詳解
阿新 • • 發佈:2019-02-13
Android系統原始碼中,存在大量的SystemProperties.get或SystemProperties.set,通過這兩個介面可以對系統的屬性進行讀取/設定,看著挺簡單的就是呼叫get或set就能獲取或設定系統屬性,其實並不然。曾經也遇到過有關的坑,所以就總結了下,這樣以後自己就不會在再次入坑了,接下來了正題吧
property_get和property_set是在properties.c裡實現的,如下
3.1 Makefile中直接把$(TARGET_DEVICE_DIR)/system.prop的內容追加到build.prop中
3.2 收集ADDITIONAL_BUILD_PROPERTIES中的屬性,追加到build.prop中
3.3 ADDITIONAL_BUILD_PROPERTIES又會收集PRODUCT_PROPERTY_OVERRIDES中定義的屬性
在配置系統屬性時,如果是在*.prop檔案中配置直接是在檔案新增一行“persist.timed.enable=true”就行,但在*.mk配置時就需要加上PRODUCT_PROPERTY_OVERRIDES屬性,需要注意最後一個沒有“\”,下面提供了一個例項
屬性名稱以“persist.”開頭,當設定這個屬性時,其值也將寫入/data/property。
屬性名稱以“net.”開頭,當設定這個屬性時,“net.change”屬性將會自動設定,以加入到最後修改的屬性名。
(這是很巧妙的。 netresolve模組的使用這個屬性來追蹤在net.*屬性上的任何變化。)
屬性“ ctrl.start ”和“ ctrl.stop ”是用來啟動和停止服務。每一項服務必須在init.rc中定義。系統啟動時,與init守護程序將解析init.rc和啟動屬性服務(此處在7.0上有改動,做了相關優化,後面會說到)。一旦收到設定“ ctrl.start ”屬性的請求,屬性服務將使用該屬性值作為服務名找到該服務,啟動該服務。這項服務的啟動結果將會放入“ init.svc.<服務名>“屬性中。客戶端應用程式可以輪詢那個屬性值,以確定結果。
需要注意的一點是在Android7.0以後在mk檔案中提供了一個編譯巨集LOCAL_INIT_RC用於將服務相關的RC檔案編譯到相應位置。這能確保服務定義和服務的可執行檔案同時存在,避免了之前出現的服務對應的可執行程式不存在的問題。
單一的init*.rc,被拆分,服務根據其二進位制檔案的位置(/system,/vendor,/odm)定義到對應分割槽的etc/init目錄中,每個服務一個rc檔案。與該服務相關的觸發器、操作等也定義在同一rc檔案中。
在init執行mount_all指令掛載分割槽時,會載入這些目錄中的rc檔案,並在適當的時機執行這些服務和操作,具體可以參考system/core/init/readme.txt檔案裡面有詳細的介紹。
系統屬性是在init.rc中載入的,具體啟動方式如下:
private static final String TAG = "FlashlightController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
利用adb命令設定屬性值來控制該日誌開關
adb shell setprop log.tag.FlashlightController DEBUG 設定該TAG的輸出級別為DEBUG,則 level為DEBUG以上的都返回true
需要注意的是通過adb shell setprop設定的屬性值每次重啟後都會恢復之前的值,log的級別如下:
where <tag> is a log component tag (or * for all) and priority is:
V Verbose (default for <tag>)
D Debug (default for '*')
I Info
W Warn
E Error
F Fatal
S Silent (suppress all output)
也可以將該屬性新增在data/local.prop屬性檔案中,不同的是隻要存在local.prop,該手機重啟與否屬性值都存在,另外需要注意的是user版ro.debuggable=0,系統啟動時不會讀取/data/local.prop檔案
終於寫得差不多了,可能比較囉嗦,不過感覺自己對系統屬性的瞭解更深了,中間還學習到了不少的c中常用的函式,感覺還是值得。
參考文件:
http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html
http://androidxref.com/7.1.1_r6/xref/system/core/init/readme.txt
1、SystemProperties的使用
SystemProperties的使用很簡單,在SystemProperties.java中所以方法都是static,直接通過SystemProperties.get(String key)或SystemProperties.set(String key, String val)就可以了,系統屬性都是以鍵值對的形式存在即name和value需要注意的是對name和value的length是有限制的,name的最大長度是31,value最大長度是91,具體定義如下:
在呼叫get或set時,都是通過呼叫native方法去操作的,開始本來想一筆帶過的,還是看看native方法中的具體流程吧,如果不感興趣可以直接看第三條//frameworks/base/core/java/android/os/SystemProperties.java public class SystemProperties { public static final int PROP_NAME_MAX = 31; public static final int PROP_VALUE_MAX = 91; ... private static native String native_get(String key); private static native void native_set(String key, String def); /** * Get the value for the given key. * @return an empty string if the key isn't found * @throws IllegalArgumentException if the key exceeds 32 characters */ public static String get(String key) { if (key.length() > PROP_NAME_MAX) { throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); } return native_get(key); } ... /** * 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); } ... }
ps:在呼叫SystemProperties.set時所在的apk uid必須在system group,否則設定屬性會報錯,在manifest配置下行:
2、SystemProperties中方法具體實現
SystemProperties.java中所有native方法都是在android_os_SystemProperties.cpp裡實現,先來看看java中個方法是怎麼和cpp中方法對應起來的register_android_os_SystemProperties方法是在AndroidRuntime.cpp中呼叫的,RegisterMethodsOrDie可以簡單的理解為把method_table數組裡的方法一一對應起來。在android_os_SystemProperties.cpp中可以發現最終實現是不區分SystemProperties_get_int或 SystemProperties_getS,基本所有的方法都是呼叫property_get和property_set方法來實現的//frameworks/base/core/jni/android_os_SystemProperties.cpp static const 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 }, { "native_add_change_callback", "()V", (void*) SystemProperties_add_change_callback }, }; int register_android_os_SystemProperties(JNIEnv *env) { return RegisterMethodsOrDie(env, "android/os/SystemProperties", method_table, NELEM(method_table)); }
property_get和property_set是在properties.c裡實現的,如下
//system/core/libcutils/properties.c
int property_get(const char *key, char *value, const char *default_value)
{
int len;
len = __system_property_get(key, value);
if(len > 0) {
return len;
}
if(default_value) {
len = strlen(default_value);
if (len >= PROPERTY_VALUE_MAX) {
len = PROPERTY_VALUE_MAX - 1;
}
memcpy(value, default_value, len);
value[len] = '\0';
}
return len;
}
int property_set(const char *key, const char *value)
{
return __system_property_set(key, value);
}
程序啟動後資料已經將系統屬性資料讀取到相應的共享記憶體中,儲存在全域性變數__system_property_area__,具體操作在system_properties.cpp中
//bionic/libc/bionic/system_properties.cpp
int __system_property_get(const char *name, char *value)
{
const prop_info *pi = __system_property_find(name);
if (pi != 0) {
//資料已經儲存在記憶體中__system_property_area__ 等待讀取完返回
return __system_property_read(pi, 0, value);
} else {
value[0] = 0;
return 0;
}
}
設定屬性通過非同步socket通訊,向property_service傳送訊息
int __system_property_set(const char *key, const char *value)
{
if (key == 0) return -1;
if (value == 0) value = "";
if (strlen(key) >= PROP_NAME_MAX) return -1;
if (strlen(value) >= PROP_VALUE_MAX) return -1;
prop_msg msg;
memset(&msg, 0, 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 < 0) {
return err;
}
return 0;
}
static int send_prop_msg(const prop_msg *msg)
{ //sokcet 通訊 /dev/socket/property_service
const int fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd == -1) {
return -1;
}
//static const char property_service_socket[] = "/dev/socket/" PROP_SERVICE_NAME;
const size_t namelen = strlen(property_service_socket);
sockaddr_un addr;
memset(&addr, 0, 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) + 1;
if (TEMP_FAILURE_RETRY(connect(fd, reinterpret_cast<sockaddr*>(&addr), alen)) < 0) {
close(fd);
return -1;
}
const int num_bytes = TEMP_FAILURE_RETRY(send(fd, msg, sizeof(prop_msg), 0));
...
close(fd);
return result;
}
property_service是在init程序呼叫start_property_service啟動的,在property_service.cpp中
//system/core/init/property_service.cpp
void start_property_service() {
//建立socket
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
//監聽socket
listen(property_set_fd, 8);
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
還是在property_service.cpp的handle_property_set_fd()處理對應屬性
static void handle_property_set_fd()
{
//等待建立通訊
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/獲取套接字相關資訊 uid gid
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
...
//接收屬性設定請求訊息
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
//處理訊息
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
//檢查屬性名是否合法
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
//處理ctl.開頭訊息
if(memcmp(msg.name,"ctl.",4) == 0) {
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
//檢查許可權,處理以ctl開頭的屬性
if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
//檢查許可權,設定對應的屬性
if (check_mac_perms(msg.name, source_ctx, &cr)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
close(s);
}
...
}
}
不繼續看了,後面的坑還有很多,考慮到後面還有幾個方面沒講,簡單說下property_set最後是呼叫了property_set_impl實現的,有興趣的可以繼續去跟蹤
3、系統屬性怎麼生成的
Android的build.prop檔案是在Android編譯時刻收集的各種property(LCD density/語言/編譯時間, etc.),編譯完成之後,檔案生成在out/target/product/<board>/system/目錄下,build.prop的生成是由make系統解析build/core/Makefile完成。3.1 Makefile中直接把$(TARGET_DEVICE_DIR)/system.prop的內容追加到build.prop中
3.2 收集ADDITIONAL_BUILD_PROPERTIES中的屬性,追加到build.prop中
3.3 ADDITIONAL_BUILD_PROPERTIES又會收集PRODUCT_PROPERTY_OVERRIDES中定義的屬性
在配置系統屬性時,如果是在*.prop檔案中配置直接是在檔案新增一行“persist.timed.enable=true”就行,但在*.mk配置時就需要加上PRODUCT_PROPERTY_OVERRIDES屬性,需要注意最後一個沒有“\”,下面提供了一個例項
PRODUCT_PROPERTY_OVERRIDES += \
persist.timed.enable=true \
persist.timed.enable=true \
... \
key=value
4、系統屬性類別和載入優先順序
屬性名稱以“ro.”開頭,被視為只讀屬性。一旦設定,屬性值不能改變。屬性名稱以“persist.”開頭,當設定這個屬性時,其值也將寫入/data/property。
屬性名稱以“net.”開頭,當設定這個屬性時,“net.change”屬性將會自動設定,以加入到最後修改的屬性名。
(這是很巧妙的。 netresolve模組的使用這個屬性來追蹤在net.*屬性上的任何變化。)
屬性“ ctrl.start ”和“ ctrl.stop ”是用來啟動和停止服務。每一項服務必須在init.rc中定義。系統啟動時,與init守護程序將解析init.rc和啟動屬性服務(此處在7.0上有改動,做了相關優化,後面會說到)。一旦收到設定“ ctrl.start ”屬性的請求,屬性服務將使用該屬性值作為服務名找到該服務,啟動該服務。這項服務的啟動結果將會放入“ init.svc.<服務名>“屬性中。客戶端應用程式可以輪詢那個屬性值,以確定結果。
需要注意的一點是在Android7.0以後在mk檔案中提供了一個編譯巨集LOCAL_INIT_RC用於將服務相關的RC檔案編譯到相應位置。這能確保服務定義和服務的可執行檔案同時存在,避免了之前出現的服務對應的可執行程式不存在的問題。
單一的init*.rc,被拆分,服務根據其二進位制檔案的位置(/system,/vendor,/odm)定義到對應分割槽的etc/init目錄中,每個服務一個rc檔案。與該服務相關的觸發器、操作等也定義在同一rc檔案中。
在init執行mount_all指令掛載分割槽時,會載入這些目錄中的rc檔案,並在適當的時機執行這些服務和操作,具體可以參考system/core/init/readme.txt檔案裡面有詳細的介紹。
系統屬性是在init.rc中載入的,具體啟動方式如下:
//system/core/rootdir/init.rc
on property:sys.boot_from_charger_mode=1
trigger late-init
# Load properties from /system/ + /factory after fs mount.
on load_system_props_action
load_system_props #定義在property_service.cpp
on load_persist_props_action
load_persist_props #定義在property_service.cpp
start logd
start logd-reinit
# Mount filesystems and start core system services.
on late-init
# Load properties from /system/ + /factory after fs mount. Place
# this in another action so that the load will be scheduled after the prior
# issued fs triggers have completed.
trigger load_system_props_action
# Load persist properties and override properties (if enabled) from /data.
trigger load_persist_props_action
當屬性值sys.boot_from_charger_mode為1時,會觸發late-init,在late-init又會觸發load_system_props_action和load_persist_props_action,具體看看property_service.cpp這兩個方法對應幹啥了
//system/core/init/property_service.cpp
void load_system_props() {
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);//載入system/build.prop
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);//載入vendor/build.prop
load_properties_from_file(PROP_PATH_FACTORY, "ro.*");//載入factory/factory.prop
load_recovery_id_prop();//載入recovery相關prop
}
void load_persist_props(void) {
load_override_properties();//如果"ro.debuggable"為1載入data/local.prop裡的屬性
/* Read persistent properties after all default values have been loaded. */
load_persistent_properties();//載入/data/property裡的persistent properties
}
當同一屬性在多個檔案中都有配置,先載入的會被後加載的覆蓋。5、利用系統屬性動態設定程式中Log的開關
android 動態控制logcat日誌開關,通過Log.isLoggable(TAG,level)方法動態控制,以FlashlightController類為例private static final String TAG = "FlashlightController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
利用adb命令設定屬性值來控制該日誌開關
adb shell setprop log.tag.FlashlightController DEBUG 設定該TAG的輸出級別為DEBUG,則 level為DEBUG以上的都返回true
需要注意的是通過adb shell setprop設定的屬性值每次重啟後都會恢復之前的值,log的級別如下:
where <tag> is a log component tag (or * for all) and priority is:
V Verbose (default for <tag>)
D Debug (default for '*')
I Info
W Warn
E Error
F Fatal
S Silent (suppress all output)
也可以將該屬性新增在data/local.prop屬性檔案中,不同的是隻要存在local.prop,該手機重啟與否屬性值都存在,另外需要注意的是user版ro.debuggable=0,系統啟動時不會讀取/data/local.prop檔案
終於寫得差不多了,可能比較囉嗦,不過感覺自己對系統屬性的瞭解更深了,中間還學習到了不少的c中常用的函式,感覺還是值得。
參考文件:
http://www.cnblogs.com/bastard/archive/2012/10/11/2720314.html
http://androidxref.com/7.1.1_r6/xref/system/core/init/readme.txt