Flutter/OpenHarmony 三方库适配实战:sqflite 数据库
Flutter/OpenHarmony 三方库适配实战:sqflite 数据库
一、项目信息
| 项目 | 内容 |
|---|---|
| 仓库名称 | sqflite |
| 仓库地址 | https://atomgit.com/oh-flutter/sqflite |
| 基于版本 | sqflite 2.4.2 |
| 适配 TAG | 2.4.2-ohos-1.0.0 |
| OHOS 插件包 | sqflite_ohos |
| OHOS 原生入口 | SqflitePlugin |
| Dart 注册类 | SqfliteOhos |
| 原生执行模型 | BinaryMessenger + ThreadWorker + RDB |
sqflite 是 Flutter 生态中最常用的 SQLite 插件之一。业务代码通常直接使用
openDatabase()、Database.execute()、Batch 和 transaction(),并不关心数据库
最终由 Android 的 SQLite、iOS 的 SQLite,还是 OpenHarmony 的 RDB 承载。
因此,本次适配的核心目标不是重写一套数据库 API,而是让原有的
sqflite_platform_interface 契约在 OpenHarmony 上得到完整实现:
- Dart 侧继续使用
sqflite的公开 API。 - OHOS 侧使用
@ohos.data.relationalStore执行 SQL。 - 数据库路径、打开、关闭、查询、更新、删除、批处理和事务保持原有语义。
- 大 SQL 和批处理不能长时间占用平台主线程。
- 插件销毁时释放 Worker、RDB Store 和待回复消息。
二、整体适配架构
本次适配分为四层:
业务 Dart 代码
|
v
sqflite / sqflite_common
|
v
sqflite_platform_interface
|
v
SqfliteOhos(Dart 插件注册)
|
v
BinaryMessenger(未解码 ArrayBuffer)
|
v
SqflitePlugin(主线程仅负责分发)
|
v
SqfliteWorker(解码 SQL、调用 RDB、编码结果)
|
v
@ohos.data.relationalStore
这个结构最重要的一点是:MethodChannel 的标准处理流程会在平台主线程解码
MethodCall。当一次批处理包含大量 SQL 时,消息体可能非常大,主线程解码和执行
很容易触发长时间卡顿。适配版本改用 BinaryMessenger.setMessageHandler() 获取
未解码的 ArrayBuffer,主线程只分配序号并转发消息,实际解码和数据库操作放入
ArkTS Worker。
三、让 Flutter 找到 OHOS 实现
根包声明 OHOS 默认实现:
flutter:
plugin:
platforms:
android:
default_package: sqflite_android
ios:
default_package: sqflite_darwin
macos:
default_package: sqflite_darwin
ohos:
default_package: sqflite_ohos
sqflite_ohos/pubspec.yaml 声明原生插件类和 Dart 注册类:
name: sqflite_ohos
version: 2.4.2
flutter:
plugin:
platforms:
ohos:
package: io.flutter.plugins.sqflite
pluginClass: SqflitePlugin
dartPluginClass: SqfliteOhos
Dart 注册类将 OHOS 实现接入统一平台接口:
import 'package:sqflite_platform_interface/sqflite_platform_interface.dart';
class SqfliteOhos extends SqflitePlatform {
static void registerWith() {
SqflitePlatform.initWithDatabaseFactoryMethodChannel();
}
}
这样业务代码仍然通过同一个 DatabaseFactory 和 Database 抽象工作,不需要为
OpenHarmony 单独封装一套数据库调用。
四、主线程只做消息分发
SqflitePlugin 在引擎附加时创建 Worker,并维护消息序号与回复对象的映射:
export default class SqflitePlugin implements FlutterPlugin {
private sqfliteWorker: worker.ThreadWorker | null = null;
private pending: Map<number, BinaryReply> = new Map<number, BinaryReply>();
private seq: number = 0;
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.messenger = binding.getBinaryMessenger();
this.sqfliteWorker = new worker.ThreadWorker('./SqfliteWorker.ets', {
name: 'SqfliteWorker',
});
this.sqfliteWorker.onmessage = (e: MessageEvents): void => {
const data = e.data as SqfliteReply;
const reply = this.pending.get(data.seq);
if (reply != null) {
reply.reply(data.result);
this.pending.delete(data.seq);
}
};
}
}
原始消息处理器只做三件事:
const rawHandler: BinaryMessageHandler = {
onMessage: (message: ArrayBuffer, reply: BinaryReply): void => {
const id: number = ++this.seq;
this.pending.set(id, reply);
this.sqfliteWorker?.postMessage(
{ seq: id, call: message, context: this.context },
[message],
);
},
};
这里使用 seq 而不是“最新一条回复”作为关联键,可以避免并发请求互相串号。
未处理的消息不会在平台线程解码,也不会直接调用 RdbStore。
引擎分离时必须终止旧 Worker 并清空回复映射,防止热重启后产生重复 Worker:
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.messenger?.setMessageHandler(Constant.SQFLITE_CHANNEL, null);
this.sqfliteWorker?.terminate();
this.sqfliteWorker = null;
this.pending.clear();
}
五、Worker 中完成 SQL 解码与执行
Worker 收到原始消息后,先用标准方法解码器还原 MethodCall,再按方法名分发:
async function dispatch(
call: MethodCall,
result: MethodResult,
context: common.Context,
): Promise<void> {
switch (call.method) {
case Constant.METHOD_OPEN_DATABASE:
await DatabaseHelper.openDatabase(context, call, result);
break;
case Constant.METHOD_GET_DATABASES_PATH:
handleGetDatabasesPathCall(context, result);
break;
case Constant.METHOD_EXECUTE:
await handleExecuteCall(call, result);
break;
case Constant.METHOD_INSERT:
await handleInsertCall(call, result);
break;
case Constant.METHOD_QUERY:
await handleQueryCall(call, result);
break;
case Constant.METHOD_BATCH:
await handleBatchCall(call, result);
break;
case Constant.METHOD_CLOSE_DATABASE:
await handleCloseDatabaseCall(call, result);
break;
default:
result.notImplemented();
}
}
查询路径先根据路径取得对应 RdbStore,再创建 MethodCallOperation:
async function handleQueryCall(
call: MethodCall,
result: MethodResult,
): Promise<void> {
const rdbStore = DatabaseHelper.getDatabaseOrError(call, result);
if (rdbStore == null) {
return;
}
const operation = new MethodCallOperation(call, result);
await Database.query(operation, rdbStore);
}
Operation、BatchOperation 和 MethodCallOperation 将原库的参数转换成
RDB 的参数数组,并把查询结果转换为 Flutter 可识别的 Map/List。这个转换层是
兼容性的关键,尤其需要注意 null、整数、浮点数、Blob 和字符串的类型保持。
六、数据库目录与业务用法
OHOS 实现把应用数据库目录返回给 Dart:
function handleGetDatabasesPathCall(
context: common.Context,
result: MethodResult,
): void {
const appContext = context.getApplicationContext();
result.success(appContext.databaseDir + '/rdb');
}
业务代码无需改动:
import 'package:path/path.dart' as p;
import 'package:sqflite/sqflite.dart';
Future<void> demo() async {
final String path = p.join(await getDatabasesPath(), 'demo.db');
final Database db = await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute(
'CREATE TABLE user(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
);
},
);
final int id = await db.insert('user', <String, Object?>{
'name': 'Alice',
'age': 18,
});
final List<Map<String, Object?>> rows = await db.query(
'user',
where: 'id = ?',
whereArgs: <Object?>[id],
);
print(rows);
await db.close();
}
参数化查询必须继续使用占位符,避免通过字符串拼接生成 SQL:
await db.query(
'user',
where: 'age >= ? AND name != ?',
whereArgs: <Object?>[18, 'Bob'],
);
事务中应使用回调传入的 txn:
await db.transaction((txn) async {
await txn.insert('user', <String, Object?>{
'name': 'Tom',
'age': 19,
});
await txn.update(
'user',
<String, Object?>{'age': 20},
where: 'name = ?',
whereArgs: <Object?>['Tom'],
);
});
七、构建、安装与验证
执行静态分析:
flutter analyze --no-pub
结果:未发现问题。
示例应用已构建并安装到 OpenHarmony 模拟器。示例首页包含 Raw tests、Open tests、
Type tests、Batch tests、Todo database example、Exception tests 和 IO tests 等
入口,可以分别验证 SQL、数据库生命周期、值类型和异常处理。

