Android系统属性和设置项读取

2022-11-01  本文已影响0人  文泰ChrisTwain

一、系统属性值

(1)系统属性定义了Android 系统的一些公共系统属性,都是字符串格式,可用于业务代码中区分不同机型、系统版本等

业务代码中可读取属性值后进行特定业务逻辑,比如区分手机或者平板设备等,/system/build.prop文件能看到部分系统属性,可通过adb shell getprop查看全部系统属性,相关命令如下:
adb shell getprop vendor.xxx.xxxadb查看特定属性值
adb shell setprop [key] [value]设置指定key的属性值
watchprops命令可监听系统属性的变化
这三个命令都是toolbox的子命令,对应的源码:system/core/toolbox/
部分属性举例:

getprop ro.serialno 序列号
getprop ro.carrier CID号
getprop ro.hardware 板子代号
getprop ro.bootloader SPL(Hboot)版本号
getprop ro.build.version.sdk Android版本号
getprop ro.build.characteristics 如果是平板属性值为tablet,可用于区分手机和平板

ps.只有有权限的进程才能修改属性

系统属性的四个优点,当然缺点也很明显,只能支持三种基本类型:string、int、boolean
1)全局:只要拥有对应的权限,就可以同步获取和修改;
2)通用:在Java层,native层,shell层都可以获取和修改;
3)初始化早:属性服务实在 init 进程中启动的;
4)简单:主要就两个方法 set 和 get

(2)主要属性简介:
(3)Java反射读取系统属性
private static final String CLASS_SYSTEM_PROPERTIES = "android.os.SystemProperties";

/**
 * 获取get prop 字符串类型配置值(SystemProperties读取的配置)
 *
 * @param cfgName 配置名
 * @param defValue 获取不到的默认值
 * @return 成功返回实际的值,识别返回默认值
 */
public static String getStringOfRoConfig(String cfgName, String defValue) {
    Object result = invoke(CLASS_SYSTEM_PROPERTIES, "get", new Class<?>[] {String.class, String.class},
            cfgName, defValue);
    if (result instanceof String) {
        return (String) result;
    }
    return defValue;
}

/**
 * 反射调用指定类中的指定方法
 *
 * @param className 方法所在的类名
 * @param methodName 方法名
 * @param parameterTypes 可变参数class类型数组,注意args要和其一致
 * @param args 方法可变参数
 * @return 方法调用返回的结果
 */
public static Object invoke(String className, String methodName, Class<?>[] parameterTypes, Object... args) {
    Object value = null;

    try {
        Class<?> clz = Class.forName(className);
        Method method = clz.getDeclaredMethod(methodName, parameterTypes);
        value = method.invoke(clz, args);
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
            | SecurityException | ClassNotFoundException e) {
        Logger.e(TAG, "invoke " + e.getClass().getSimpleName());
    } catch (Exception e) {
        Logger.e(TAG, "unknown Exception in invoke");
    }
    return value;
}
(4)Native层C++读取属性值

NDKndk\21.0.6113669\toolchains\llvm\prebuilt\windows-x86_64\sysroot\usr\include\bits路径下get_device_api_level_inlines.h代码

// Avoid circular dependencies since this is exposed from <sys/cdefs.h>.
int __system_property_get(const char* __name, char* __value);
int atoi(const char* __s) __attribute_pure__;

__BIONIC_GET_DEVICE_API_LEVEL_INLINE int android_get_device_api_level() {
  char value[92] = { 0 };
  if (__system_property_get("ro.build.version.sdk", value) < 1) return -1;
  int api_level = atoi(value);
  return (api_level > 0) ? api_level : -1;
}

故可通过__system_property_get函数读取系统属性,同时还提供了获取android版本的属性例子方法android_get_device_api_level
这里char value[92]设置为92是由于属性key的最大字符长度为31,value最大字符长度为91,可看到源码中有如下限制:

public static final int PROP_NAME_MAX = 31;
public static final int PROP_VALUE_MAX = 91;

......

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);  // 调用Native方法设置属性
}

二、Android设置项开关值

(1)设置App中的各个开关值,存储在/data/system/users/0/下的三个文件中,这些文件在手机中有被加密,具体路径如下:

/data/system/users/0/settings_global.xml
/data/system/users/0/settings_secure.xml
/data/system/users/0/settings_system.xml
业务中可读取或者监听开关变化进行自身业务逻辑,App有写设置的权限可往其中写开关值。比如业务可以自定义一个开关值判断,当开关通过adb打开时,执行一些测试逻辑。
adb shell settings put global intelliability_enabled 1 比如通过adb打开intelliability_enabled开关,开关在global文件中
adb shell settings get global intelliability_enabled 通过adb查看开关值

settings_global.xml文件中部分设置项示例如下:

<setting id="89" name="adb_wifi_enabled" value="0" package="android" defaultValue="0" defaultSysSet="true" />
<setting id="40" name="low_battery_sound_timeout" value="0" package="android" defaultValue="0" defaultSysSet="true" />
<setting id="32" name="car_undock_sound" value="/system/media/audio/ui/Undock.ogg" package="android" defaultValue="/system/media/audio/ui/Undock.ogg" defaultSysSet="true" />
<setting id="152" name="keyguard_occluded_notify" value="keyguard_no_occluded" package="com.android.systemui" defaultValue="keyguard_no_occluded" defaultSysSet="true" />
<setting id="169" name="captive_portal_notification_shown" value="0" package="android" defaultValue="0" defaultSysSet="true" />
<setting id="157" name="aps_display_resolution" value="2" package="com.huawei.android.hwaps" defaultValue="2" defaultSysSet="true" />

通过Java读取设置项的值,需要先知道设置项的名称和所在的文件

Settings.System.getInt(context.getContentResolver(), xxSettingName, 0);  // System表示存在于settings_system.xml中的开关
Settings.Global.getInt(context.getContentResolver(), xxSettingName, 0);
Settings.Secure.getInt(context.getContentResolver(), xxSettingName, 0);

ps.有些应用或以SDK的形式附加在其它使用此SDK的应用,获取到修改设置的权限后可能会写唯一标识到设置项文件中,使得用户设备被唯一标识,因为其它APP也能读取到这个值,故能用于精准广告推荐。
还有些设置开关是广播,比如位置信息开关,广播Aciton是 LocationManager.MODE_CHANGED_ACTION
收到广播后再根据LocationManager查询是否打开
LocationManager lm = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
boolean isEnabled = lm.isLocationEnabled();

(2)监听设置项开关值变化

根据上述,通过文件名+字段名方式构造URI,注册设置变化监听
content://settings/设置文件/文件中的具体设置值

Uri uri = Uri.parse("content://settings/secure/ui_night_mode");  // 监听深色模式开关
ContentObserver contentObserver = new ContentObserver(null) {
    @Override
    public void onChange(boolean isSelfChange, Uri uri) {
        if (uri == null) {
            Logger.e(TAG, "setting item uri is null");
            return;
        }
        Logger.d(TAG, "onChange uri = " + uri);
        // 可通过uri.toString()字符串中解析文件名和字段名,然后Settings.System.getInt()查询变化后的开关值
    }
};
context.getContentResolver().registerContentObserver(uri, true, contentObserver);

注意:不监听时需解注册内容观察者

context.getContentResolver().unregisterContentObserver(uriObserverStore.get(name));

参考:
Android属性系统简介及使用
Android系统属性set、get详解
Android source官网-添加系统属性

上一篇 下一篇

猜你喜欢

热点阅读