

引言
在边防巡检、电力抢修、野外勘探、基层政务等场景中,网络不可用是常态而非例外。然而,多数 OpenHarmony + Flutter 应用仍严重依赖在线服务,一旦断网即"瘫痪"------无法登录、不能提交表单、地图空白、数据丢失。
要构建真正高可靠的国产化应用,必须实现 全链路离线能力:
- 身份认证离线化(支持离线登录);
- 业务数据本地持久化与同步;
- 静态资源(UI、地图、文档)预置;
- 网络恢复后自动回传队列。
本文将提供一套 端到端离线架构方案,结合 OpenHarmony 的分布式能力与 Flutter 的跨端优势,打造"有网增强、无网可用"的韧性应用,并附完整代码示例。
一、离线能力全景设计
┌───────────────────────────────────────────────────────┐
│ Flutter App (Dart) │
├───────────────┬───────────────┬───────────────────────┤
│ 离线登录 │ 本地数据库 │ 静态资源管理 │
│ - Token 缓存 │ - Hive / Moor │ - AssetBundle 预加载 │
│ - 生物认证 │ - 冲突解决 │ - 动态资源包下载 │
└───────▲───────┴───────▲───────┴───────────▲───────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ OpenHarmony 离线支撑层 (ArkTS) │
├───────────────┬───────────────┬───────────────────────┤
│ @ohos.account │ @ohos.data.rdb│ @ohos.file.fs │
│ .distributed │ /relational │ │
│ │ │ │
└───────▲───────┴───────▲───────┴───────────▲───────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ 设备本地存储(SQLite / 文件系统) │
│ + 分布式软总线(SoftBus)备用同步通道 │
└───────────────────────────────────────────────────────┘
✅ 核心理念 :所有关键功能必须能在无网络、无服务器情况下运行;网络仅用于增强与同步。
二、离线登录:支持无网身份验证
问题
传统 JWT Token 过期后需联网刷新 → 断网即登出。
✅ 解决方案:长有效期 Token + 本地生物认证二次校验
步骤 1:登录时缓存凭证(Dart)
dart
// lib/services/auth_service.dart
class AuthService {
static const String _tokenKey = 'offline_token';
static const String _userIdKey = 'user_id';
static const Duration _longExpiry = Duration(days: 30);
Future<void> login(String username, String password) async {
// 1. 联网认证(首次)
final response = await http.post('/api/login', body: {'username': username, 'password': password});
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
// 2. 保存长期 Token(加密存储,见前文安全篇)
await SecureStorage().write(_tokenKey, data['token']);
await SecureStorage().write(_userIdKey, data['userId']);
// 3. 注册生物认证(如指纹)
await BiometricAuth.register();
}
}
// 离线登录:验证本地 Token + 生物特征
Future<bool> offlineLogin() async {
final token = await SecureStorage().read(_tokenKey);
if (token == null) return false;
// 检查 Token 是否在有效期内(宽松策略)
final payload = Jwt.parse(token);
if (DateTime.now().isAfter(payload.expiry.add(_longExpiry))) {
return false; // 需重新联网
}
// 生物认证二次确认(防设备丢失)
final authResult = await BiometricAuth.authenticate();
return authResult.success;
}
}
🔐 安全提示:Token 存储需结合前文《安全加固》中的 TEE 加密方案。
三、本地数据库:结构化数据持久化
选型对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Hive | 纯 Dart、零依赖、快 | 无关系查询 | 配置、缓存 |
| Moor (Drift) | SQL 支持、类型安全 | 需 native 编译 | 复杂业务数据 |
| OpenHarmony RDB | 系统级、高性能 | 需 ArkTS 封装 | 高频读写 |
✅ 推荐组合 :Hive(轻量) + OpenHarmony RDB(核心业务)
示例:使用 OpenHarmony RDB 存储巡检记录(ArkTS)
typescript
// model/InspectionDb.ts
import relationalStore from '@ohos.data.relationalStore';
export class InspectionDb {
private store: relationalStore.RdbStore | null = null;
async init(context: any): Promise<void> {
const config = relationalStore.createRdbStoreConfig('inspection.db');
this.store = await relationalStore.getRdbStore(context, config);
// 创建表
await this.store.executeSql(`
CREATE TABLE IF NOT EXISTS inspections (
id TEXT PRIMARY KEY,
location TEXT,
status TEXT,
timestamp INTEGER,
synced BOOLEAN DEFAULT 0
)
`);
}
async saveInspection(inspection: any): Promise<void> {
const values = new relationalStore.ValuesBucket();
values.putString('id', inspection.id);
values.putString('location', inspection.location);
values.putString('status', inspection.status);
values.putLong('timestamp', Date.now());
values.putBoolean('synced', false);
await this.store!.insert('inspections', values);
}
async getUnsyncedInspections(): Promise<any[]> {
const resultSet = await this.store!.querySql(
'SELECT * FROM inspections WHERE synced = 0'
);
const list: any[] = [];
while (resultSet.goToNextRow()) {
list.push({
id: resultSet.getString(resultSet.getColumnIndex('id')),
location: resultSet.getString(resultSet.getColumnIndex('location')),
status: resultSet.getString(resultSet.getColumnIndex('status')),
timestamp: resultSet.getLong(resultSet.getColumnIndex('timestamp'))
});
}
resultSet.close();
return list;
}
async markAsSynced(id: string): Promise<void> {
const values = new relationalStore.ValuesBucket();
values.putBoolean('synced', true);
await this.store!.update(values, 'id = ?', [id]);
}
}
Dart 层调用(通过 MethodChannel)
dart
// lib/repositories/inspection_repo.dart
class InspectionRepository {
static const _channel = MethodChannel('com.example.inspection_db');
Future<void> save(Inspection inspection) async {
await _channel.invokeMethod('save', inspection.toJson());
}
Future<List<Inspection>> getUnsynced() async {
final List<dynamic> rawList = await _channel.invokeMethod('getUnsynced');
return rawList.map((e) => Inspection.fromJson(e)).toList();
}
Future<void> markSynced(String id) async {
await _channel.invokeMethod('markSynced', {'id': id});
}
}
四、静态资源离线化:UI、地图、文档预置
1. Flutter Asset 预加载
将图片、字体、JSON 配置打包进 HAP:
yaml
# pubspec.yaml
flutter:
assets:
- assets/offline_maps/
- assets/forms/
- assets/icons/
运行时直接读取:
dart
final ByteData data = await rootBundle.load('assets/forms/patrol_template.json');
2. 动态资源包按需下载
对超大资源(如城市离线地图),启动时检测并下载:
dart
// lib/services/resource_manager.dart
class ResourceManager {
Future<void> ensureOfflineMap(String city) async {
final file = File('/data/storage/el2/base/haps/entry/files/maps/$city.dat');
if (!await file.exists()) {
// 从内网 CDN 下载(有网时)
final response = await http.get('http://intranet-cdn/maps/$city.dat');
await file.writeAsBytes(response.bodyBytes);
}
}
}
📌 路径说明 :OpenHarmony 应用私有目录为
/data/storage/el2/base/haps/entry/files/,无需额外权限。
五、网络状态感知与自动同步
步骤 1:监听网络变化(ArkTS)
typescript
// model/NetworkMonitor.ts
import net from '@ohos.net.connection';
export class NetworkMonitor {
private callback: () => void;
constructor(callback: () => void) {
this.callback = callback;
}
start(): void {
const conn = net.getDefaultNet();
conn.on('netAvailable', () => {
console.log('🌐 网络已恢复');
this.callback(); // 触发同步
});
conn.on('netUnavailable', () => {
console.log('📴 网络断开');
});
}
}
步骤 2:Dart 层实现同步队列
dart
// lib/services/sync_service.dart
class SyncService {
final InspectionRepository _repo = InspectionRepository();
Future<void> syncAll() async {
final unsynced = await _repo.getUnsynced();
for (final item in unsynced) {
try {
// 上传到服务器
await http.post('/api/inspections', body: item.toJson());
// 标记为已同步
await _repo.markSynced(item.id);
} catch (e) {
// 网络再次中断?保留未同步状态
break;
}
}
}
}
// 在 main.dart 中监听
void main() {
runApp(MyApp());
// 注册网络恢复回调
const EventChannel('com.example.network/events')
.receiveBroadcastStream()
.listen((_) => SyncService().syncAll());
}
六、利用 SoftBus 实现 P2P 离线协同(高级场景)
当多台设备同处无网环境(如野外作业队),可通过 OpenHarmony 软总线实现设备间数据交换:
typescript
// ArkTS: 发送未同步数据到附近设备
async function shareViaSoftBus(peerDeviceId: string) {
const unsynced = await inspectionDb.getUnsyncedInspections();
const message = JSON.stringify(unsynced);
await softbus.sendMessage(peerDeviceId, message); // 前文 SoftBus 插件
}
接收方解析后存入本地数据库,待任一设备联网即可统一回传。
🌐 价值:构建"去中心化"离线协作网络,极大提升野外作业效率。
七、测试与验证:模拟无网环境
1. DevEco Studio 网络控制
- 设备 > 网络 > 关闭 Wi-Fi/蜂窝数据;
- 使用 Network Profiler 验证无外发请求。
2. 自动化测试脚本
dart
test('离线登录成功', () async {
// 模拟无网
HttpOverrides.runZoned(() {}, createHttpClient: (_) => MockHttpClient());
final success = await AuthService().offlineLogin();
expect(success, true);
});
class MockHttpClient extends HttpClient {
@override
Future<HttpClientRequest> openUrl(String method, Uri url) {
throw SocketException('No network'); // 强制断网
}
}
八、总结:离线能力 Checklist
| 能力 | 是否实现 | 验证方式 |
|---|---|---|
| 离线登录 | ✅ | 关闭网络后仍可进入主界面 |
| 本地数据存储 | ✅ | 重启应用后数据不丢失 |
| 静态资源可用 | ✅ | 无网时地图/表单正常显示 |
| 自动同步队列 | ✅ | 恢复网络后数据自动上传 |
| P2P 协同(可选) | ⚠️ | 多设备无网环境下数据互通 |
💡 终极目标:用户完全感知不到"在线"与"离线"的边界------这才是真正的高可靠应用。
通过本文方案,你的 OpenHarmony + Flutter 应用将具备 军工级离线韧性,从容应对最严苛的野外与应急场景。