尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Dart FFI 在 Android 上的落地实践:用 NDK 构建 SQLite 并集成到 Flutter 应用

发布时间:2026/9/26 8:34:18

资讯中心
01
ARTICLE

Dart FFI 在 Android 上的落地实践:用 NDK 构建 SQLite 并集成到 Flutter 应用

Dart FFI 在 Android 上的落地实践:用 NDK 构建 SQLite 并集成到 Flutter 应用
编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载本文对应 Dart SDK 仓库samples/ffi/sqlite示例中的 Android 使用指南。围绕“如何在 Android 设备上通过dart:ffi加载原生库”这一核心问题完整讲解从 NDK 交叉编译 SQLite、Gradle 打包.so、到 Dart 中DynamicLibrary.open加载并指定--target-platform的端到端流程并补充本仓库示例的源码级佐证帮助你一次性跑通「FFI 原生库 → Flutter App」的完整链路。dart:ffiForeign Function Interface让 Dart 可以不借助任何中间层直接调用 C 函数、读写 C 内存。但 FFI 本身只解决“运行时如何调用”库从哪来、以什么 ABI 编译、如何打进 App 包是 Flutter 开发者绕不开的工程问题。本指南以 SQLite 为例给出 Android 平台上一套可复制的构建与打包方案。适用前提本文面向 Linux 工作站 Android SDK含 NDK Flutter 工程示例代码来自 Dart SDK 仓库 samples/ffi/sqlite。文中命令与配置以该仓库文档为基准不同版本的 NDK/SQLite 路径可能略有差异。为什么 Android 上不能直接使用系统自带的 SQLite每个 Android 设备出厂都自带一份 SQLite 动态库位于/system/lib/libsqlite.so。直觉上既然设备上有这份.so直接DynamicLibrary.open加载即可。但实际上从 Android 7.0Nougat开始应用无法直接加载/system/lib下的库NDK 相关的系统库仅允许通过 Java API 间接访问系统库的 ABI、编译选项、接口细节由 ROM 厂商决定不受应用控制无法保证与你的 FFI 绑定一致。因此正确做法是用 NDK 自行编译一份与目标 ABI 匹配的 SQLite 共享库打进应用包随 App 一起分发。这也是本文后续所有步骤的出发点。前置准备理解 Android ABI 与 Flutter 的 --target-platform构建原生库之前必须先明确目标 ABIApplication Binary Interface。ABI 决定了机器码指令集、函数调用约定与库文件格式库必须与目标 ABI 严格匹配才能被加载。ABI常见设备NDK 编译器前缀示例Flutter--target-platform取值arm64-v8a现代 ARM 64 位手机/平板aarch64-linux-android24-clangandroid-arm64armeabi-v7a旧 32 位 ARM 设备armv7a-linux-androideabi24-clangandroid-armx86_64x86 模拟器/部分设备x86_64-linux-android24-clangandroid-x64x86老式模拟器i686-linux-android24-clangandroid-x86在 Flutter 中目标 ABI 由flutter命令的--target-platform参数控制。编译哪个 ABI 的.so运行/构建 App 时就要传对应的参数二者必须一致——这是本文反复强调的关键约束。第一步用 NDK 从源码编译 SQLite 共享库1. 获取 SQLite amalgamation 源码从 SQLite 官网下载页获取amalgamation合并版源码。所谓 amalgamation是把整个 SQLite 源码合并成sqlite3.c/sqlite3.h等少数几个文件的单文件版本非常适合外部集成与交叉编译。为便于叙述下文假设文件已保存为sqlite-amalgamation-XXXXXXX.zip且 Android SDK含 NDK 扩展位于~/Android。2. 解压并用 NDK 交叉编译在 Linux 工作站上执行unzip sqlite-amalgamation-XXXXXXX.zip cd sqlite-amalgamation-XXXXXXX ~/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -c sqlite3.c -o sqlite3.o ~/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ld -shared sqlite3.o -o libsqlite3.so两条命令说明第一条使用aarch64-linux-android24-clang将sqlite3.c编译为目标文件sqlite3.o。其中aarch64前缀表明这是为arm64-v8aABI编译24是 minSdkVersion 对应的 Android API 级别android24即 Android 7.0NDK 的 sysroot 按 API 级别划分。第二条使用aarch64-linux-android-ld将目标文件链接为共享库libsqlite3.so-shared是关键参数FFI 只能加载共享库。注意这里刻意选用了aarch64前缀即只产出arm64-v8a一个 ABI 的库。如果 App 需要覆盖多种设备则需要为每个目标 ABI 各编译一份并分别放入对应的 ABI 子目录见下一步。3. 产物校验编译完成后应得到一个libsqlite3.so文件。可以用file命令快速确认目标架构file libsqlite3.so # 预期输出中应包含 aarch64 字样例如: ELF 64-bit LSB shared object, ARM aarch64如果架构不符例如编出了 x86_64 的库却用于 arm64 设备运行时DynamicLibrary.open将失败——这一步排查成本远高于编译时的确认成本。第二步通过 Gradle 将 .so 打包进 Flutter 应用原生库编译好后还需要告诉 Gradle“把这个库打进 App”这样它在运行时才存在于设备上、可被加载。1. 创建 native-libraries 目录在 Flutter 应用的根目录创建一个native-libraries文件夹注意是应用根目录不是android/目录。2. 修改 android/app/build.gradle在android/app/build.gradle中把该目录注册为 Gradle 的jniLibs源目录android { // ... sourceSets { main { jniLibs.srcDir ${project.projectDir.path}/../../native-libraries } } }project.projectDir.path指向android/app../../上溯到应用根目录最终定位到根目录下的native-libraries。jniLibs.srcDir是 Gradle 约定的原生库打包入口其中的.so文件会被原样打进 APK 的lib/abi/路径下。3. 按 ABI 组织库文件Gradle 打包时native-libraries内的库按 ABI 子目录组织native-libraries/ └── arm64-v8a/ └── libsqlite3.so因此把上一步编译出的libsqlite3.so复制到native-libraries/arm64-v8a/libsqlite3.so即可。若存在多个 ABI 子目录与目标 ABI 对应的那个子目录里的库会出现在应用运行时的链接路径中Dart 侧即可用DynamicLibrary.open(libsqlite3.so)直接按名字加载无需写完整路径。第三步在 Dart 中加载原生库并绑定函数打包完成后Dart 侧的标准加载方式是DynamicLibrary.openimport dart:ffi; final DynamicLibrary sqlite DynamicLibrary.open(libsqlite3.so);DynamicLibrary.open的实现位于 sdk/lib/ffi/dynamic_library.dart它按平台约定查找动态库。本仓库示例对“平台差异”做了封装dylib_utils.dart 中的dlopenPlatformSpecific会根据平台拼接库文件名String _platformPath(String name, String path) { if (Platform.isLinux || Platform.isAndroid || Platform.isFuchsia) return path lib name .so; if (Platform.isMacOS) return path lib name .dylib; if (Platform.isWindows) return path name .dll; throw Exception(Platform not implemented); }注意Android 属于 “libname.so” 分支因此在 Android 上dlopenPlatformSpecific(sqlite3)等价于DynamicLibrary.open(libsqlite3.so)——与打包进 APK 的库文件名libsqlite3.so正好对应。拿到DynamicLibrary后就可以通过lookupNativeFunctionT查找 C 函数、再asFunction()转成 Dart 可调用的函数。示例的绑定封装在 bindings.dartC 侧函数签名定义在 signatures.dart例如sqlite3_open_v2 sqlite .lookupNativeFunctionsqlite3_open_v2_native_t(sqlite3_open_v2) .asFunction();第四步以正确的 --target-platform 运行或构建既然libsqlite3.so是为arm64-v8aABI 编译的运行/构建 App 时必须显式指定目标平台让 Flutter 生成与之匹配的原生产物flutter run --target-platformandroid-arm64 # 或构建 release 包 flutter build apk --target-platformandroid-arm64如果省略该参数或传了不匹配的值例如库只有arm64-v8a一份却以android-arm运行App 在运行时将找不到对应 ABI 的libsqlite3.soDynamicLibrary.open会抛出加载失败异常。验证与测试仓库示例给出的完整链路上述步骤并非孤立流程Dart SDK 仓库 samples/ffi/sqlite 提供了完整的 SQLite FFI 示例桌面端可先直接跑通逻辑再迁移到 Android 链路构建与运行dart run example/main预期输出包含 Cookie 表的查询结果、The result has already been closed.关闭后访问结果集的异常以及no such column: non_existing_column (Code 1: SQL logic error)等完整示例代码见 example/main.dart。自动化测试test/sqlite_test.dart 覆盖了建表/插入/查询、并发打开多个数据库连接、100 次循环写入的 stress test以及 UTF-8 字符串往返转换。FFI 底层实现加载库与lookup的机制参见 sdk/lib/ffi/ffi.dartSQLite 句柄/语句对象被建模为PointerVoid的扩展类见 bindings/types.dartDatabase/Result/Row的对象化封装见 database.dart。资源释放约定示例用ClosableIterator抽象“带底层资源、需要手动 close 的迭代器”见 closable_iterator.dart遍历完所有行时自动finalize语句提前中断遍历则必须显式close()。桌面端跑通后把其中的dlopenPlatformSpecific(sqlite3)替换为 Android 上的DynamicLibrary.open(libsqlite3.so)再配合本文第一第四步的构建、打包、传参流程即可在真机上复现同样的查询行为。常见问题排查现象可能原因处理建议DynamicLibrary.open抛Failed to load dynamic library未打包.so、ABI 不匹配、--target-platform与库 ABI 不一致检查 APK 内lib/abi/libsqlite3.so是否存在核对编译器前缀与--target-platform运行时报UnsatisfiedLinkError/ 找不到库native-libraries未注册进jniLibs.srcDir或.so未放入对应 ABI 子目录复查build.gradle的sourceSets配置与目录结构桌面端可运行、Android 端失败桌面用系统 SQLitelibsqlite3.so系统库Android 需自带确认 Android 构建产物已通过 Gradle 打包多 ABI 设备覆盖不全只编了单一 ABI 的.so为每个目标 ABI 分别编译并放入对应子目录构建时逐 ABI 传参小结在 Android 上使用dart:ffi本质是四件事的串联NDK 按目标 ABI 编译原生库 → Gradle 将.so按 ABI 目录打包 → Dart 用DynamicLibrary.open按库名加载 →flutter以匹配的--target-platform运行。其中“ABI 一致性”贯穿始终编译器前缀、native-libraries/abi/目录、--target-platform三者必须指向同一个 ABI。本文所有配置与命令均可直接复用到其他原生库BoringSSL、zlib 等的 Android FFI 集成中桌面端Linux/macOS/Windows的加载差异已由示例的dlopenPlatformSpecific封装处理。若需要从零理解 FFI 绑定、C 内存管理、对象化 API 设计的完整思路可继续阅读配套教程 sqlite-tutorial.md。赞分享编程语言编译器语言运行时标准库开发工具【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址https://gitcode.com/gh_mirrors/sdk1/sdk点击查看免费下载相关推荐如何用 cargo ndk 构建 AppFlowy 的 Android SDK 并拷贝 jniLibs 到 Flutter 工程如何用 cargo ndk 构建 AppFlowy 的 Android SDK 并拷贝 jniLibs 到 Flutter 工程 当你需要为 Android 平前端后端企业应用内容协同知识管理AI 应用Flutter应用集成SQLite向量搜索Dart调用sqlite-vec完整指南Flutter应用集成SQLite向量搜索Dart调用sqlite vec完整指南 在现代移动应用开发中向量搜索技术正成为构建智能应用的关键能力。sqlit向量数据库数据库Signal-Android NDK应用C代码在Android中的集成Signal Android NDK应用C代码在Android中的集成 Signal作为一款注重隐私保护的即时通讯应用在Android平台上通过NDK上一篇AnythingLLM OCR技术深度解析如何实现企业级文档智能识别下一篇终极AnythingLLM OCR集成打造高效文档智能处理系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。