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) \