在Android系统开发中,经常会遇到需要动态修改系统属性的情况。尤其是对于那些标记为只读的属性,修改它们可能会比较困难。本文将以RK3568 Android11为例,介绍如何动态替换任意的ro
属性以及在此过程中遇到的问题和解决方案。
需求背景
在Android系统中,属性经常用于配置信息,例如设备型号、硬件版本等。有时,为了适应不同的硬件或软件需求,我们可能需要在系统运行时动态修改这些属性,而不是重新编译整个系统。
方案思路
我的解决方案是在系统启动时,加载一个自定义的属性文件,该文件中的属性将覆盖系统中已存在的属性。这样,我们就可以在不修改系统源代码的情况下,动态地修改任意属性。
Android 11的Property加载流程
- 启动init进程:Android系统启动时,首先启动的是
init
进程。这是系统的第一个进程,负责初始化系统环境和启动其他系统服务。 - 加载默认属性:
init
进程首先加载/system/etc/prop.default
文件中的属性。这些属性通常包括硬件和系统的基本配置。
if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) { // Try recovery path if (!load_properties_from_file("/prop.default", nullptr, &properties)) { // Try legacy path load_properties_from_file("/default.prop", nullptr, &properties); } }
- 加载其他属性文件:除了默认属性文件,
init
进程还会加载其他多个属性文件,如/system/build.prop
、/vendor/build.prop
等。这些文件中的属性可以覆盖默认属性文件中的属性。
load_properties_from_file("/system/build.prop", nullptr, &properties); load_properties_from_file("/vendor/build.prop", nullptr, &properties);
- 加载设备特定属性:为了支持多种硬件配置,Android 11引入了设备特定的属性文件,如
/odm/build.prop
、/product/build.prop
等。这些文件中的属性可以根据具体的硬件配置进行定制。
load_properties_from_file("/odm/build.prop", nullptr, &properties); load_properties_from_file("/product/build.prop", nullptr, &properties);
- 属性覆盖:在加载属性时,后加载的属性会覆盖先加载的属性。这意味着,如果多个属性文件中都定义了同一个属性,那么最后加载的文件中的属性值将被使用。
为什么自定义属性可以覆盖默认属性?
这是因为Android的属性加载机制是基于后加载的属性覆盖先加载的属性的原则设计的。这种设计允许开发者和OEM厂商在不修改系统源代码的情况下,通过添加或修改属性文件来定制系统配置。
例如,我们可以在/product/customize.prop
中定义一个属性ro.product.system.model=rk3568_custom
。由于这个文件是在其他属性文件之后加载的,所以这个属性会覆盖在之前的文件中定义的同名属性。
实现步骤:
- 选择自定义属性文件的位置:考虑到系统的启动顺序和分区挂载情况,我们选择将自定义属性文件放在
/product
分区,因为这个分区在系统的早期启动阶段就已经被挂载,所以init
进程可以在启动时访问它。我们将自定义属性文件命名为customize.prop
。 - 修改属性加载代码:在
system/core/init/property_service.cpp
文件中,找到PropertyLoadBootDefaults
函数。在该函数中,我们在加载其他属性文件之后,添加了加载我们的自定义属性文件的代码。
void PropertyLoadBootDefaults() { // TODO(b/117892318): merge prop.default and build.prop files into one // We read the properties and their values into a map, in order to always allow properties // loaded in the later property files to override the properties in loaded in the earlier // property files, regardless of if they are "ro." properties or not. std::map<std::string, std::string> properties; if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) { // Try recovery path if (!load_properties_from_file("/prop.default", nullptr, &properties)) { // Try legacy path load_properties_from_file("/default.prop", nullptr, &properties); } } load_properties_from_file("/system/build.prop", nullptr, &properties); load_properties_from_file("/system_ext/build.prop", nullptr, &properties); load_properties_from_file("/vendor/default.prop", nullptr, &properties); load_properties_from_file("/vendor/build.prop", nullptr, &properties); if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) { load_properties_from_file("/odm/etc/build.prop", nullptr, &properties); } else { load_properties_from_file("/odm/default.prop", nullptr, &properties); load_properties_from_file("/odm/build.prop", nullptr, &properties); } load_properties_from_file("/product/build.prop", nullptr, &properties); load_properties_from_file("/factory/factory.prop", "ro.*", &properties); if (access(kDebugRamdiskProp, R_OK) == 0) { LOG(INFO) << "Loading " << kDebugRamdiskProp; load_properties_from_file(kDebugRamdiskProp, nullptr, &properties); } // 加载自定义的prop文件 start //const char* customPropPath = "/mnt/private/customize.prop"; const char* customPropPath = "/product/customize.prop"; if (access(customPropPath, R_OK) == 0) { LOG(INFO) << "[custom_prop] Loading " << customPropPath; load_properties_from_file(customPropPath, nullptr, &properties); } else { LOG(INFO) << customPropPath << " not found, skipping.[custom_prop]"; } // 加载自定义的prop文件 end for (const auto& [name, value] : properties) { std::string error; if (PropertySet(name, value, &error) != PROP_SUCCESS) { LOG(ERROR) << "Could not set '" << name << "' to '" << value << "' while loading .prop files" << error; } } property_initialize_ro_product_props(); property_derive_build_fingerprint(); update_sys_usb_config(); }
如何测试:
我们在customize.prop
文件中设置了ro.product.system.model=rk3568_custom
。启动系统后,我们使用getprop ro.product.system.model
命令,确实得到了rk3568_custom
,这证明我们的修改是成功的。
- 属性验证:使用
getprop
命令验证属性是否已被正确加载。 - 系统功能测试:确保修改后的系统可以正常启动,并运行各种应用程序。
- 日志检查:通过
logcat
命令,检查系统日志,确保没有与属性加载相关的错误或警告。
遇到的问题:
- 分区挂载问题:最初,我们尝试将自定义属性文件放在
/mnt/private
分区。但在系统启动早期,该分区尚未挂载,导致init
进程无法访问。因此,我们选择了/product
分区。 - 属性覆盖问题:由于属性加载的顺序,后加载的属性会覆盖先加载的属性。因此,我们确保自定义属性文件是在其他属性文件之后加载的。
结论
通过上述方法,我们成功地实现了在RK3568 Android12系统中动态替换任意的ro
属性。这为Android系统开发提供了更大的灵活性,使我们能够更容易地适应不同的硬件和配置需求。