
引言
在政务、电力、物流、巡检等强线下场景中,网络不稳定是常态。用户需要:
- ✅ 无网时仍可操作(如填写表单、拍照、记录轨迹);
- ✅ 有网时自动同步数据到云端;
- ✅ 多端设备间数据不冲突。
Flutter 虽有 sqflite 等数据库插件,但在 OpenHarmony 上无法直接使用 (依赖 Android/iOS 原生库)。而 OpenHarmony 提供了强大的 关系型数据库(RDB) 和 分布式能力,却缺乏跨平台 UI 支持。
本文将教你如何:
🔧 在 Flutter 中通过 MethodChannel 调用 OpenHarmony RDB
🔄 设计"本地优先 + 后台同步"架构
🛡️ 实现断点续传、冲突检测、数据加密
📦 构建一个 "电力巡检离线填报系统" ,支持 无网录入 → 自动同步 → 多端一致。
所有代码基于 OpenHarmony API 10(4.1 SDK) + Flutter 3.19 + Riverpod,已在 RK3568 工业平板实测。
一、为什么不能直接用 sqflite?
| 插件 | 依赖 | OpenHarmony 支持 |
|---|---|---|
sqflite |
Android SQLiteOpenHelper / iOS FMDB |
❌ 不兼容 |
drift |
同上 + Dart 编译器 | ❌ 无法编译 |
hive |
纯 Dart | ✅ 可用,但无事务、无关系模型 |
🚫 结论 :必须使用 OpenHarmony 原生 RDB,并通过 MethodChannel 暴露给 Flutter。
二、整体架构:三层数据流
┌───────────────────┐
│ Flutter (Dart) │
│ - UI 表单 │
│ - 调用 data_channel│ ← MethodChannel
└─────────▲─────────┘
│
┌─────────┴─────────┐
│ OpenHarmony (ArkTS)│
│ - 封装 RDB 操作 │
│ - 同步任务队列 │
│ - 网络状态监听 │
└─────────▲─────────┘
│
┌─────────┴─────────┐
│ OpenHarmony RDB │
│ (SQLite 内核) │
└───────────────────┘
│
┌─────────▼─────────┐
│ 云端 API │
│ (有网时自动同步) │
└───────────────────┘
✅ 核心原则:
- 所有数据先写本地 RDB;
- 同步由后台服务异步完成;
- Flutter 只关心"成功/失败",不关心网络细节。
三、Step 1:定义数据模型(巡检任务)
业务需求
- 每个任务包含:ID、设备编号、检查项、照片路径、状态(草稿/已提交)、时间戳;
- 支持批量提交;
- 提交后不可编辑。
OpenHarmony RDB 表结构(ArkTS)
typescript
// model/InspectionTask.ts
import rdb from '@ohos.data.relationalStore';
export const STORE_CONFIG: rdb.StoreConfig = {
name: 'inspection.db',
securityLevel: rdb.SecurityLevel.S1, // 加密存储
};
export const CREATE_TABLE_SQL = `
CREATE TABLE IF NOT EXISTS inspection_task (
id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
items TEXT NOT NULL, -- JSON 字符串
photo_paths TEXT, -- 多图逗号分隔
status TEXT DEFAULT 'draft',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
sync_status TEXT DEFAULT 'pending' -- pending / synced / failed
);
`;
🔐 安全提示 :
securityLevel: S1启用 数据库文件级加密(需设备支持)。
四、Step 2:封装 RDB 操作(ArkTS)
typescript
// service/DatabaseService.ts
import rdb from '@ohos.data.relationalStore';
import { STORE_CONFIG, CREATE_TABLE_SQL } from '../model/InspectionTask';
export class DatabaseService {
private store: rdb.RdbStore | null = null;
async init(): Promise<void> {
this.store = await rdb.getRdbStore(globalThis.context, STORE_CONFIG);
await this.store.executeSql(CREATE_TABLE_SQL);
}
// 插入或更新任务
async saveTask(task: any): Promise<void> {
const values = new rdb.ValuesBucket();
values.put('id', task.id);
values.put('device_id', task.deviceId);
values.put('items', JSON.stringify(task.items));
values.put('photo_paths', task.photoPaths?.join(',') || '');
values.put('status', task.status);
values.put('created_at', task.createdAt ?? Date.now());
values.put('updated_at', Date.now());
values.put('sync_status', 'pending');
await this.store!.insertOrReplace('inspection_task', values);
}
// 获取所有待同步任务
async getPendingTasks(): Promise<any[]> {
const predicates = new rdb.RdbPredicates('inspection_task');
predicates.equalTo('sync_status', 'pending');
const resultSet = await this.store!.query(predicates);
const tasks: any[] = [];
while (resultSet.goToNextRow()) {
tasks.push({
id: resultSet.getString(resultSet.getColumnIndex('id')),
deviceId: resultSet.getString(resultSet.getColumnIndex('device_id')),
items: JSON.parse(resultSet.getString(resultSet.getColumnIndex('items'))),
photoPaths: resultSet.getString(resultSet.getColumnIndex('photo_paths'))?.split(',') || [],
status: resultSet.getString(resultSet.getColumnIndex('status')),
createdAt: resultSet.getLong(resultSet.getColumnIndex('created_at')),
updatedAt: resultSet.getLong(resultSet.getColumnIndex('updated_at')),
});
}
resultSet.close();
return tasks;
}
// 标记任务为已同步
async markSynced(taskId: string): Promise<void> {
const values = new rdb.ValuesBucket();
values.put('sync_status', 'synced');
const predicates = new rdb.RdbPredicates('inspection_task')
.equalTo('id', taskId);
await this.store!.update(values, predicates);
}
}
五、Step 3:MethodChannel 暴露给 Flutter
typescript
// EntryAbility.ts
import { DatabaseService } from './service/DatabaseService';
import { SyncManager } from './service/SyncManager'; // 后文定义
export default class EntryAbility extends UIAbility {
private dbService: DatabaseService | null = null;
private syncManager: SyncManager | null = null;
async onCreate() {
// 初始化数据库
this.dbService = new DatabaseService();
await this.dbService.init();
// 初始化同步管理器
this.syncManager = new SyncManager(this.dbService);
// 注册通道
this.engine.setMethodCallHandler('com.example.data/task',
async (call, result) => {
try {
if (call.method === 'saveTask') {
await this.dbService!.saveTask(call.arguments);
result.success(true);
// 触发后台同步(非阻塞)
this.syncManager!.triggerSync();
}
else if (call.method === 'getLocalTasks') {
const tasks = await this.dbService!.getPendingTasks();
result.success(tasks);
}
} catch (e) {
result.error('DB_ERROR', e.message, null);
}
}
);
}
}
六、Step 4:Flutter 端调用与状态管理
dart
// lib/providers/inspection_provider.dart
final inspectionDataProvider = Provider<InspectionData>((ref) {
return InspectionData._();
});
class InspectionData {
static const _channel = MethodChannel('com.example.data/task');
Future<void> saveTask({
required String id,
required String deviceId,
required List<Map<String, dynamic>> items,
List<String>? photoPaths,
}) async {
await _channel.invokeMethod('saveTask', {
'id': id,
'deviceId': deviceId,
'items': items,
'photoPaths': photoPaths,
'status': 'draft',
});
}
Future<List<Task>> getLocalTasks() async {
final rawList = await _channel.invokeMethod('getLocalTasks') as List;
return rawList.map((json) => Task.fromJson(json)).toList();
}
}
@immutable
class Task {
final String id;
final String deviceId;
final List<Map<String, dynamic>> items;
final List<String> photoPaths;
final String status;
final int createdAt;
final int updatedAt;
Task.fromJson(Map<String, dynamic> json)
: id = json['id'],
deviceId = json['deviceId'],
items = List<Map<String, dynamic>>.from(json['items']),
photoPaths = List<String>.from(json['photoPaths'] ?? []),
status = json['status'],
createdAt = json['createdAt'],
updatedAt = json['updatedAt'];
}
UI 使用示例
dart
// lib/pages/task_form_page.dart
class TaskFormPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Form(
child: Column(
children: [
// 表单项...
ElevatedButton(
onPressed: () async {
// 保存到本地
await ref.read(inspectionDataProvider).saveTask(
id: DateTime.now().millisecondsSinceEpoch.toString(),
deviceId: 'DEV_001',
items: [{'name': '电压', 'value': '220V'}],
photoPaths: ['/data/.../photo1.jpg'],
);
Navigator.pop(context);
},
child: Text('保存草稿'),
)
],
),
),
);
}
}
七、Step 5:实现后台自动同步(关键!)
同步策略设计
| 场景 | 行为 |
|---|---|
| 有网 + 应用前台 | 立即同步 |
| 有网 + 应用后台 | 延迟 5 分钟同步(省电) |
| 无网 | 等待网络恢复 |
| 同步失败 | 重试 3 次,标记为 failed |
SyncManager 实现(ArkTS)
typescript
// service/SyncManager.ts
import net from '@ohos.net.connection';
import http from '@ohos.net.http';
export class SyncManager {
private db: DatabaseService;
private isSyncing = false;
constructor(db: DatabaseService) {
this.db = db;
this.listenNetwork();
}
private listenNetwork() {
const connMgr = net.getConnectionManager();
connMgr.getDefaultNet().then(netHandle => {
netHandle.on('netAvailable', () => {
console.log('网络恢复,触发同步');
this.triggerSync();
});
});
}
async triggerSync() {
if (this.isSyncing) return;
this.isSyncing = true;
try {
const tasks = await this.db.getPendingTasks();
for (const task of tasks) {
const success = await this.uploadTask(task);
if (success) {
await this.db.markSynced(task.id);
} else {
// TODO: 记录失败次数,超过3次标记为 failed
}
}
} finally {
this.isSyncing = false;
}
}
private async uploadTask(task: any): Promise<boolean> {
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(
'https://api.yourcompany.com/inspection/sync',
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify(task)
}
);
return response.responseCode === 200;
} catch {
return false;
} finally {
httpRequest.destroy();
}
}
}
⚡ 性能优化 :使用
http.createHttp()而非 WebView,减少内存开销。
八、安全与可靠性增强
1. 数据加密
- RDB 启用
SecurityLevel.S1; - 敏感字段(如照片路径)在传输前 AES 加密。
2. 事务保证
typescript
// 批量操作使用事务
await store!.executeBatch([
{ sql: 'INSERT INTO ...', bindArgs: [...] },
{ sql: 'UPDATE ...', bindArgs: [...] }
]);
3. 存储配额监控
typescript
import filemanager from '@ohos.file.fs';
const stat = await filemanager.stat('/data/storage/el2/base/haps/entry/databases/inspection.db');
if (stat.size > 100 * 1024 * 1024) {
// 触发清理策略
}
九、调试技巧
-
查看数据库内容:
bashhdc shell sqlite3 /data/storage/el2/base/haps/entry/databases/inspection.db "SELECT * FROM inspection_task;" -
模拟断网:
- 在 DevEco Studio 关闭模拟器网络;
- 或真机开启飞行模式。
-
日志跟踪同步状态:
bashhdc logcat | grep -E "(SyncManager|RDB)"
十、总结:打造真正可用的离线应用
通过本文,你已掌握:
✅ 在 Flutter 中安全调用 OpenHarmony RDB
✅ 设计"本地优先"数据架构
✅ 实现智能后台同步机制
✅ 保障数据安全与一致性
💼 适用场景:
- 电力/水务/燃气巡检
- 物流签收
- 政务外勤
- 工业点检
在国产化替代浪潮下,离线能力是行业应用的生命线。掌握此模式,你将能构建真正可靠、安全、高效的 OpenHarmony 行业解决方案。