1. 程式人生 > >Android 7.1.1中SystemProperties詳解

Android 7.1.1中SystemProperties詳解

Android系統原始碼中,存在大量的SystemProperties.get或SystemProperties.set,通過這兩個介面可以對系統的屬性進行讀取/設定,看著挺簡單的就是呼叫get或set就能獲取或設定系統屬性,其實並不然。曾經也遇到過有關的坑,所以就總結了下,這樣以後自己就不會在再次入坑了,接下來了正題吧

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,具體定義如下:

//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);
    }
    ...
}
在呼叫get或set時,都是通過呼叫native方法去操作的,開始本來想一筆帶過的,還是看看native方法中的具體流程吧,如果不感興趣可以直接看第三條

ps:在呼叫SystemProperties.set時所在的apk uid必須在system group,否則設定屬性會報錯,在manifest配置下行:

2、SystemProperties中方法具體實現

SystemProperties.java中所有native方法都是在android_os_SystemProperties.cpp裡實現,先來看看java中個方法是怎麼和cpp中方法對應起來的
//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));
}
register_android_os_SystemProperties方法是在AndroidRuntime.cpp中呼叫的,RegisterMethodsOrDie可以簡單的理解為把method_table數組裡的方法一一對應起來。在android_os_SystemProperties.cpp中可以發現最終實現是不區分SystemProperties_get_int或 SystemProperties_getS,基本所有的方法都是呼叫property_get和property_set方法來實現的
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