供應商原生開發套件 (VNDK)
供應商原生開發套件 (VNDK) 是專門用來讓供應商實現其 HAL 的一組庫。VNDK 包含在 system.img
中,並在運行時與供應商代碼動態關聯。
為何要使用 VNDK?
Android 8.0 及更高版本支持框架專用更新,在此類更新中,系統分區可以升級到最新版本,而供應商分區保持不變。這意味着在不同時間編譯的二進制文件必須能夠相互配合使用;VNDK 為 Android 版本迭代時發生的 API/ABI 變更所導致的問題提供了解決途徑。
框架專用更新面臨着以下挑戰:
- 框架模塊與供應商模塊之間的依賴關系。在 Android 8.0 之前的版本中,兩種模塊可以互相關聯。但是,來自供應商模塊的依賴關系對框架模塊開發施加了不必要的限制。
- AOSP 庫的擴展。Android 8.0 及更高版本要求所有 Android 設備在系統分區被替換為標准常規系統映像 (GSI) 時,都可以通過 CTS 測試。不過,當供應商擴展 AOSP 庫以提高性能或為其 HIDL 實現添加額外的功能時,使用標准 GSI 來刷寫系統分區可能會破壞供應商的 HIDL 實現(有關如何防止此類破壞的指南,請參閱 VNDK 擴展)。
為了克服這些挑戰,Android 8.0 引入了一些技術,例如 VNDK(本部分對其進行了介紹)、HIDL、hwbinder、設備樹疊加層和 sepolicy 疊加層。
VNDK 資源
本部分包含以下 VNDK 資源:
- VNDK 概念:(請參閱下文)介紹了框架共享庫、Same-Process HAL (SP-HAL) 和 VNDK 術語。
- VNDK 擴展:對專門針對供應商的更改進行了分類。例如,具有供應商模塊所依賴的擴展功能的庫必須復制到供應商分區中,但禁止進行 ABI 不兼容的更改。
- VNDK 編譯系統支持:介紹了與 VNDK 相關的編譯系統配置和模塊定義語法。
- VNDK 定義工具:可協助您將源代碼樹遷移到 Android 8.0 及更高版本。
- 鏈接器命名空間:提供對共享庫關聯的精細控制。
- 目錄、規則和 sepolicy:定義了搭載 Android 8.0 及更高版本的設備的目錄結構,以及 VNDK 規則和關聯的 sepolicy。
- VK 設計演示文稿:闡述了 Android 8.0 及更高版本中使用的 VDNK 基本概念。
VNDK 概念
在理想的 Android 8.0 及更高版本環境中,框架進程不加載供應商共享庫,所有供應商進程僅加載供應商共享庫(和一部分框架共享庫),而框架進程與供應商進程之間的通信由 HIDL 和硬件 binder 控制。
這樣的環境存在以下可能:來自框架共享庫的穩定、公共 API 可能不足以滿足供應商模塊開發者的需求(盡管 API 在不同的 Android 版本之間會有所變化),要防止出現這種情況,供應商進程需要能夠訪問一部分框架共享庫。此外,由於性能要求可能會導致折中方案,因此必須區別對待某些對響應時間要求嚴格的 HAL。
以下部分詳細介紹了 VNDK 如何處理適用於供應商的框架共享庫以及 Same-Process HAL (SP-HAL)。
適用於供應商的框架共享庫
本部分介紹了供應商進程可訪問的共享庫的分類標准。要讓供應商模塊在多個 Android 版本上皆可正常工作,有以下兩種方法:
- 讓框架共享庫的 ABI/API 保持穩定。新的框架模塊和舊的供應商模塊可以使用同一共享庫,以減少內存占用和存儲空間占用。此外,通過使用同一共享庫,還可以避免一些雙重加載問題。不過,保持穩定的 ABI/API 的開發成本很高,因此讓每個框架共享庫導出的所有 ABI/API 都保持穩定是不現實的。
- 復制舊的框架共享庫。此方法會嚴重限制邊信道,即在框架模塊與供應商模塊之間進行通信的所有機制,包括(但不限於)binder、套接字、管道、共享內存、共享文件和系統屬性。除非通信協議被凍結且保持穩定(例如通過 hwbinder 的 HIDL),否則不能進行通信。雙重加載共享庫也可能會導致出現問題;例如,如果將新庫創建的對象傳遞到舊庫的函數中,則可能會出錯,因為這些庫可能會以不同的方式解讀該對象。
根據共享庫的特性不同,使用的方法也有差異。因此,框架共享庫可分為以下三個子類別:
- LL-NDK 庫是已知穩定的框架共享庫。它們的開發者致力於保持其 API/ABI 穩定性。
- LL-NDK 包含以下庫:
libEGL.so
、libGLESv1_CM.so
、libGLESv2.so
、libGLESv3.so
、libandroid_net.so
、libc.so
、libdl.so
、liblog.so
、libm.so
、libnativewindow.so
、libneuralnetworks.so
、libsync.so
、libvndksupport.so
和libvulkan.so
。
- LL-NDK 包含以下庫:
- 符合條件的 VNDK 庫 (VNDK) 是指可以安全復制兩次的框架共享庫。框架模塊和供應商模塊可以與其各自的庫副本相關聯。框架共享庫只有滿足以下條件才能成為符合條件的 VNDK 庫:
- 不向框架發送或從框架接收 IPC。
- 與 ART 虛擬機無關。
- 不讀取/寫入文件格式不穩定的文件/分區。
- 沒有需要法律審查的特殊軟件許可。
- 其代碼所有者不反對供應商使用該庫。
- 框架專用庫 (FWK-ONLY) 是指不屬於上述類別的框架共享庫。此類庫具有以下特點:
- 被視為框架內部實現細節。
- 不得由供應商模塊訪問。
- 具有不穩定的 ABI/API,無 API/ABI 兼容性保證。
- 不會被復制。
Same-Process HAL (SP-HAL)
Same-Process HAL (SP-HAL) 是一組預先確定的 HAL,作為供應商共享庫進行實現,並被加載到框架進程中。SP-HAL 由鏈接器命名空間(控制共享庫可見的庫和符號)進行隔離。SP-HAL 必須僅依賴於 LL-NDK 和 VNDK-SP。
VNDK-SP 是一部分預定義的符合條件的 VNDK 庫。我們會仔細審查 VNDK-SP 庫,以確保將 VNDK-SP 庫雙重加載到框架進程中不會導致問題。SP-HAL 和 VNDK-SP 均由 Google 定義。
以下庫是經過批准的 SP-HAL:
libGLESv1_CM_${driver}.so
libGLESv2_${driver}.so
libGLESv3_${driver}.so
libEGL_${driver}.so
vulkan.${driver}.so
android.hardware.renderscript@1.0-impl.so
android.hardware.graphics.mapper@2.0-impl.so
以下庫是 SP-HAL 可以訪問的 VNDK-SP 庫:
android.hardware.graphics.common@1.0.so
android.hardware.graphics.mapper@2.0.so
android.hardware.renderscript@1.0.so
(Renderscript)libRS_internal.so
(Renderscript)libbase.so
libc++.so
libcutils.so
libhardware.so
libhidlbase.so
libhidltransport.so
libhwbinder.so
libion.so
libutils.so
libz.so
以下 VNDK-SP 依賴項 (VNDK-SP-Private) 對 SP-HAL 來說是不可見的:
libRSCpuRef.so
(Renderscript)libRSDriver.so
(Renderscript)libbacktrace.so
libblas.so
(Renderscript)libbcinfo.so
(Renderscript)liblzma.so
libunwind.so
以下是具有 RS 例外的框架專用庫 (FWK-ONLY-RS):
libft2.so
(Renderscript)libmediandk.so
(Renderscript)
VNDK 術語
- 模塊是指共享庫或可執行文件。
- 進程是指可執行文件產生的操作系統任務。
- 以“框架”打頭的術語是指與系統分區相關的概念。
- 以“供應商”打頭的術語是指與供應商分區相關的概念。
例如:
- 框架可執行文件是指
/system/bin
或/system/xbin
中的可執行文件。 - 框架共享庫是指
/system/lib[64]
下的共享庫。 - 框架模塊是指框架共享庫和框架可執行文件。
- 框架進程是指從框架可執行文件衍生而來的進程(例如
/system/bin/app_process
)。 - 供應商可執行文件是指
/vendor/bin
中的可執行文件。 - 供應商共享庫是指
/vendor/lib[64]
下的共享庫。 - 供應商模塊是指供應商可執行文件和供應商共享庫。
- 供應商進程是指供應商可執行文件(例如
/vendor/bin/android.hardware.camera.provider@2.4-service
-
)產生的進程。
VNDK 版本控制
在 Android 9 中,VNDK 共享庫帶有版本編號:
ro.vndk.version
系統屬性將自動添加到/vendor/default.prop
。- VNDK 共享庫將安裝到
/system/lib[64]/vndk-${ro.vndk.version}
中。 - VNDK-SP 共享庫將安裝到
/system/lib[64]/vndk-sp-${ro.vndk.version}
中。 - 動態鏈接器配置文件將安裝到
/system/etc/ld.config.${ro.vndk.version}.txt
中。
系統將按以下算法選擇 ro.vndk.version
的值:
- 如果
BOARD_VNDK_VERSION
不等於current
,則使用BOARD_VNDK_VERSION
。 - 如果
BOARD_VNDK_VERSION
等於current
: - 如果
PLATFORM_VERSION_CODENAME
為REL
,則使用PLATFORM_SDK_VERSION
(例如28
)。 - 否則使用
PLATFORM_VERSION_CODENAME
(例如P
)。
升級設備
如果 Android 8.x 設備停用了 VNDK 運行時增強功能(即,編譯時未使用 BOARD_VNDK_VERSION
或使用了 BOARD_VNDK_RUNTIME_DISABLE
),則在升級到 Android 9 時,可能會將 PRODUCT_USE_VNDK_OVERRIDE := false
添加到 BoardConfig.mk
。
如果 PRODUCT_USE_VNDK_OVERRIDE
為 false
,則 ro.vndk.lite
屬性將自動添加到 /vendor/default.prop
,且其值將為 true
。因此,動態鏈接器將加載 /system/etc/ld.config.vndk_lite.txt
中的鏈接器命名空間配置,這僅會隔離 SP-HAL 和 VNDK-SP。
要將 Android 7.0 或更低版本的設備升級到 Android 9,請將 PRODUCT_TREBLE_LINKER_NAMESPACES_OVERRIDE := false
添加到 BoardConfig.mk
。
供應商測試套件 (VTS)
Android 9 供應商測試套件 (VTS) 強制要求存在非空 ro.vndk.version
屬性。新發布的設備和升級設備都必須定義 ro.vndk.version
。一些 VNDK 測試用例(例如 VtsVndkFilesTest
和 VtsVndkDependencyTest
)依賴於 ro.vndk.version
屬性來加載符合條件且匹配的 VNDK 庫數據集。
如果 ro.product.first_api_level
屬性大於 27,則不能定義 ro.vndk.lite
屬性。 如果在新推出的 Android 9 設備中定義了 ro.vndk.lite
,則 VtsTreblePlatformVersionTest
將失敗。
文檔歷史記錄
本部分跟蹤了對 VNDK 文檔進行的更改。
Android 9 的變化
- 添加了“VNDK 版本編號”部分。
- 添加了“VTS”部分。
- 更改了部分 VNDK 類別的名稱:
- LL-NDK-Indirect 已改名為 LL-NDK-Private。
- VNDK-Indirect 已改名為 VNDK-Private。
- VNDK-SP-Indirect-Private 已改名為 VNDK-SP-Private。
- 移除了 VNDK-SP-Indirect。
Android 8.1 的變化
- SP-NDK 庫已合並到 LL-NDK 庫中。
- 在 RS 命名空間部分中將
libui.so
替換為libft2.so
。包含libui.so
會出錯。 - 將
libGLESv3.so
和libandroid_net.so
添加到 LL-NDK 庫中。 - 將
libion.so
添加到 VNDK-SP 庫中。 - 從 LL-NDK 庫中移除
libstdc++.so
。改用libc++.so
。某些版本的獨立工具鏈可以將-lstdc++
添加到默認鏈接器標記中。要停用默認設置,請將-nodefaultlibs -lc -lm -ldl
添加到LDFLAGS
中。 - 將
libz.so
從 LL-NDK 移到 VNDK-SP 庫中。在某些配置中,libz.so
可能仍是 LL-NDK。但是,應該沒有明顯的差異。
啟用 VNDK
為了區隔供應商模塊與系統模塊,您在啟用 VNDK 前需要對代碼庫進行一些更改。請按照以下指南在供應商/OEM 代碼庫中啟用 VNDK。
編譯系統庫
編譯系統包含多種類型的對象,其中包括庫(共享、靜態或標頭)和二進制文件:

- core:位於系統映像中,由系統映像使用。
vendor
、vendor_available
、vndk
或vndk-sp
庫不能使用此類庫。cc_library {
name: "libThatIsCore",
...
} - vendor-only(或
proprietary
)。位於供應商映像中,由供應商映像使用。cc_library {
name: "libThatIsVendorOnly",
proprietary: true,
# or: vendor: true, # (for things in AOSP)
...
} - vendor_available:位於供應商映像中,由供應商映像使用(可能包含
core
的重復項)。cc_library {
name: "libThatIsVendorAvailable",
vendor_available: true,
...
} - vndk:位於系統映像中,由供應商映像使用(
vendor_available
的子集)。cc_library {
name: "libThatIsVndk",
vendor_available: true,
vndk: {
enabled: true,
}
...
} - vndk-sp:位於系統映像中,由系統映像間接使用(
core
的子集)。cc_library {
name: "libThatIsVndkSp",
vendor_available: true,
vndk: {
enabled: true,
support_system_process: true,
}
...
} - llndk:同時由系統映像和供應商映像使用。
llndk_library {
name: "libThasIsLlndk",
}
當一個庫被標記為 vendor_available:true
時,它將編譯兩次:
- 一次是為平台編譯(因此被安裝到
/system/lib
中)。 - 一次是為供應商編譯(因此被安裝到
/vendor/lib
、/system/lib/vndk
或/system/lib/vndk-sp
中)。
庫的供應商版本使用 -D__ANDROID_VNDK__
標記編譯。您可以使用此標記停用在 Android 未來版本中可能會發生顯著變化的專用系統組件。此外,不同的庫會導出一組不同的標頭(例如 liblog
)。可以在 Android.bp
文件中指定相應目標的供應商變體特有的選項:
target: { vendor: { … } }
為代碼庫啟用 VNDK
要為代碼庫啟用 VNDK,請執行以下操作:
- 通過計算
vendor.img
和system.img
分區的所需大小來確定是否符合條件。 - 啟用
BOARD_VNDK_VERSION=current
。您可以將其添加到BoardConfig.mk
,也可以直接使用此選項編譯組件(即m -j BOARD_VNDK_VERSION=current MY-LIB
)。
啟用 BOARD_VNDK_VERSION=current
后,編譯系統會強制執行以下依賴項和標頭要求。
管理依賴項
如果 vendor
對象依賴的 core
組件在 vndk
中不存在或未以 vendor
對象的形式存在,則必須通過以下某種方式解決該問題:
- 可以移除該依賴項。
- 如果該
core
組件歸vendor
所有,則可將其標記為vendor_available
或vendor
。 - 可以在上游向 Google 提交更改請求,以便將此核心對象列入
vndk
。
此外,如果有 core
組件依賴於 vendor
組件,則必須使此 vendor
組件成為 core
組件,或者以其他方式移除此依賴項(例如,通過移除依賴項或將其移到 vendor
組件中)。
管理標頭
必須移除全局標頭依賴項,編譯系統才能知道在編譯標頭時是否帶 -D__ANDROID_VNDK__
。例如,您仍然可以使用標頭庫 libutils_headers
訪問 utils/StrongPointer.h
等 libutils 標頭。
某些標頭(例如 unistd.h
)無法再以傳遞方式包含在內,但可以包含在本地。
最后,private/android_filesystem_config.h
的公共部分已移至 cutils/android_filesystem_config.h
。要管理這些標頭,請執行下列操作之一:
- 通過將所有
AID_*
宏替換為getgrnam
/getpwnam
調用(如果可能),移除對private/android_filesystem_config.h
的依賴。例如:(uid_t)AID_WIFI
會變為getpwnam("wifi")->pw_uid
。(gid_t)AID_SDCARD_R
會變為getgrnam("sdcard_r")->gr_gid
。
private/android_filesystem_config.h
。 - 對於硬編碼的 AIS,請包含
cutils/android_filesystem_config.h
。
VNDK 編譯系統支持
在 Android 8.1 及更高版本中,編譯系統具有內置的 VNDK 支持。如果啟用了 VNDK 支持,編譯系統就會檢查各模塊之間的依賴關系,為供應商模塊編譯特定於供應商的變體,並自動將這些模塊安裝到指定目錄中。
VNDK 編譯支持示例
在此示例中,Android.bp
模塊定義定義了一個名為 libexample
的庫。vendor_available
屬性表示框架模塊和供應商模塊均可能依賴於 libexample
。
框架可執行文件 /system/bin/foo
和供應商可執行文件 /vendor/bin/bar
均依賴於 libexample
,並且在其 shared_libs
屬性中具有 libexample
。
如果框架模塊和供應商模塊均使用 libexample
,則編譯 libexample
的兩個變體。核心變體(以 libexample
命名)由框架模塊使用,供應商變體(以 libexample.vendor
命名)由供應商模塊使用。這兩個變體將安裝到不同的目錄中。
- 核心變體將安裝到
/system/lib[64]/libexample.so
中。 - 供應商變體將安裝到
/system/lib[64]/vndk/libexample.so
中,因為vndk.enabled
為true
。
如需了解詳情,請參閱模塊定義。
配置編譯支持
要為產品設備啟用完整編譯系統支持,請將 BOARD_VNDK_VERSION
添加到 BoardConfig.mk
:
BOARD_VNDK_VERSION := current
此設置會產生全局效應:如果在 BoardConfig.mk
中定義,系統會檢查所有模塊。由於沒有將違規模塊列入黑名單或白名單的機制,因此在添加 BOARD_VNDK_VERSION
之前應清除所有不必要的依賴項。您可以通過在環境變量中設置 BOARD_VNDK_VERSION
來測試和編譯模塊:
$ BOARD_VNDK_VERSION=current m module_name.vendor
如果啟用 BOARD_VNDK_VERSION
,系統會移除多個默認的全局頭文件搜索路徑。其中包括:
frameworks/av/include
frameworks/native/include
frameworks/native/opengl/include
hardware/libhardware/include
hardware/libhardware_legacy/include
hardware/ril/include
libnativehelper/include
libnativehelper/include_deprecated
system/core/include
system/media/audio/include
如果某個模塊依賴於上述目錄中的標頭,則您必須明確指定與 header_libs
、static_libs
和/或 shared_libs
的依賴關系。
模塊定義
要使用 BOARD_VNDK_VERSION
編譯 Android,您必須在 Android.mk
或 Android.bp
中修改模塊定義。此部分介紹了不同種類的模塊定義,一些與 VNDK 相關的模塊屬性,以及在編譯系統中實現的依賴性檢查。
供應商模塊
供應商模塊是特定於供應商的可執行文件或共享庫(必須將這些模塊安裝到供應商分區中)。在 Android.bp
文件中,供應商模塊必須將供應商或專有屬性設置為 true
。在 Android.mk
文件中,供應商模塊必須將 LOCAL_VENDOR_MODULE
或 LOCAL_PROPRIETARY_MODULE
設置為 true
。
如果定義了 BOARD_VNDK_VERSION
,則編譯系統不允許在供應商模塊和框架模塊之間建立依賴關系,並且編譯系統會在以下情況下發出錯誤:
- 不具有
vendor:true
的模塊依賴於具有vendor:true
的模塊,或 - 具有
vendor:true
的模塊依賴於既不具有vendor:true
也不具有vendor_available:true
的非llndk_library
模塊。
依賴性檢查適用於 Android.bp
中的 header_libs
、static_libs
和 shared_libs
以及 Android.mk
中的 LOCAL_HEADER_LIBRARIES
、LOCAL_STATIC_LIBRARIES
和 LOCAL_SHARED_LIBRARIES
。
LL-NDK
LL-NDK 共享庫是具有穩定 ABI 的共享庫。框架模塊和供應商模塊均具有相同的最新實現。對於每個 LL-NDK 共享庫,Android.bp
都包含一個 llndk_library
模塊定義:
llndk_library {
name: "libvndksupport",
symbol_file: "libvndksupport.map.txt",
}
該模塊定義指定了模塊名稱和符號文件,后者描述了對供應商模塊可見的符號。例如:
LIBVNDKSUPPORT {
global:
android_load_sphal_library; # vndk
android_unload_sphal_library; # vndk
local:
*;
};
編譯系統會根據符號文件為供應商模塊生成存根共享庫。如果啟用了 BOARD_VNDK_VERSION
,供應商模塊將與這些存根共享庫建立關聯。只有在滿足以下條件時,存根共享庫中才會包含符號:
- 它未在以
_PRIVATE
或_PLATFORM
結尾的部分中定義, - 它不含
#platform-only
標記,並且 - 不含
#introduce*
標記或者該標記與目標匹配。
VNDK
在 Android.bp
文件中,cc_library
、cc_library_static
、cc_library_shared
和 cc_library_headers
模塊定義支持三個與 VNDK 相關的屬性:vendor_available
、vndk.enabled
和 vndk.support_system_process
。
如果 vendor_available
或 vndk.enabled
為 true
,則可以編譯兩種變體(核心變體和供應商變體)。核心變體應被視為框架模塊,而供應商變體應被視為供應商模塊。如果某些框架模塊依賴於此模塊,則會編譯核心變體。如果某些供應商模塊依賴於此模塊,則會編譯供應商變體。編譯系統會強制執行以下依賴性檢查:
- 核心變體始終供框架專用,無法供供應商模塊訪問。
- 供應商變體始終無法供框架模塊訪問。
- 供應商變體的所有依賴項(在
header_libs
、static_libs
和/或shared_libs
中指定)必須是llndk_library
或具有vendor_available
或vndk.enabled
的模塊。 - 如果
vendor_available
為true
,則供應商變體可供所有供應商模塊訪問。 - 如果
vendor_available
為false
,則供應商變體僅可供其他 VNDK 或 VNDK-SP 模塊訪問(即,具有vendor:true
的模塊無法與vendor_available:false
模塊相關聯)。
系統將通過以下規則確定 cc_library
或 cc_library_shared
的默認安裝路徑:
- 將核心變體安裝到
/system/lib[64]
中。 - 供應商變體安裝路徑可能會有所不同:
- 如果
vndk.enabled
為false
,則將供應商變體將安裝到/vendor/lib[64]
中。 - 如果
vndk.enabled
為true
,則vndk.support_system_process
可以是true
或false
。如果:- 為
false
,則供應商變體將安裝到/system/lib[64]/vndk-${VER}
中。 - 為
true
,則供應商變體將安裝到/system/lib[64]/vndk-sp-${VER}
中。
- 為
- 如果
下表總結了編譯系統如何處理供應商變體:
vendor_available | vndk 已啟用 |
vndk support_same_process |
供應商變體說明 |
---|---|---|---|
true |
false |
false |
供應商變體為 VND-ONLY。共享庫將安裝到 /vendor/lib[64] 中。 |
true |
無效(編譯錯誤) | ||
true |
false |
供應商變體為 VNDK。共享庫將安裝到 /system/lib[64]/vndk-${VER} 中。 |
|
true |
供應商變體為 VNDK-SP。共享庫將安裝到 /system/lib[64]/vndk-sp-${VER} 中。 |
||
|
|
|
沒有供應商變體。此模塊為 FWK-ONLY。 |
true |
無效(編譯錯誤) | ||
true |
false |
供應商變體為 VNDK-Private。共享庫將安裝到 /system/lib[64]/vndk-${VER} 中。供應商模塊不得直接使用這些變體。 |
|
true |
供應商變體為 VNDK-SP-Private。共享庫將安裝到 /system/lib[64]/vndk-sp-${VER} 中。供應商模塊不得直接使用這些變體。 |
vendor_available
,但不得設置
vndk.enabled
和
vndk.support_system_process
,因為供應商模塊無法在
通用系統映像 (GSI) 中找到它們。
VNDK 擴展
VNDK 擴展是具有額外 API 的 VNDK 共享庫,將安裝到 /vendor/lib[64]/vndk[-sp]
中(不含版本后綴),並在系統運行時會替換原始的 VNDK 共享庫。
定義 VNDK 擴展
在 Android 9 及更高版本中,Android.bp
本身支持 VNDK 擴展。要編譯 VNDK 擴展,請定義另一個具有 vendor:true
和 extends
屬性的模塊:
cc_library {
name: "libvndk",
vendor_available: true,
vndk: {
enabled: true,
},
}
cc_library {
name: "libvndk_ext",
vendor: true,
vndk: {
enabled: true,
extends: "libvndk",
},
}
具有 vendor:true
、vndk.enabled:true
和 extends
屬性的模塊可定義 VNDK 擴展:
extends
屬性必須指定基礎 VNDK 共享庫名稱(或 VNDK-SP 共享庫名稱)。- VNDK 擴展(或 VNDK-SP 擴展)以擴展時所基於的基礎模塊名稱命名。例如,
libvndk_ext
的輸出二進制文件是libvndk.so
,而非libvndk_ext.so
。 - VNDK 擴展將安裝到
/vendor/lib[64]/vndk
中。 - VNDK-SP 擴展將安裝到
/vendor/lib[64]/vndk-sp
中。 - 基礎共享庫必須同時具有
vndk.enabled:true
和vendor_available:true
。
VNDK-SP 擴展必須從 VNDK-SP 共享庫進行擴展(vndk.support_system_process
必須相等):
cc_library {
name: "libvndk_sp",
vendor_available: true,
vndk: {
enabled: true,
support_system_process: true,
},
}
cc_library {
name: "libvndk_sp_ext",
vendor: true,
vndk: {
enabled: true,
extends: "libvndk_sp",
support_system_process: true,
},
}
VNDK 擴展(或 VNDK-SP 擴展)可以依賴於其他供應商共享庫:
cc_library {
name: "libvndk",
vendor_available: true,
vndk: {
enabled: true,
},
}
cc_library {
name: "libvndk_ext",
vendor: true,
vndk: {
enabled: true,
extends: "libvndk",
},
shared_libs: [
"libvendor",
],
}
cc_library {
name: "libvendor",
vendor: true,
}
注意:與 SP-HAL-Dep 類似,VNDK-SP 擴展及其依賴項(包括供應商庫)在 sepolicy 中必須標記為
same_process_hal_file
。
使用 VNDK 擴展
如果供應商模塊依賴於由 VNDK 擴展定義的其他 API,則該模塊必須在其 shared_libs
屬性中指定 VNDK 擴展的名稱:
// A vendor shared library example
cc_library {
name: "libvendor",
vendor: true,
shared_libs: [
"libvndk_ext",
],
}
// A vendor executable example
cc_binary {
name: "vendor-example",
vendor: true,
shared_libs: [
"libvndk_ext",
],
}
如果供應商模塊依賴於 VNDK 擴展,則這些 VNDK 擴展將自動安裝到 /vendor/lib[64]/vndk[-sp]
中。如果某個模塊不再依賴於 VNDK 擴展,請向 CleanSpec.mk
添加一個清理步驟,以移除共享庫。例如:
$(call add-clean-step, rm -rf $(TARGET_OUT_VENDOR)/lib/libvndk.so)
條件編譯
本節介紹了如何處理以下三個 VNDK 共享庫之間的細微差別(例如,為其中一個變體添加或移除某項功能):
- 核心變體(例如
/system/lib[64]/libexample.so
) - 供應商變體(例如
/system/lib[64]/vndk[-sp]-${VER}/libexample.so
) - VNDK 擴展(例如
/vendor/lib[64]/vndk[-sp]/libexample.so
)
條件編譯器標記
默認情況下,Android 編譯系統會為供應商變體和 VNDK 擴展定義 __ANDROID_VNDK__
。您可以使用 C 預處理器防護程序保護代碼:
void all() { }
#if !defined(__ANDROID_VNDK__)
void framework_only() { }
#endif
#if defined(__ANDROID_VNDK__)
void vndk_only() { }
#endif
除了 __ANDROID_VNDK__
,還可以在 Android.bp
中指定不同的 cflags
或 cppflags
。在 target.vendor
中指定的 cflags
或 cppflags
是專門針對供應商變體的。
例如,以下 Android.bp
定義了 libexample
和 libexample_ext
:
cc_library {
name: "libexample",
srcs: ["src/example.c"],
vendor_available: true,
vndk: {
enabled: true,
},
target: {
vendor: {
cflags: ["-DLIBEXAMPLE_ENABLE_VNDK=1"],
},
},
}
cc_library {
name: "libexample_ext",
srcs: ["src/example.c"],
vendor: true,
vndk: {
enabled: true,
extends: "libexample",
},
cflags: [
"-DLIBEXAMPLE_ENABLE_VNDK=1",
"-DLIBEXAMPLE_ENABLE_VNDK_EXT=1",
],
}
這是 src/example.c
的代碼清單:
void all() { }
#if !defined(LIBEXAMPLE_ENABLE_VNDK)
void framework_only() { }
#endif
#if defined(LIBEXAMPLE_ENABLE_VNDK)
void vndk() { }
#endif
#if defined(LIBEXAMPLE_ENABLE_VNDK_EXT)
void vndk_ext() { }
#endif
編譯系統會根據這兩個文件生成帶有以下導出符號的共享庫:
安裝路徑 | 導出的符號 |
---|---|
/system/lib[64]/libexample.so |
all 、framework_only |
/system/lib[64]/vndk-${VER}/libexample.so |
all 、vndk |
/vendor/lib[64]/vndk/libexample.so |
all 、vndk 、vndk_ext |
對導出符號的要求
VNDK ABI 檢查工具會將 VNDK 供應商變體和 VNDK 擴展的 ABI 同 prebuilts/abi-dumps/vndk
下的參考 ABI 轉儲進行比較。
- 通過 VNDK 供應商變體(例如
/system/lib[64]/vndk-${VER}/libexample.so
)導出的符號必須與 ABI 轉儲中定義的符號相同(而不是后者的超集)。 - 通過 VNDK 擴展(例如
/vendor/lib[64]/vndk/libexample.so
)導出的符號必須是 ABI 轉儲中定義的符號的超集。
如果 VNDK 供應商變體或 VNDK 擴展未能遵循上述要求,則 VNDK ABI 檢查工具會發出編譯錯誤並停止編譯。
從供應商變體中排除源文件或共享庫
要從供應商變體中排除源文件,請將這些文件添加到 exclude_srcs
屬性中。同樣,要確保共享庫未與供應商變體相關聯,請將這些庫添加到 exclude_shared_libs
屬性中。例如:
cc_library {
name: "libexample_cond_exclude",
srcs: ["fwk.c", "both.c"],
shared_libs: ["libfwk_only", "libboth"],
target: {
vendor: {
exclude_srcs: ["fwk.c"],
exclude_shared_libs: ["libfwk_only"],
},
},
}
在本示例中,libexample_cond_exclude
的核心變體包含 fwk.c
和 both.c
中的代碼,並且依賴於共享庫 libfwk_only
和 libboth
。libexample_cond_exclude
的供應商變體僅包含 both.c
中的代碼,因為 fwk.c
已被 exclude_srcs
屬性排除。同樣,libexample_cond_exclude
僅依賴於共享庫 libboth
,因為 libfwk_only
已被 exclude_shared_libs
屬性排除。
從 VNDK 擴展導出頭文件
VNDK 擴展可以向 VNDK 共享庫添加新類或新函數。建議將這些聲明保留在獨立頭文件中,並避免更改現有頭文件。
例如,為 VNDK 擴展 libexample_ext
創建了一個新的頭文件 include-ext/example/ext/feature_name.h
:
- Android.bp
- include-ext/example/ext/feature_name.h
- include/example/example.h
- src/example.c
- src/ext/feature_name.c
在下面的 Android.bp
中,libexample
只會導出 include
,而 libexample_ext
會同時導出 include
和 include-ext
。這樣可以確保 libexample
的用戶不會錯誤地添加 feature_name.h
:
cc_library {
name: "libexample",
srcs: ["src/example.c"],
export_include_dirs: ["include"],
vendor_available: true,
vndk: {
enabled: true,
},
}
cc_library {
name: "libexample_ext",
srcs: [
"src/example.c",
"src/ext/feature_name.c",
],
export_include_dirs: [
"include",
"include-ext",
],
vendor: true,
vndk: {
enabled: true,
extends: "libexample",
},
}
如果將擴展分離到獨立的頭文件不可行,替代方案是添加 #ifdef
防護程序。但是,請確保所有 VNDK 擴展用戶都添加了 define 標記。您可以定義 cc_defaults
以向 cflags
添加定義標記並使用 shared_libs
鏈接共享庫。
例如,要將新成員函數 Example2::get_b()
添加到 VNDK 擴展 libexample2_ext
中,您必須修改現有的頭文件並添加 #ifdef
防護程序:
#ifndef LIBEXAMPLE2_EXAMPLE_H_
#define LIBEXAMPLE2_EXAMPLE_H_
class Example2 {
public:
Example2();
void get_a();
#ifdef LIBEXAMPLE2_ENABLE_VNDK_EXT
void get_b();
#endif
private:
void *impl_;
};
#endif // LIBEXAMPLE2_EXAMPLE_H_
為 libexample2_ext
的用戶定義了一個名為 libexample2_ext_defaults
的 cc_defaults
:
cc_library {
name: "libexample2",
srcs: ["src/example2.cpp"],
export_include_dirs: ["include"],
vendor_available: true,
vndk: {
enabled: true,
},
}
cc_library {
name: "libexample2_ext",
srcs: ["src/example2.cpp"],
export_include_dirs: ["include"],
vendor: true,
vndk: {
enabled: true,
extends: "libexample2",
},
cflags: [
"-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
],
}
cc_defaults {
name: "libexample2_ext_defaults",
shared_libs: [
"libexample2_ext",
],
cflags: [
"-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
],
}
libexample2_ext
的用戶只需在其 defaults
屬性中添加 libexample2_ext_defaults
:
cc_binary {
name: "example2_user_executable",
defaults: ["libexample2_ext_defaults"],
vendor: true,
}
產品包
在 Android 編譯系統中,變量 PRODUCT_PACKAGES
指定應安裝到設備中的可執行文件、共享庫或軟件包。指定模塊的傳遞依賴項也會隱式安裝到設備中。
如果啟用了 BOARD_VNDK_VERSION
,具有 vendor_available
或 vndk.enabled
的模塊會得到特殊處理。如果框架模塊依賴於具有 vendor_available
或 vndk.enabled
的模塊,則核心變體將納入傳遞安裝集中。同樣,如果供應商模塊依賴於具有 vendor_available
或 vndk.enabled
的模塊,則供應商變體將納入傳遞安裝集中。
當相關依賴項對編譯系統不可見時(例如,可以在運行時使用 dlopen()
打開的共享庫),您應該在 PRODUCT_PACKAGES
中指定模塊名稱來明確安裝這些模塊。
如果某個模塊具有 vendor_available
或 vndk.enabled
,則模塊名稱代表該模塊的核心變體。要在 PRODUCT_PACKAGES
中明確指定供應商變體,請將 .vendor
后綴附加到模塊名稱上。例如:
cc_library {
name: "libexample",
srcs: ["example.c"],
vendor_available: true,
}
在本示例中,libexample
代表 /system/lib[64]/libexample.so
、libexample.vendor
代表 /vendor/lib[64]/libexample.so
。要安裝 /vendor/lib[64]/libexample.so
,請將 libexample.vendor
添加到 PRODUCT_PACKAGES
:
PRODUCT_PACKAGES += libexample.vendor
VNDK 擴展
Android 設備制造商會出於各種原因而更改 AOSP 庫的源代碼。一些供應商會為了提高性能而重新實現 AOSP 庫中的函數,另一些供應商則會向 AOSP 庫添加新鈎子、新 API 或新功能。本部分將介紹一些准則,以說明如何在不破壞 CTS/VTS 的前提下擴展 AOSP 庫。
簡易替換
所有修改后的共享庫都必須與其 AOSP 副本保持二進制兼容,且可以簡易替換該副本。所有現有的 AOSP 用戶都必須能在不進行重新編譯的情況下使用修改后的共享庫。此要求有以下幾點含義:
- 不得移除 AOSP 函數。
- 不得更改面向用戶提供的結構。
- 不得強化函數的前提條件。
- 函數必須提供等效功能。
- 不得削弱函數的后置條件。
擴展后的模塊分類
按模塊所定義和使用的功能對其進行分類。
注意:此處之所以使用“功能”一詞而未使用 API/ABI,是因為可在不更改任何 API/ABI 的情況下添加功能。
根據模塊中定義的功能,可將模塊分為 DA 模塊和 DX 模塊:
- Defining-only-AOSP 模塊(DA 模塊)不會定義 AOSP 副本中未包含的新功能。
- 示例 1: 一個未經修改且完整無缺的 AOSP 庫即是一個 DA 模塊。
- 示例 2: 如果供應商使用 SIMD 指令重寫
libcrypto.so
中的函數(不添加新函數),那么修改后的libcrypto.so
將是一個 DA 模塊。
- Defining-Extension 模塊(DX 模塊)要么會定義新功能,要么沒有 AOSP 副本。
- 示例 1: 如果供應商向
libjpeg.so
添加一個 helper 函數以訪問某些內部數據,那么修改后的libjpeg.so
將是一個 DX 庫,而這個新增函數將是該庫的擴展部分。 - 示例 2: 如果供應商定義了一個名為
libfoo.so
的非 AOSP 庫,那么libfoo.so
將是一個 DX 庫。
- 示例 1: 如果供應商向
根據模塊所使用的功能,可將模塊分為 UA 模塊和 UX 模塊。
- Using-only-AOSP(UA 模塊)僅會在其實現過程中使用 AOSP 功能。它們不依賴任何非 AOSP 擴展功能。
- 示例 1: 一個未經修改且完整無缺的 AOSP 庫即是一個 UA 模塊。
- 示例 2: 如果修改后的共享庫
libjpeg.so
僅依賴於其他 AOSP API,那么它將是一個 UA 模塊。
- Using-Extension 模塊(UX 模塊)會在其實現過程中依賴某些非 AOSP 功能。
- 示例 1: 如果修改后的
libjpeg.so
依賴另一個名為libjpeg_turbo2.so
的非 AOSP 庫,那么修改后的libjpeg.so
將是一個 UX 模塊。 - 示例 2: 如果供應商向其修改后的
libexif.so
添加了一個新函數,並且其修改后的libjpeg.so
使用libexif.so
中的這個新增函數,那么修改后的libjpeg.so
將是一個 UX 模塊。
- 示例 1: 如果修改后的
定義的功能和使用的功能相互獨立:
使用的功能 | |||
---|---|---|---|
Only AOSP (UA) | Extended (UX) | ||
定義的功能 | Only AOSP (DA) | DAUA | DAUX |
Extended (DX) | DXUA | DXUX |
VNDK 擴展機制
由於同名的 AOSP 庫不包含擴展功能,因此依賴擴展功能的供應商模塊將無法正常工作。如果供應商模塊直接或間接依賴擴展功能,則供應商應將 DAUX、DXUA 和 DXUX 共享庫復制到供應商分區(供應商進程始終都會先在供應商分區中查找共享庫)。但是,由於不得復制 LL-NDK 庫,因此供應商模塊不得依賴由修改后的 LL-NDK 庫定義的擴展功能。
當系統分區被常規系統映像 (GSI) 覆蓋時,如果相應的 AOSP 庫可以提供相同的功能,且供應商模塊可以繼續正常工作,DAUA 共享庫便可保留在系統分區上。
簡易替換非常重要,因為 GSI 中未經修改的 VNDK 庫將會在名稱沖突時與修改后的共享庫關聯。如果以 API/ABI 不兼容的方式修改 AOSP 庫,那么 GSI 中的 AOSP 庫可能會無法進行關聯或會出現未定義的行為。
VNDK 定義工具
VNDK 定義工具可幫助供應商將其源代碼樹遷移到 Android 8.0 環境。該工具會先掃描系統映像及供應商映像中的二進制文件,然后解析依賴項。若有模塊依賴項圖為依據,該工具還可檢測出不符合 VNDK 概念的行為,以及為在分區之間移動模塊提供分析數據/建議。如果指定了常規系統映像 (GSI),VNDK 定義工具便可將您的系統映像與 GSI 進行比較,從而確定擴展后的庫。
本部分將介紹 VNDK 定義工具常用的 3 個命令:
vndk
:為 Android 8.0 及更高版本中的編譯系統臨時解決方法計算 VNDK_SP_LIBRARIES、VNDK_SP_EXT_LIBRARIES 和 EXTRA_VENDOR_LIBRARIES。check-dep
:檢查是否有違規模塊依賴項(從供應商模塊指向不符合條件的框架共享庫)。deps
:顯示共享庫與可執行文件之間的依賴關系。
要詳細了解高級命令用法,請參閱 VNDK 定義工具代碼庫中的 README.md 文件。
vndk
vndk
子命令會從系統分區和供應商分區加載共享庫和可執行文件,然后解析模塊依賴項,從而確定必須被復制到 /system/lib[64]/vndk-sp-${VER}
和 /vendor/lib[64]
的庫。vndk
子命令包含以下選項:
選項 | 說明 |
---|---|
--system |
指向一個包含將會存放在系統分區中的文件的目錄。 |
--vendor |
指向一個包含將會存放在供應商分區中的文件的目錄。 |
--aosp-system |
指向一個包含將會存放在常規系統映像 (GSI) 中的文件的目錄。 |
--load-extra-deps |
指向一個描述隱式依賴項(例如 dlopen() )的文件。 |
例如,要計算 VNDK 庫集,請運行以下 vndk
子命令:
./vndk_definition_tool.py vndk \
--system ${ANDROID_PRODUCT_OUT}/system \
--vendor ${ANDROID_PRODUCT_OUT}/vendor \
--aosp-system ${ANDROID_PRODUCT_OUT}/../generic_arm64_ab/system\
--load-extra-deps dlopen.dep
請使用簡單的文件格式指定額外的依賴關系。每行表示一項依賴關系,其中冒號前面的文件依賴冒號后面的文件。例如:
/system/lib/libart.so: /system/lib/libart-compiler.so
通過此行,VNDK 定義工具可得知 libart.so
依賴 libart-compiler.so
。
安裝目標位置
VNDK 定義工具會列出以下類別的庫及相應的安裝目錄:
類別 | 目錄 |
---|---|
vndk_sp | 必須安裝到 /system/lib[64]/vndk-sp-${VER} |
vndk_sp_ext | 必須安裝到 /vendor/lib[64]/vndk-sp |
extra_vendor_libs | 必須安裝到 /vendor/lib[64] |
編譯系統模板
在收集了 VNDK 定義工具的輸出信息之后,供應商可以創建一個 Android.mk
並填充 VNDK_SP_LIBRARIES
、VNDK_SP_EXT_LIBRARIES
和 EXTRA_VENDOR_LIBRARIES
,以自動執行相應進程,將庫復制到指定的安裝目標位置。
ifneq ($(filter $(YOUR_DEVICE_NAME),$(TARGET_DEVICE)),)
VNDK_SP_LIBRARIES := ##_VNDK_SP_##
VNDK_SP_EXT_LIBRARIES := ##_VNDK_SP_EXT_##
EXTRA_VENDOR_LIBRARIES := ##_EXTRA_VENDOR_LIBS_##
#-------------------------------------------------------------------------------
# VNDK Modules
#-------------------------------------------------------------------------------
LOCAL_PATH := $(call my-dir)
define define-vndk-lib
include $$(CLEAR_VARS)
LOCAL_MODULE := $1.$2
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_PREBUILT_MODULE_FILE := $$(TARGET_OUT_INTERMEDIATE_LIBRARIES)/$1.so
LOCAL_STRIP_MODULE := false
LOCAL_MULTILIB := first
LOCAL_MODULE_TAGS := optional
LOCAL_INSTALLED_MODULE_STEM := $1.so
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_RELATIVE_PATH := $3
LOCAL_VENDOR_MODULE := $4
include $$(BUILD_PREBUILT)
ifneq ($$(TARGET_2ND_ARCH),)
ifneq ($$(TARGET_TRANSLATE_2ND_ARCH),true)
include $$(CLEAR_VARS)
LOCAL_MODULE := $1.$2
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_PREBUILT_MODULE_FILE := $$($$(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_INTERMEDIATE_LIBRARIES)/$1.so
LOCAL_STRIP_MODULE := false
LOCAL_MULTILIB := 32
LOCAL_MODULE_TAGS := optional
LOCAL_INSTALLED_MODULE_STEM := $1.so
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_RELATIVE_PATH := $3
LOCAL_VENDOR_MODULE := $4
include $$(BUILD_PREBUILT)
endif # TARGET_TRANSLATE_2ND_ARCH is not true
endif # TARGET_2ND_ARCH is not empty
endef
$(foreach lib,$(VNDK_SP_LIBRARIES),\
$(eval $(call define-vndk-lib,$(lib),vndk-sp-gen,vndk-sp,)))
$(foreach lib,$(VNDK_SP_EXT_LIBRARIES),\
$(eval $(call define-vndk-lib,$(lib),vndk-sp-ext-gen,vndk-sp,true)))
$(foreach lib,$(EXTRA_VENDOR_LIBRARIES),\
$(eval $(call define-vndk-lib,