OpenHarmony + Flutter 离线能力构建指南:打造无网可用的高可靠政务/工业应用

引言

在边防巡检、电力抢修、野外勘探、基层政务等场景中,网络不可用是常态而非例外。然而,多数 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 应用将具备 军工级离线韧性,从容应对最严苛的野外与应急场景。


相关推荐
松☆1 小时前
OpenHarmony + Flutter 多语言与国际化(i18n)深度适配指南:一套代码支持中英俄等 10+ 语种
android·javascript·flutter
晚霞的不甘1 小时前
Flutter 与开源鸿蒙(OpenHarmony)性能调优与生产部署实战:从启动加速到线上监控的全链路优化
flutter·开源·harmonyos
AskHarries1 小时前
Flutter + Supabase 接入 Google 登录
flutter
晚霞的不甘1 小时前
架构演进与生态共建:构建面向 OpenHarmony 的 Flutter 原生开发范式
flutter·架构
Ya-Jun1 小时前
架构设计模式:依赖注入最佳实践
flutter
松☆2 小时前
Flutter + OpenHarmony 构建工业巡检 App:离线采集、多端协同与安全上报
安全·flutter
●VON2 小时前
Flutter for OpenHarmony前置知识《Flutter 路由与导航完整教程》
学习·flutter·华为·openharmony·开源鸿蒙
天意__2 小时前
Flutter开发,scroll_to_index适配flutter_list_view
前端·flutter
Ya-Jun2 小时前
架构设计模式:模块化设计方案
flutter