截图中的 Sqflite demo 页面来自 sqflite/example,说明 OHOS 插件已经成功注册,
Dart 示例页面可以正常加载并进入数据库测试入口。
八、需要注意的能力边界
- 当前实现聚焦 OpenHarmony RDB,不等同于桌面端对任意 SQLite 动态库的加载能力。
- 加密数据库、SQLCipher 和外部自定义 SQLite 构建不在本次适配范围内。
- Worker 能避免大 SQL 阻塞平台主线程,但业务仍应控制超大事务和不必要的全表查询。
- 返回的查询结果是跨通道序列化后的新对象,业务不应依赖原生 RDB 对象。
- 真机、多窗口、后台销毁和异常断电场景仍应在业务上线前补充测试。
如果安装 HAP 时提示 9568289,应检查应用是否申请了超出当前应用等级的权限。
数据库本身不需要该权限,错误通常来自示例或宿主工程的其他权限声明。
九、代码提交与协作
本次适配已经推送到 AtomGit main 分支,并创建 TAG:
main: e7ad98de849b16038f6f5815f29891e2840a5d0e
tag: 2.4.2-ohos-1.0.0
业务工程可以按 README 中的 TAG 引用适配版本:
dependencies:
sqflite:
git:
url: https://atomgit.com/oh-flutter/sqflite.git
ref: 2.4.2-ohos-1.0.0
path: sqflite
欢迎加入 CPF-Flutter 鸿蒙社区,参与
Flutter OHOS 插件适配、HAP 构建和模拟器/真机验证经验交流。
更多推荐



所有评论(0)