Android O 将整个 Android 操作系统拆分为通用分区 (system.img) 和特定于硬件的分区(vendor.img 和 odm.img)。受这种变更的影响,您必须从安装到系统分区的模块中移除条件式编译,而且此类模块现在必须在运行时确定系统配置(并根据相应配置采取不同的行为)。
1 综述
1.1 那为什么不使用系统属性?
Android考虑过使用系统属性,但发现了以下几个重大问题,例如:
l 值的长度受限:系统属性对其值的长度具有严格限制(92 个字节)。此外,由于这些限制已作为C宏直接提供给 Android 应用,增加长度会导致出现向后兼容性问题。
l 无类型支持:所有值本质上都是字符串,而API仅仅是将字符串解析为int或bool。其他复合数据类型(数组、结构体等)应由客户端进行编码/解码(例如,“aaa,bbb,ccc”可以解码为由三个字符串组成的数组)。
l 覆盖:由于只读系统属性是以一次写入属性的形式实现的,因此如果供应商/原始设计制造商ODM)想要覆盖AOSP定义的只读值,则必须先于AOSP定义的只读值导入自己的只读值,而这反过来又会导致供应商定义的可重写值被 AOSP 定义的值所覆盖。
l 地址空间要求:系统属性在每个进程中都会占用较大的地址空间。系统属性在prop_area单元中以128KB的固定大小进行分组,即使目前只访问该单元中的一个系统属性,其中的所有属性也将会分配到进程地址空间。这可能会导致对地址空间需求较高的32位设备出现问题。
Google曾尝试在不牺牲兼容性的情况下克服这些限制,但依然会担心系统属性的设计不支持访问只读配置项。最终,Google判定系统属性更适合在所有Android中实时共享一些动态更新内容,因此需要采用一个专用于访问只读配置项的新系统。
1.2 ConfigStore HAL设计
基本设计很简单:
图 1. ConfigStore HAL 设计
l 以 HIDL 描述编译标记(目前用于对框架进行条件式编译)。
l 供应商和原始设备制造商(OEM)通过实现HAL服务为编译标记提供SoC和设备特定值。
l 修改框架,以使用 HAL 服务在运行时查找配置项的值。
当前由框架引用的配置项会包含在具有版本号的HIDL软件包 (android.hardware.configstore@1.0)中。供应商和/或原始设备制造商(OEM)通过实现此软件包中的接口为配置项提供值,而框架会在需要获取配置项的值时使用接口。
1.3 安全注意事项
当前由框架引用的配置项会包含在具有版本号的HIDL软件包 (android.hardware.configstore@1.0
)中。供应商和/或原始设备制造商(OEM)通过实现此软件包中的接口为配置项提供值,而框架会在需要获取配置项的值时使用接口。
2 创建HAL接口
您必须使用HIDL来描述用于对框架进行条件式编译的所有编译标记。相关编译标记必须分组并包含在单个.hal文件中。使用HIDL指定配置项具有以下优势:
l 可实施版本控制(为了添加新配置项,供应商/OEM 必须明确扩展 HAL)
l 记录详尽
l 可使用 SELinux 实现访问控制
l 可通过供应商测试套件对配置项进行全面检查(范围检查、各项内容之间的相互依赖性检查等)
l 在 C++ 和 Java 中自动生成 API
2.1 确定框架使用的编译标记
首先,请确定用于对框架进行条件式编译的编译标记,然后舍弃过时的配置以缩小编译标记集的范围。例如,下列编译标记集已确定用于surfaceflinger
:
TARGET_USES_HWC2(即将弃用)
TARGET_BOARD_PLATFORM
TARGET_DISABLE_TRIPLE_BUFFERING
TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
NUM_FRAMEBUFFER_SURFACE_BUFFERS
TARGET_RUNNING_WITHOUT_SYNC_FRAMEWORK
VSYNC_EVENT_PHASE_OFFSET_NS
SF_VSYNC_EVENT_PHASE_OFFSET_NS(即将弃用)
PRESENT_TIME_OFFSET_FROM_VSYNC_NS
MAX_VIRTUAL_DISPLAY_DIMENSION
2.2 创建 HAL 接口
子系统的编译配置是通过 HAL 接口访问的,而用于提供配置值的接口会在 HAL 软件包android.hardware.configstore(目前为1.0版)中进行分组。例如,要为 surfaceflinger 创建HAL接口文件,请在hardware/interfaces/configstore/1.0/ ISurfaceFlingerConfigs.hal 中运行以下命令:
package android.hardware.configstore@1.0; interface ISurfaceFlingerConfigs { // TO-BE-FILLED-BELOW };
创建.hal
文件后,请运行hardware/interfaces/update-makefiles.sh
以将新的.hal
文件添加到Android.bp
和Android.mk
文件中。
2.3 为编译标记添加函数
对于每个编译标记,请向相应接口各添加一个新函数。例如,在 hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal 中运行以下命令:
interface ISurfaceFlingerConfigs { disableTripleBuffering() generates(OptionalBool ret); forceHwcForVirtualDisplays() generates(OptionalBool ret); enum NumBuffers: uint8_t { USE_DEFAULT = 0, TWO = 2, THREE = 3, }; numFramebufferSurfaceBuffers() generates(NumBuffers ret); runWithoutSyncFramework() generates(OptionalBool ret); vsyncEventPhaseOffsetNs generates (OptionalUInt64 ret); presentTimeOffsetFromSyncNs generates (OptionalUInt64 ret); maxVirtualDisplayDimension() generates(OptionalInt32 ret); };
添加函数时,请注意以下事项:
l 采用简洁的名称。请避免将 makefile 变量名称转换为函数名称,并切记 TARGET_ 和 BOARD_ 前缀不再是必需的。
l 添加注释。帮助开发者了解配置项的用途,配置项如何改变框架行为、有效值等。
函数返回类型可以是 Optional[Bool|String|Int32|UInt32|Int64|UInt64]。类型会在同一目录中的 types.hal 中进行定义,并使用字段(可表明原始值是否是由 HAL 指定)来封装原始值;如果原始值不是由 HAL 指定,则使用默认值。
struct OptionalString { bool specified; string value; };
在适当的情况下,请定义最能代表配置项类型的枚举,并将该枚举用作返回类型。在上述示例中,NumBuffers 枚举会被定义为限制有效值的数量。在定义这类自定义数据类型时,请添加字段或枚举值(例如 USE_DEFAULT)来表示该值是否由 HAL 指定。
在 HIDL 中,单个编译标记并不一定要变成单个函数。模块所有者也可以将密切相关的编译标记汇总为一个结构体,并通过某个函数返回该结构体(这样做可以减少函数调用的次数)。
例如,用于在 hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal 中将两个编译标记汇总到单个结构体的选项如下:
interface ISurfaceFlingerConfigs { // other functions here struct SyncConfigs { OptionalInt64 vsyncEventPhaseoffsetNs; OptionalInt64 presentTimeoffsetFromSyncNs; }; getSyncConfigs() generates (SyncConfigs ret); // other functions here };
2.4 单个 HAL 函数的替代函数
作为针对所有编译标记使用单个 HAL 函数的替代函数,HAL 接口还提供了 getBoolean(string key) 和 getInteger(string key) 等简单函数。实际的 key=value 对会存储在单独的文件中,而 HAL 服务会通过读取/解析这些文件来提供值。
虽然这种方法很容易定义,但它不具备 HIDL 提供的优势(强制实施版本控制、便于记录、实现访问控制),因此不推荐使用。
注意:在使用简单函数时,几乎不可能实现访问控制,因为 HAL 自身无法识别客户端。
2.5 单个接口与多个接口
面向配置项设计的 HAL 接口提供了以下两种选择:
l 单个接口;涵盖所有配置项
l 多个接口;每个接口分别涵盖一组相关配置项
单个接口更易于使用,但随着更多的配置项添加到单个文件中,单个接口可能会越来越难以维护。此外,由于访问控制不够精细,获得接口访问权限的进程可能会读取所有配置项(无法授予对部分配置项的访问权限)。此外,如果未授予访问权限,则无法读取任何配置项。
由于存在这些问题,Android 会针对一组相关配置项将多个接口与单个 HAL 接口搭配使用。例如,对surfaceflinger相关配置项使用 ISurfaceflingerConfigs,对蓝牙相关配置项使用 IBluetoothConfigs 等等。
3 实现服务
为了准备 HAL 实现,您可以先生成基本的 configstore 接口代码,然后再对其进行修改以满足自己的需求。
3.1 生成接口代码
要为接口生成样板代码,请运行 hidl-gen。 例如,要为 surfaceflinger 生成代码,请运行以下命令:
hidl-gen -o hardware/interfaces/configstore/1.0/default \ -Lc++-impl \ -randroid.hardware:hardware/interfaces \ -randroid.hidl:system/libhidl/transport \ android.hardware.config@1.0::ISurfaceFlingerConfigs
注意:请勿使用 -Landroidbp-impl 运行 hidl-gen,因为这会生成 Android.bp。该模块必须通过 Android.mk 进行编译才能访问编译标记。
3.2 修改 Android.mk
接下来,请修改 Android.mk 文件,以便将实现文件 (<modulename>Configs.cpp) 添加到 LOCAL_SRC_FILES 并将编译标记映射到宏定义中。例如,您可以在 hardware/interface/configstore/1.0/default/Android.mk 中运行以下命令来修改 surfaceflinger:
LOCAL_SRC_FILES += SurfaceFlingerConfigs.cpp ifneq ($(NUM_FRAMEBUFFER_SURFACE_BUFFERS),) LOCAL_CFLAGS += -DNUM_FRAMEBUFFER_SURFACE_BUFFERS=$(NUM_FRAMEBUFFER_SURFACE_BUFFERS) endif ifeq ($(TARGET_RUNNING_WITHOUT_SYNC_FRAMEWORK),true) LOCAL_CFLAGS += -DRUNNING_WITHOUT_SYNC_FRAMEWORK endif
如果Android.mk包含几个ifeq-endif块,请考虑将代码移动到新文件(即 surfaceflinger.mk)中,然后从 Android.mk 中引用该文件。
3.3 实现函数
要填充函数以实现 HAL,请以不同的值回调_hidl_cb函数(以编译标记为条件)。例如,您可以在hardware/interfaces/configstore/1.0/default/SurfaceFlingerConfigs.cpp 中填充surfaceflinger的函数:
Return<void> SurfaceFlingerConfigs::numFramebufferSurfaceBuffers( numFramebufferSurfaceBuffers_cb _hidl_cb) { #if NUM_FRAMEBUFFER_SURFACE_BUFFERS 2 _hidl_cb(NumBuffers.TWO); #else if NUM_FRAMEBUFFER_SURFACE_BUFFERS 3 _hidl_cb(NumBuffers.THREE); #else _hidl_cb(NumBuffers.USE_DEFAULT); #endif } Return<void> SurfaceFlingerConfigs::runWithoutSyncFramework( runWithoutSyncFramework_cb _hidl_cb) { #ifdef RUNNING_WITHOUT_SYNC_FRAMEWORK _hidl_cb({true /* specified */, true /* value */}); #else // when macro not defined, we can give any value to the second argument. // It will simply be ignored in the framework side. _hidl_cb({false /* specified */, false /* value */}); #endif }
请确保该实现不包含名为 HIDL_FETCH_<interface name> 的函数(例如 HIDL_FETCH_ISurfaceFlingerConfigs)。这是 HIDL 直通模式所需的函数,configstore 不使用(且被禁止使用)该函数。ConfigStore必须始终在绑定模式下运行。
3.4 注册为服务
最后,将所有接口实现注册为 configstore 服务。例如,您可以在 hardware/interfaces/configstore/1.0/default/service.cpp 中注册 surfaceflinger 实现:
configureRpcThreadpool(maxThreads, true); sp<ISurfaceFlingerConfigs> surfaceFlingerConfigs = new SurfaceFlingerConfigs; status_t status = surfaceFlingerConfigs->registerAsService(); sp<IBluetoothConfigs> bluetoothConfigs = new BluetoothConfigs; status = bluetoothConfigs->registerAsService(); // register more interfaces here joinRpcThreadpool();
3.5 确保可尽早访问
为了确保框架模块可以尽早访问HAL 服务,config HAL服务应该在 hwservicemanager 准备就绪之后尽早启动。由于配置 HAL 服务不会读取外部文件,因此在启动之后预计很快就能准备就绪。
4 客户端使用情况
您可以重构经过条件式编译的代码,以便从 HAL 接口动态读取值。例如:
#ifdef TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS //some code fragment #endif
随后,框架代码便可以调用一个在 <configstore/Utils.h> 中定义的适当效用函数(根据其类型)。
4.1 ConfigStore 示例
以下示例显示了读取 TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS(在 ConfigStore HAL 中定义为 forceHwcForVirtualDisplays(),返回类型为 OptionalBool)的情形:
#include <configstore/Utils.h> using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; static bool vsyncPhaseOffsetNs = getBool<ISurfaceFlingerConfigs, ISurfaceFlingerConfigs::forceHwcForVirtualDisplays>(false);
效用函数(上例中的 getBool
)会与 configstore
服务进行通信以获取接口函数代理的句柄,然后通过 HIDL/hwbinder 来调用句柄,从而检索该值。
4.2 效用函数
<configstore/Utils.h> (configstore/1.0/include/configstore/Utils.h) 会为每个原始返回类型(包括 Optional[Bool|String|Int32|UInt32|Int64|UInt64])提供效用函数,如下所示:
类型 |
函数(已省略模板参数) |
OptionalBool |
bool getBool(const bool defValue) |
OptionalInt32 |
int32_t getInt32(const int32_t defValue) |
OptionalUInt32 |
uint32_t getUInt32(const uint32_t defValue) |
OptionalInt64 |
int64_t getInt64(const int64_t defValue) |
OptionalUInt64 |
uint64_t getUInt64(const uint64_t defValue) |
OptionalString |
std::string getString(const std::string &defValue) |
defValue是在 HAL 实现没有为配置项指定值时返回的默认值。每个函数都需要使用两个模板参数:
l 接口类名称。
l Func. 用于获取配置项的成员函数指针。
由于配置值是只读属性且不会发生更改,因此效用函数会在内部缓存配置值。使用同一链接单元中的缓存值可以更有效地执行后续调用。
4.3 使用 configstore-utils
ConfigStore HAL 旨在向前兼容次要版本升级,这意味着当 HAL 进行升级并且某些框架代码使用新引入的项时,您仍然可以使用 /vendor 中旧的次要版本的 ConfigStore 服务。
为了实现向前兼容性,请确保在实现过程中遵循以下准则:
1) 当只有旧版服务可用时,新项使用默认值。例如:
service = V1_1::IConfig::getService(); // null if V1_0 is installed value = DEFAULT_VALUE; if(service) { value = service->v1_1API(DEFAULT_VALUE); }
2) 客户端使用引入 ConfigStore 项的最早的接口。例如:
V1_1::IConfig::getService()->v1_0API(); // NOT ALLOWED V1_0::IConfig::getService()->v1_0API(); // OK
3) 可以为旧版接口检索新版服务。在以下示例中,如果已安装版本为 v1_1,则必须为 getService() 返回 v1_1 服务:
V1_0::IConfig::getService()->v1_0API();
当configstore-utils库中的访问函数用于访问ConfigStore项时,#1由实现保证,#2由编译器错误保证。基于这些原因,我们强烈建议尽量使用configstore-utils。
5 添加 ConfigStore 类和项
您可以为现有接口类添加新的 ConfigStore 项(即接口方法)。如果您未定义接口类,则必须先添加一个新类,然后才能为该接口类添加 ConfigStore 项。本部分使用 disableInitBlank 配置项示例来演示将 healthd 添加到 IChargerConfigs 接口类的过程。
5.1 添加接口类
如果您没有为要添加的接口方法定义接口类,则必须先添加接口类,然后才能添加相关联的 ConfigStore 项。
1) 创建 HAL 接口文件。ConfigStore 版本为 1.0,因此请在 hardware/interfaces/configstore/1.0 中定义 ConfigStore 接口。例如,在 hardware/interfaces/configstore/1.0/IChargerConfigs.hal 中运行以下命令:
package android.hardware.configstore@1.0; interface IChargerConfigs { // TO-BE-FILLED-BELOW };
2) 为 ConfigStore 共享库和标头文件更新 Android.bp 和 Android.mk,以包含新的接口 HAL。例如:
hidl-gen -o hardware/interfaces/configstore/1.0/default -Lmakefile -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.configstore@1.0::IChargerConfigs hidl-gen -o hardware/interfaces/configstore/1.0/default -Landroidbp -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.configstore@1.0::IChargerConfigs
这些命令可在 hardware/interfaces/configstore/1.0 中更新 Android.bp 和 Android.mk。
3) 生成用于实现服务器代码的 C++ 存根。例如:
hidl-gen -o hardware/interfaces/configstore/1.0/default -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.configstore@1.0::IChargerConfigs
此命令可在 hardware/interfaces/configstore/1.0/default 中创建两个文件:ChargerConfigs.h 和 ChargerConfigs.cpp。
4) 打开 .h 和 .cpp 实现文件,并移除与函数 HIDL_FETCH_name(例如 HIDL_FETCH_IChargerConfigs)相关的代码。这是 HIDL 直通模式所需的函数,ConfigStore 不使用该模式。
5) 将实现注册为 ConfigStore 服务。例如,在 hardware/interfaces/configstore/1.0/default/service.cpp 中运行以下命令:
#include <android/hardware/configstore/1.0/IChargerConfigs.h> #include "ChargerConfigs.h" using android::hardware::configstore::V1_0::IChargerConfigs; using android::hardware::configstore::V1_0::implementation::ChargerConfigs; int main() { ... // other code sp<IChargerConfigs> chargerConfigs = new ChargerConfigs; status = chargerConfigs->registerAsService(); LOG_ALWAYS_FATAL_IF(status != OK, "Could not register IChargerConfigs"); ... // other code }
6) 修改 Android.mk 文件,以便将实现文件 (modulenameConfigs.cpp) 添加到 LOCAL_SRC_FILES 并将编译标记映射到宏定义中。例如,在 hardware/interfaces/configstore/1.0/default/Android.mk 中运行以下命令:
LOCAL_SRC_FILES += ChargerConfigs.cpp ifeq ($(strip $(BOARD_CHARGER_DISABLE_INIT_BLANK)),true) LOCAL_CFLAGS += -DCHARGER_DISABLE_INIT_BLANK endif
7) (可选)添加清单项。如果清单项不存在,则默认添加 ConfigStore 的“default”实例名称。例如,在 device/google/marlin/manifest.xml 中运行以下命令:
<hal format="hidl"> <name>android.hardware.configstore</name> ... <interface> <name>IChargerConfigs</name> <instance>default</instance> </interface> </hal>
8) 视需要(即,如果客户端没有向 hal_configstore 进行 hwbinder 调用的权限)添加 sepolicy 规则。例如,在 system/sepolicy/private/healthd.te 中运行以下命令:
... // other rules binder_call(healthd, hal_configstore)
5.2 添加新的 ConfigStore 项
要添加新的 ConfigStore 项,请执行以下操作:
1) 打开 HAL 文件,并为该项添加所需的接口方法(ConfigStore 的 .hal 文件位于 hardware/interfaces/configstore/1.0 中)。例如,在 hardware/interfaces/configstore/1.0/IChargerConfigs.hal 中运行以下命令:
package android.hardware.configstore@1.0; interface IChargerConfigs { ... // Other interfaces disableInitBlank() generates(OptionalBool value); };
2) 在相应的接口 HAL 实现文件(.h 和 .cpp)中实现该方法。将默认实现放置在 hardware/interfaces/configstore/1.0/default 中。
注意:使用 -Lc++-impl 运行 hidl-gen 将为新添加的接口方法生成框架代码。不过,由于该方法也会覆盖所有现有接口方法的实现,因此请酌情使用 -o 选项。
例如,在 hardware/interfaces/configstore/1.0/default/ChargerConfigs.h 中运行以下命令:
struct ChargerConfigs : public IChargerConfigs { ... // Other interfaces Return<void> disableInitBlank(disableInitBlank_cb _hidl_cb) override; };
在hardware/interfaces/configstore/1.0/default/ChargerConfigs.cpp中运行以下命令:
Return<void> ChargerConfigs::disableInitBlank(disableInitBlank_cb _hidl_cb) { bool value = false; #ifdef CHARGER_DISABLE_INIT_BLANK value = true; #endif _hidl_cb({true, value}); return Void(); }
5.3 使用ConfigStore项
要使用 ConfigStore 项,请执行以下操作:
1) 添加所需的标头文件。例如,在 system/core/healthd/healthd.cpp 中运行以下命令:
#include <android/hardware/configstore/1.0/IChargerConfigs.h> #include <configstore/Utils.h>
2) 使用 android.hardware.configstore-utils 中相应的模板函数访问 ConfigStore 项。例如,在 system/core/healthd/healthd.cpp 中运行以下命令:
using namespace android::hardware::configstore; using namespace android::hardware::configstore::V1_0; static int64_t disableInitBlank = getBool< IChargerConfigs, &IChargerConfigs::disableInitBlank>(false);
在本例中,系统检索了 ConfigStore 项 disableInitBlank 并将其存储到某个变量中(在需要多次访问该变量时,这样做非常有帮助)。从 ConfigStore 检索的值会缓存到实例化的模板函数内,这样系统就可以快速从缓存值中检索到该值,而无需与 ConfigStore 服务通信以便稍后调用实例化的模板函数。
3) 在 Android.mk 或 Android.bp 中添加对 ConfigStore 和 configstore-utils 库的依赖关系。例如,在 system/core/healthd/Android.mk 中运行以下命令:
LOCAL_SHARED_LIBRARIES := \ android.hardware.configstore@1.0 \ android.hardware.configstore-utils \ ... (other libraries) \