尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

第 11 章:NDK —— 原生开发工具包

第 11 章:NDK —— 原生开发工具包 Android NDK(原生开发工具包)是应用使用 C/C++ 语言访问 Android 平台的入口。与 Java/Kotlin 框架 API 可以随版本自由迭代不同,NDK API 具备严格的稳定性保障:在 API 级别 21 导出的符号,必须在后续所有版本中保持可用,且 ABI 完全兼容。该约束从根本上决定了 NDK 的构建方式、AOSP 内部头文件与桩库的生成逻辑,以及三层嵌套库分类 —— NDK、LL‑NDK、VNDK,它们将原生代码划分为多个稳定层级。本章从平台构建者的视角讲解 NDK。首先介绍将面向应用的 API 与框架内部代码隔离开的架构;接着分析 Soong 模块类型(ndk_library、ndk_headers、llndk_libraries_txt、vndk_prebuilt_shared),这些模块生成交付给应用开发者的 sysroot;随后讲解 LL‑NDK 与 VNDK 层如何将同一套稳定性原则延伸到厂商代码;研究 Camera、Media、Binder 的框架绑定层,它们通过 NDK 头文件对外暴露原生服务;探究用于打包 NativeBridge 依赖的ndk_translation_package模块类型;最后通过实操练习串联全部知识点。本章全程引用 AOSP 源码树中的真实文件。文中提及的每一条路径、结构体定义、构建规则,都可以在源码树中找到对应内容。11.1 NDK 架构总览11.1.1 NDK 是什么 —— 以及它不是什么NDK 是一套稳定的 C/C++ API 集合,应用开发者可以通过System.loadLibrary()加载原生代码,或是完全基于NativeActivity纯原生应用调用这些接口。“稳定” 包含两层含义:ABI 稳定性:给定 API 级别下导出的全部函数,其符号名、调用约定、数据结构体布局永远不会变更。头文件稳定性:安装到 NDK sysroot 中的每一个头文件,在编译阶段都会校验,保证自包含,是合法的 C 语言代码。NDK 显然不等于平台全部原生代码。frameworks/、system/、hardware/下绝大多数 C/C++ 代码属于框架内部实现,永远不会暴露给应用。“NDK” 与 “非 NDK” 的边界在两个层面强制生效:编译期:ndk_library与ndk_headers这两类 Soong 模块,精确控制哪些符号、头文件被放入 sysroot。运行时:动态链接器的命名空间隔离机制,阻止应用通过dlopen()加载不在 NDK 或 LL‑NDK 列表中的库。11.1.2 NDK 调用栈下图展示 Java 应用代码,经由 JNI 调用 NDK API,再向下调用系统库的典型调用链路:图中每一层对应不同的稳定性域:层级稳定性保障使用者NDK API跨版本 ABI 稳定应用开发者平台系统库无稳定性保障框架开发者内核接口由内核 ABI 保障稳定全部原生代码11.1.3 NDK vs 框架原生代码必须区分使用 NDK 的原生代码和属于平台本身的原生代码。看两个具体示例:使用 NDK 的应用:游戏引擎链接libc.so、liblog.so、libEGL.so、libGLESv3.so、libaaudio.so。这些库全部位于 NDK 列表。游戏打包进 APK,携带lib/arm64‑v8a/libgame.so;平台保证,在相同或更高 API 级别的任意设备上,它调用的 API 行为完全一致。框架原生代码:SurfaceFlinger 合成器链接libgui.so、libui.so、libsync.so、libhwbinder.so以及数十个其他内部库。这些库没有任何 NDK 稳定性承诺。设备厂商需要(并且必须)基于完整平台源码重新编译 SurfaceFlinger。构建系统强制该区分。当模块设置sdk_version: "current",Soong 会将其共享库依赖解析指向 NDK 桩库,而非平台真实实现。如果模块试图使用非 NDK 符号,编译阶段就会链接失败。11.1.4 sysroot 生成流程NDK sysroot 不是手动维护的头文件与库目录,它是 AOSP 的编译产物。构建系统从三类组件组装 sysroot,这些组件在build/soong/cc/ndk_sysroot.go中注册为 Soong 模块类型:build/soong/cc/ndk_sysroot.go文件头部注释明确列出这四项产物:// 平台需要为NDK提供以下产物: // 1. Bionic头文件。 // 2. 平台API头文件。 // 3. NDK桩共享库。 // 4. Bionic静态库。该文件注册 3 个模块类型和 1 个单例:// build/soong/cc/ndk_sysroot.go (81‑86行) func RegisterNdkModuleTypes(ctx android.RegistrationContext) { ctx.RegisterModuleType("ndk_headers", NdkHeadersFactory) ctx.RegisterModuleType("ndk_library", NdkLibraryFactory) ctx.RegisterModuleType("preprocessed_ndk_headers", preprocessedNdkHeadersFactory) ctx.RegisterParallelSingletonType("ndk", NdkSingleton) }NdkSingleton遍历源码树所有模块,收集头文件、桩库、静态库。生成 3 个时间戳文件,顶层 Makefile 依赖这些文件:ndk_headers.timestamp:仅依赖头文件(用于.tidy检查)ndk_base.timestamp:依赖头文件 + 桩共享库ndk.timestamp:依赖上面全部 + 静态库执行m ndk即可触发全部 sysroot 产物的生成。11.2 NDK API 接口集11.2.1 NDK 库总览NDK API 接口集是 AOSP 中全部ndk_library模块的集合,这些就是应用开发者可以链接的库。在源码树搜索ndk_library {,可以得到完整列表:库名称首次引入 API 等级源码路径libc9bionic/libc/Android.bplibm9bionic/libm/Android.bplibdl9bionic/libdl/Android.bpliblog9system/logging/liblog/Android.bplibz9external/zlib/Android.bplibandroid9frameworks/base/native/android/Android.bplibEGL9frameworks/native/opengl/libs/Android.bplibGLESv1_CM9frameworks/native/opengl/libs/Android.bplibGLESv29frameworks/native/opengl/libs/Android.bplibGLESv318frameworks/native/opengl/libs/Android.bplibmediandk21frameworks/av/media/ndk/Android.bplibcamera2ndk24frameworks/av/camera/ndk/Android.bplibnativewindow26frameworks/native/libs/nativewindow/Android.bplibaaudio26frameworks/av/media/libaaudio/Android.bplibvulkan24frameworks/native/vulkan/libvulkan/Android.bplibbinder_ndk29frameworks/native/libs/binder/ndk/Android.bplibsync26system/core/libsync/Android.bplibneuralnetworks27packages/modules/NeuralNetworks/runtime/Android.bplibicu31external/icu/libicu/Android.bplibnativehelper31 ("S")system/extras/module_ndk_libs/libnativehelper/Android.bp表中每一项对应一个 Soong 的ndk_library代码块,示例:// frameworks/av/camera/ndk/Android.bp (51‑56行) ndk_library { name: "libcamera2ndk", symbol_file: "libcamera2ndk.map.txt", first_version: "24", unversioned_until: "current", }11.2.2 API 分类NDK API 覆盖非常多功能,概念分组如下:11.2.3 关键 NDK API本节介绍应用高频使用的重要 NDK API。AHardwareBufferAHardwareBuffer提供跨进程访问 GPU 分配内存的句柄。API 26 随libnativewindow引入,允许 CPU、GPU、相机、视频解码器之间无拷贝共享图形缓冲区。libnativewindow提供的核心函数:AHardwareBuffer_allocate()— 根据指定格式与使用标志分配缓冲区AHardwareBuffer_lock()— 映射缓冲区,供 CPU 访问AHardwareBuffer_sendHandleToUnixSocket()— 跨进程传递句柄AHardwareBuffer_recvHandleFromUnixSocket()— 从其他进程接收句柄ANativeWindowANativeWindow是android.view.Surface的原生侧接口。是应用直接渲染帧(OpenGL ES、Vulkan 或者软件渲染)的主要接口。API 9 起在libandroid可用:ANativeWindow_fromSurface()— 将 Java 层 Surface 转为原生句柄ANativeWindow_setBuffersGeometry()— 配置缓冲区尺寸ANativeWindow_lock()/ANativeWindow_unlockAndPost()— 软件渲染AAudioAAudio(Android Audio)从 API 26 开始,替代 OpenSL ES,作为低延迟音频推荐 API,定义在libaaudio:AAudioStreamBuilder_create()— 创建流构建器AAudioStreamBuilder_setPerformanceMode()— 请求低延迟模式AAudioStream_requestStart()/AAudioStream_requestStop()— 启停播放AAudio 的 NDK 头文件声明示例:// frameworks/av/media/libaaudio/Android.bp (24‑31行) ndk_headers { name: "libAAudio_headers", from: "include", to: "", srcs: ["include/aaudio/AAudio.h"], license: "include/aaudio/NOTICE", }ACameraCamera NDK 在 API 24 随libcamera2ndk引入,把 Camera2 API 暴露给原生代码。16.5.6 节会详细讲解它的实现。ASensor传感器 API 属于libandroid,提供加速度计、陀螺仪等硬件传感器访问:ASensorManager_getInstance()— 获取传感器管理器ASensorManager_getDefaultSensor()— 获取指定传感器ASensorEventQueue_enableSensor()— 开始接收传感器事件11.2.4 原生应用胶水层 ¶NDK 自带一套辅助库叫native app glue,用来简化纯原生应用开发。源码存放路径:plaintextprebuilts/ndk/current/sources/android/native_app_glue/ android_native_app_glue.c android_native_app_glue.h胶水库提供一套线程模型:应用在独立线程运行主循环,不和 Activity UI 线程抢占。核心数据结构struct android_app:// prebuilts/ndk/current/sources/android/native_app_glue/ // android_native_app_glue.h (109‑183行) struct android_app { void* userData; void (*onAppCmd)(struct android_app* app, int32_t cmd); int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); ANativeActivity* activity; AConfiguration* config; void* savedState; size_t savedStateSize; ALooper* looper; AInputQueue* inputQueue; ANativeWindow* window; ARect contentRect; int activityState; int destroyRequested; // ... 私有实现字段 };应用通过命令码接收生命周期事件:命令含义APP_CMD_INIT_WINDOW新的 ANativeWindow 就绪APP_CMD_TERM_WINDOW窗口即将销毁APP_CMD_GAINED_FOCUSActivity 获得输入焦点APP_CMD_LOST_FOCUSActivity 失去输入焦点APP_CMD_RESUMEActivity 恢复APP_CMD_PAUSEActivity 暂停APP_CMD_SAVE_STATE应用需要保存状态APP_CMD_DESTROYActivity 被销毁应用入口不是main(),而是android_main():// prebuilts/ndk/current/sources/android/native_app_glue/ // android_native_app_glue.h (346行) extern void android_main(struct android_app* app);11.2.5 符号映射文件每一个 NDK 库都受.map.txt符号文件管控。该文件是库 API 接口集的权威定义。下面是 Camera NDK 符号文件片段:// frameworks/av/camera/ndk/libcamera2ndk.map.txt(节选) LIBCAMERA2NDK { global: ACameraCaptureSession_abortCaptures; ACameraCaptureSession_capture; ACameraCaptureSession_captureV2; # introduced=33 ACameraCaptureSession_logicalCamera_capture; # introduced=29 ACameraCaptureSession_close; ACameraCaptureSession_getDevice; ACameraCaptureSession_setRepeatingRequest; ACameraCaptureSession_stopRepeating; ACameraCaptureSession_updateSharedOutput; # introduced=28 ACameraDevice_close; ACameraDevice_createCaptureRequest; ACameraDevice_createCaptureRequest_withPhysicalIds; # introduced=29 ACameraDevice_createCaptureSession; ACameraDevice_getId; ACameraManager_create; ACameraManager_delete; ACameraManager_deleteCameraIdList; ACameraManager_getCameraCharacteristics; ACameraManager_getCameraIdList; ACameraManager_openCamera; ACameraManager_registerAvailabilityCallback; ACameraManager_unregisterAvailabilityCallback; ACameraMetadata_copy; ACameraMetadata_free; ACameraMetadata_getAllTags; ACameraMetadata_getConstEntry; ACameraMetadata_getTagFromName; # introduced=35 ACameraMetadata_isLogicalMultiCamera; # introduced=29 ACameraMetadata_fromCameraMetadata; # introduced=30 ACameraOutputTarget_create; ACameraOutputTarget_free; ACaptureRequest_addTarget; ACaptureRequest_copy; # introduced=28 ACaptureRequest_free; ACaptureRequest_getAllTags; ACaptureRequest_getConstEntry; ACaptureRequest_setEntry_double; ACaptureRequest_setEntry_float; ACaptureRequest_setEntry_i32; ACaptureRequest_setEntry_i64; ACaptureRequest_setEntry_rational; ACaptureRequest_setEntry_u8; ACaptureSessionOutputContainer_add; ACaptureSessionOutputContainer_create; ACaptureSessionOutputContainer_free; ACaptureSessionOutputContainer_remove; ACaptureSessionOutput_create; ACaptureSessionOutput_free; local: *; };符号映射格式要点:global:— 列出的符号会从桩库对外导出local: *;— 其余所有符号全部隐藏(通用兜底规则)# introduced=N— 该符号在 API 级别 N 新增;ndkstubgen工具会在更低版本的桩库中剔除该符号# systemapi— 该符号仅系统应用可用,普通第三方应用不可见不带# introduced=标记的符号,从库的first_version版本起可用(例如libcamera2ndk为 API24)该格式允许在单个文件内精细跟踪每个符号对应的 API 等级。当ndkstubgen生成 API 28 桩库时,会包含所有≤28 版本引入的符号,剔除 29 及更高版本新增符号。11.2.6 Bionic NDK 头文件Bionic C 库提供数量最多的 NDK 头文件。bionic/libc/Android.bp中定义了多个ndk_headers模块:// bionic/libc/Android.bp (2084‑2089行) ndk_headers { name: "common_libc", from: "include", to: "", srcs: ["include/**/*.h"], license: "NOTICE", }还有其他头文件模块覆盖内核 UAPI 头文件、架构相关头文件等:bp// bionic/libc/Android.bp (2097‑2106行) ndk_headers { name: "libc_uapi", from: "kernel/uapi", to: "", srcs: [ "kernel/uapi/asm-generic/**/*.h", // ... ], license: "NOTICE", }这些 Bionic 头文件构成 NDK sysroot 基础,包含:标准 C 库头文件(stdio.h、stdlib.h、string.h等)POSIX 头文件(pthread.h、unistd.h、sys/mman.h等)Linux 内核 UAPI 头文件(linux/*.h、asm/*.h)Android 扩展头文件(android/log.h、android/dlext.h)11.2.7 CPU 特性库cpufeatures库允许原生代码在运行时查询 CPU 硬件能力,源码路径:prebuilts/ndk/current/sources/android/cpufeatures/ cpu-features.c cpu-features.h核心 API 是两个函数:// prebuilts/ndk/current/sources/android/cpufeatures/cpu-features.h (58行) extern AndroidCpuFamily android_getCpuFamily(void); // cpu-features.h (65行) extern uint64_t android_getCpuFeatures(void);android_getCpuFamily()返回值可选:ANDROID_CPU_FAMILY_ARMANDROID_CPU_FAMILY_ARM64ANDROID_CPU_FAMILY_X86ANDROID_CPU_FAMILY_X86_64android_getCpuFeatures()返回 CPU 能力位掩码。ARM64 平台标志:// cpu-features.h (246‑254行) enum { ANDROID_CPU_ARM64_FEATURE_FP = (1 0), ANDROID_CPU_ARM64_FEATURE_ASIMD = (1 1), ANDROID_CPU_ARM64_FEATURE_AES = (1 2), ANDROID_CPU_ARM64_FEATURE_PMULL = (1 3), ANDROID_CPU_ARM64_FEATURE_SHA1 = (1 4), ANDROID_CPU_ARM64_FEATURE_SHA2 = (1 5), ANDROID_CPU_ARM64_FEATURE_CRC32 = (1 6), };x86/x86_64 架构标志:// cpu-features.h (260‑271行) enum { ANDROID_CPU_X86_FEATURE_SSSE3 = (1 0), ANDROID_CPU_X86_FEATURE_POPCNT = (1 1), ANDROID_CPU_X86_FEATURE_MOVBE = (1 2), ANDROID_CPU_X86_FEATURE_SSE4_1 = (1 3), ANDROID_CPU_X86_FEATURE_SSE4_2 = (1 4), ANDROID_CPU_X86_FEATURE_AES_NI = (1 5), ANDROID_CPU_X86_FEATURE_AVX = (1 6), ANDROID_CPU_X86_FEATURE_RDRAND = (1 7), ANDROID_CPU_X86_FEATURE_AVX2 = (1 8), ANDROID_CPU_X86_FEATURE_SHA_NI = (1 9), };对于手写 SIMD 优化路径的库,这套接口非常有价值:应用启动时检测特性标志,选择对当前 CPU 最高效的代码分支。11.3 NDK 构建集成AOSP 中 NDK 构建集成由build/soong/cc/下 4 个核心 Go 源文件实现:文件行数用途ndk_library.go662桩共享库生成ndk_headers.go280头文件安装到 sysrootndk_sysroot.go321sysroot 组装单例ndk_abi.go102ABI 导出与差异监控11.3.1ndk_library模块类型 ¶ndk_library是生成 NDK 桩库的核心构建原语。每个 NDK 库成对定义:一个ndk_library模块生成桩库,一个cc_library_shared模块实现真实逻辑。桩库给应用开发者链接使用;真实库运行在设备上。模块实现在build/soong/cc/ndk_library.go的NdkLibraryFactory():// build/soong/cc/ndk_library.go (658‑662行) func NdkLibraryFactory() android.Module { module := newStubLibrary() android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth) return module }属性说明ndk_library支持的属性:// build/soong/cc/ndk_library.go (95‑123行) type libraryProperties struct { // 符号映射文件相对路径。 Symbol_file *string `android:"path"` // 库首次可用的API等级。 First_version *string // 开始应用版本脚本的首个API等级。 Unversioned_until *string // 如果开启,允许该库全部符号可以在纯原生应用进程调用(参见11.6.5节)。 // 仅用于不依赖Android Runtime的库;否则应在符号映射中对单个符号添加artless标记。 Bypass_artless_denylist *bool // 禁止使用 // NDK库不应该导出自身头文件。 Export_header_libs []string }Bypass_artless_denylist是 Android 17 新增属性,和同版本工具链新增的artless符号标签配套。artless 含义:无 Android Runtime,代表可以在不启动 JVM 的纯原生应用进程调用(见 11.6.5 节)。默认每个ndk_library会生成黑名单桩库,拦截不适合该类进程的符号;设置bypass_artless_denylist: true会清空黑名单,声明整个库都可以用于纯原生场景。也可以在.map.txt中给单个符号打上artless标签做细粒度控制。默认拒绝是因为绝大多数 NDK 入口会调用 Android Runtime,在无 JVM 进程中调用会失败;Bionic、liblog 这类完全不依赖运行时的库会标记为 artless。11.8.2 节会再讲解黑名单的构建逻辑。symbol_file指向.map.txt文件,记录全部导出符号以及对应 API 等级,是 NDK 接口集的唯一事实来源。例如libcamera2ndk.map.txt记录 Camera NDK 全部函数以及它们的引入版本。first_version指定生成桩库的最低 API 等级。构建系统会为从first_version到当前版本,外加一个future版本,分别生成独立的桩库。桩库生成流程桩库生成入口是stubDecorator的compile()方法:// build/soong/cc/ndk_library.go (488‑521行) func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects { if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") { ctx.PropertyErrorf("symbol_file", "must end with .map.txt") } // ... symbolFile := String(c.properties.Symbol_file) nativeAbiResult := ParseNativeAbiDefin
返回列表