供应商原生开发套件 (VNDK)
供应商原生开发套件 (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。
编译系统库
编译系统包含多种类型的对象,其中包括库(共享、静态或标头)和二进制文件:
图 1. 编译系统库。- 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