- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
导读
Operit 应用中有两类 native 库不经过 Gradle/CMake 构建,而是由独立工具链脚本生成后复制进工程:Rust 编译的liboperit_ripgrep.so(提供代码搜索能力)与本地构建导入的 FFmpegKit AAR(提供音视频处理能力)。本指南讲解如何通过注册一个专用校验任务并挂接到preBuild,在打包开始前检查这些外部产物的存在性与非空性,一旦缺失立即让 APK/AAB 构建失败并给出指向生成脚本的错误信息。读完本文,你将掌握"对构建链之外的 native 输入做打包前置门禁"的完整 Gradle 实现方案及其在 Operit 中的真实落地细节。
背景:构建链之外的 native 输入为何会"静默缺失"
在典型的 Android 工程中,native 库由 CMake/NDK 或第三方 AAR 提供,Android Gradle Plugin(AGP)能感知其构建与打包过程。但 Operit 中存在两类特殊的 native 输入,它们完全独立于 Gradle 的构建图之外:
liboperit_ripgrep.so:由 tools/native_ripgrep/build_native_ripgrep.ps1 使用 Rust 工具链编译,再复制到应用 JNI 目录app/src/main/jniLibs/<abi>/liboperit_ripgrep.so;- FFmpegKit:由 tools/ffmpeg/build_ffmpeg_kit_wsl.sh 在 WSL 中独立构建,经 tools/ffmpeg/import_local_ffmpeg_kit.ps1 导入为
app/libs/ffmpeg-kit-local.aar,并以implementation(files("libs/ffmpeg-kit-local.aar"))方式参与依赖(见 app/build.gradle.kts)。
问题在于:当这些输入缺失(例如 clone 后未运行生成脚本、CI 环境未同步构建产物、或产物被误清理)时,AGP 依然可以顺利完成打包——因为 Gradle 不知道这些文件本应存在。构建出的 APK/AAB 在运行时才会因为System.loadLibrary失败或 FFmpegKit 初始化失败而崩溃,问题被推迟到发布之后才发现。
相关实施背景与作用域说明见 native-library-package-verification/index.md:该方案只覆盖"在 Gradle 之外构建并复制到应用 JNI 目录的产物",对缺失或零长度的 native 文件立即中止打包。
校验设计意图:三个明确边界
根据 01-external-native-library-input-check.md,校验任务的设计意图可以归纳为:
- 检查对象明确:
preBuild依赖一个专用校验任务,该任务检查 Rust 编译产物app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so,并读取 FFmpegKit AAR,确认其中 10 个 arm64 native 库均为非空条目; - 失败即中止打包:
liboperit_ripgrep.so、FFmpegKit AAR 或其 arm64 native 条目缺失或大小为零时,应用打包失败; - 错误信息可操作:错误消息指向相应的生成或导入脚本,开发者能据此快速定位修复动作;
- 作用域克制:不约束 CMake、AAR 或子模块提供的 native 库——即不影响其他由 Gradle 构建图正常管理的 native 输入。
实现剖析:verifyExternallyBuiltNativeLibraries任务
校验清单的声明
任务首先声明了两个校验清单(见 app/build.gradle.kts):
val requiredExternallyBuiltNativeLibraries = listOf( file("src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so"), ) val ffmpegKitLocalAar = file("libs/ffmpeg-kit-local.aar") val requiredFfmpegKitArm64Libraries = setOf( "jni/arm64-v8a/libavcodec.so", "jni/arm64-v8a/libavdevice.so", "jni/arm64-v8a/libavfilter.so", "jni/arm64-v8a/libavformat.so", "jni/arm64-v8a/libavutil.so", "jni/arm64-v8a/libc++_shared.so", "jni/arm64-v8a/libffmpegkit.so", "jni/arm64-v8a/libffmpegkit_abidetect.so", "jni/arm64-v8a/libswresample.so", "jni/arm64-v8a/libswscale.so", )两点值得注意:路径统一使用相对于app/模块目录的写法(src/main/jniLibs/...、libs/...),与 Gradle 中file()的解析基准一致;而 FFmpegKit 的 10 个条目声明为AAR 内部的 zip 条目路径(jni/arm64-v8a/...),因为 AAR 本质上是 zip 归档,native 库位于其jni/<abi>/目录下。
任务注册与增量行为控制
任务本体(见 app/build.gradle.kts)通过inputs.property声明输入,并设置outputs.upToDateWhen { false }强制每次执行:
val verifyExternallyBuiltNativeLibraries by tasks.registering { description = "Checks native libraries built outside Gradle before Android packaging." group = "verification" inputs.property( "requiredLibraries", requiredExternallyBuiltNativeLibraries.map { library -> library.path }, ) inputs.property("ffmpegKitAar", ffmpegKitLocalAar.path) inputs.property("ffmpegKitArm64Libraries", requiredFfmpegKitArm64Libraries) outputs.upToDateWhen { false } doLast { // ... 校验逻辑 } }outputs.upToDateWhen { false }是这里的关键细节:校验任务没有输出产物,若不显式禁用 up-to-date 判定,Gradle 在增量构建时可能跳过它,导致门禁失效。声明输入属性也能让 Gradle 在依赖文件变化时正确触发重跑。
校验逻辑:文件存在性 + zip 条目完整性
doLast内分三步执行(见 app/build.gradle.kts):
第一步:检查 ripgrep 产物
val invalidLibraries = requiredExternallyBuiltNativeLibraries.filter { library -> !library.isFile || library.length() == 0L } require(invalidLibraries.isEmpty()) { "Missing or empty externally built native library: " + invalidLibraries.joinToString { library -> library.path } + ". Run tools/native_ripgrep/build_native_ripgrep.ps1 before packaging." }isFile判定文件存在且非目录,length() == 0L判定零长度——两者任一不满足即视为非法,直接抛出带生成脚本路径的错误。
第二步:检查 AAR 本身
require(ffmpegKitLocalAar.isFile && ffmpegKitLocalAar.length() > 0L) { "Missing or empty FFmpegKit AAR: ${ffmpegKitLocalAar.path}. " + "Build it with tools/ffmpeg/build_ffmpeg_kit_wsl.sh and import it with " + "tools/ffmpeg/import_local_ffmpeg_kit.ps1 before packaging." }这里同时提示了构建(WSL 脚本)与导入(PowerShell 脚本)两个步骤,覆盖完整的产物获取链路。
第三步:深入 AAR 内部检查条目
ZipFile(ffmpegKitLocalAar).use { archive -> val invalidEntries = requiredFfmpegKitArm64Libraries.filter { entryName -> val entry = archive.getEntry(entryName) entry == null || entry.size <= 0L } require(invalidEntries.isEmpty()) { "FFmpegKit AAR is missing or contains empty arm64 native libraries: " + invalidEntries.joinToString() } }AAR 文件本身存在不等于内容完整——构建脚本可能生成了残缺的归档。因此这里用java.util.zip.ZipFile打开归档,逐一查找 10 个jni/arm64-v8a/*.so条目,任何条目缺失或size <= 0L都判定失败。use { }保证归档流被正确关闭。
挂接到preBuild
任务注册完成后,通过dependsOn挂到preBuild生命周期任务上(见 app/build.gradle.kts):
tasks.named("preBuild") { dependsOn(syncMainAssets) dependsOn(verifyExternallyBuiltNativeLibraries) }preBuild是所有 Android 打包任务(assembleDebug、assembleRelease、assembleNightly、bundleRelease等)的共同前置,挂在这里可以保证无论走哪条打包路径,门禁都会生效——校验失败时整个构建立即中止,不会产出缺库的 APK/AAB。
外部产物的真实生成链路
Rust ripgrep:build_native_ripgrep.ps1
脚本 tools/native_ripgrep/build_native_ripgrep.ps1 的完整流程为:
- 解析参数:
Targets(默认aarch64-linux-android)、SdkDir、ApiLevel(默认 23); - 定位 Android SDK:依次读取
local.properties中的sdk.dir、ANDROID_HOME、ANDROID_SDK_ROOT,找不到则抛错; - 定位 NDK:优先
ANDROID_NDK_HOME/ANDROID_NDK_ROOT,否则在 SDK 的ndk/目录下按版本号降序选取最新版; - ABI 映射:将 Rust target 映射到 Android ABI——
aarch64-linux-android → arm64-v8a、armv7-linux-androideabi → armeabi-v7a、x86_64-linux-android → x86_64、i686-linux-android → x86,并为每个 target 绑定对应版本的 NDK clang 链接器(通过CARGO_TARGET_<TARGET>_LINKER环境变量); - 编译复制:
cargo build --release --target <target>后,将target/<target>/release/liboperit_ripgrep.so复制到app/src/main/jniLibs/<abi>/liboperit_ripgrep.so。
Rust 侧的 JNI 实现位于 tools/native_ripgrep/src/lib.rs,导出 Kotlin 侧调用的searchJson方法;运行时通过 NativeRipgrep.kt 的System.loadLibrary("operit_ripgrep")加载——这也是产物缺失时运行时崩溃的直接原因。校验任务检查的正是这条链路最末端、也是唯一会进入 APK 的产物。
FFmpegKit:build_ffmpeg_kit_wsl.sh+import_local_ffmpeg_kit.ps1
WSL 构建脚本 tools/ffmpeg/build_ffmpeg_kit_wsl.sh 负责在 Linux 环境构建 ffmpeg-kit Android 产物:它仅保留 arm64 架构(--disable-arm-v7a、--disable-x86、--disable-x86-64),以--api-level=24编译,并启用 fontconfig、freetype、fribidi、gmp、gnutls、lame、libass、libvpx、libwebp、opus、dav1d 等一批编解码与滤镜库——这正是 AAR 中会出现 libavcodec、libavformat、libswresample 等 10 个 arm64 库的原因。
导入脚本 tools/ffmpeg/import_local_ffmpeg_kit.ps1 则负责:自动在 WSL 中定位构建产物 AAR(优先~/build/ffmpeg-kit/prebuilt/bundle-android-aar,其次android/ffmpeg-kit-android-lib/build/outputs/aar),复制为app/libs/ffmpeg-kit-local.aar,并清理残留的旧ffmpegkit.jar与jniLibs下的旧libffmpeg*.so文件,防止多版本混杂。
运行时侧,FFmpegKit 由 StandardFFmpegTool.kt 与 FFmpegUtil.kt 通过FFmpegKit.execute、FFprobeKit等 API 调用,校验任务保证这些 API 依赖的 10 个 so 一定存在于最终包内。
失败场景与排查指引
当校验不通过时,Gradle 构建会在preBuild阶段直接失败,根据错误消息可以快速定位修复动作:
| 失败场景 | 错误消息要点 | 修复动作 |
|---|---|---|
liboperit_ripgrep.so缺失或为空 | Missing or empty externally built native library: ... | 运行tools/native_ripgrep/build_native_ripgrep.ps1 |
ffmpeg-kit-local.aar缺失或为空 | Missing or empty FFmpegKit AAR: ... | 运行tools/ffmpeg/build_ffmpeg_kit_wsl.sh后执行tools/ffmpeg/import_local_ffmpeg_kit.ps1 |
| AAR 内 10 个 arm64 条目缺失或为零长 | FFmpegKit AAR is missing or contains empty arm64 native libraries: ... | 重新构建并重新导入 AAR,确认构建产物完整 |
典型排查场景包括:新克隆仓库后未执行产物生成脚本、CI 缓存未包含构建产物、手工清理了app/libs或app/src/main/jniLibs目录。
验证方式与适用范围
该功能的完成状态已在 01-external-native-library-input-check.md 标注为 [DONE]:校验任务已注册在应用 Gradle 脚本中并接入preBuild。可在本地验证的方式:
- 临时将
app/src/main/jniLibs/arm64-v8a/liboperit_ripgrep.so改名或移出(注意:仓库为只读,验证应在本地工作副本进行),执行./gradlew :app:preBuild,应看到指向build_native_ripgrep.ps1的错误并中止; - 临时替换
app/libs/ffmpeg-kit-local.aar为空文件,重复上述命令,应看到指向两个 FFmpeg 脚本的错误。
需要明确的两个适用范围前提:
- 仅约束 arm64-v8a:校验清单只覆盖
arm64-v8aABI 的 ripgrep 产物与 FFmpegKit 的jni/arm64-v8a/条目,这与 build_ffmpeg_kit_wsl.sh 仅保留 arm64 的架构策略一致,也与 app/build.gradle.kts 中jniLibs { useLegacyPackaging = true }的打包设置对应; - 不干预 Gradle 管理的 native 输入:CMake 构建的库、其他 AAR 与子模块提供的 native 库由 AGP 自身保证,不属于该校验任务的职责范围,不会因本任务产生误报。
从设计上看,这套方案把"运行时才暴露的缺库问题"前移为"构建时立即失败",并以可操作的错误消息引导开发者完成产物生成,是处理"构建链之外 native 输入"的一类通用实践。
- AI Agent
- 人工智能
- 大模型
- AI 应用
- 工具调用
- 本地部署
- MCP Clients
- Agent 记忆
【免费下载链接】Operit
The most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent
相关推荐
Operit 外部原生库打包校验:在 preBuild 阶段拦截缺失的 Rust ripgrep 与 FFmpegKit AAR
Operit 外部原生库打包校验:在 preBuild 阶段拦截缺失的 Rust ripgrep 与 FFmpegKit AAR 本文基于 Operit 仓库
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Operit 构建期自动同步 STT 模型 assets:manifest 驱动、SHA-256 校验的 Gradle 生成资产管线
Operit 构建期自动同步 STT 模型 assets:manifest 驱动、SHA 256 校验的 Gradle 生成资产管线 导读 本文讲解 Operi
AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化深入 Apache Kafka 内部 API 检查器:KIP-1265 构建期字节码校验插件机制与 Gradle/Maven 实战
深入 Apache Kafka 内部 API 检查器:KIP 1265 构建期字节码校验插件机制与 Gradle/Maven 实战 Kafka 从 KIP 12
后端消息队列流处理大数据
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考