终极挑战:Flutter 应用在 OpenHarmony 上实现跨设备无缝流转(Continuation)与软总线协同

前言

在 OpenHarmony 的分布式能力矩阵中,除了分布式软总线(DSoftBus)分布式数据管理(DDM) ,还有一个高阶特性------跨设备任务无缝流转(Continuation)。它允许用户在一个设备上启动任务(如编辑文档、看视频),然后"接力"到另一个设备继续操作,体验如同在同一台设备上。

然而,Flutter 作为跨平台 UI 框架,并不原生支持 Continuation 机制 。本文将深入剖析 OpenHarmony Continuation 的工作原理,并手把手教你如何在 Flutter 应用中实现从手机到平板的"待办事项编辑"无缝流转,同时结合软总线完成状态同步与设备发现。

这是目前社区首篇完整实现 Flutter + OpenHarmony Continuation 的实战教程


一、什么是 Continuation?

Continuation 是 OpenHarmony 提供的一种跨设备任务迁移能力,其核心流程如下:

  1. 源设备 (如手机)调用 continueAbility()
  2. 系统通过软总线向目标设备(如平板)发送迁移请求;
  3. 目标设备收到请求后,启动相同 Ability(或指定 Ability);
  4. 源设备传递上下文数据(如当前编辑内容、页面状态);
  5. 目标设备恢复任务,用户无感知切换。

⚠️ 注意:Continuation 要求两端运行同一应用(BundleName 相同),且已建立信任关系。


二、整体架构设计

复制代码
+------------------+       +------------------+
|   手机 (Source)  |       |   平板 (Target)  |
|                  |       |                  |
|  Flutter App     |       |  Flutter App     |
|  - UI + State    |       |  - UI + State    |
+--------+---------+       +--------+---------+
         |                          |
   MethodChannel              MethodChannel
         |                          |
+--------v---------+       +--------v---------+
|  OHOS Native     |<----->|  OHOS Native     |
|  - Continuation  | DSoftBus | - Continuation  |
|  - KVStore Sync  |<------>| - KVStore Sync  |
+------------------+       +------------------+

关键点:

  • Continuation 由原生 Ability 触发,Flutter 无法直接调用;
  • 需通过 MethodChannel 通知原生层发起流转
  • 流转完成后,目标设备需通知 Flutter 恢复状态
  • 同时保留 KVStore 同步 作为兜底机制。

三、原生侧:实现 Continuation 能力

1. 配置 module.json5 支持 Continuation

json 复制代码
{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "continuable": true, // 关键:启用流转
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          }
        ]
      }
    ],
    "requestPermissions": [
      { "name": "ohos.permission.CONTINUATION_AUTHORIZATION" },
      { "name": "ohos.permission.DISTRIBUTED_DATASYNC" }
    ]
  }
}

2. 实现 EntryAbility.ets

ts 复制代码
// ets/entryability/EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import continuationManager from '@ohos.continuationManager';
import type { Callback } from '@ohos.base';

export default class EntryAbility extends UIAbility {
  private static instance: EntryAbility;

  onCreate() {
    EntryAbility.instance = this;
    console.info('[Ability] onCreate');
  }

  // 【关键】注册 Continuation 回调
  onWindowStageCreate(windowStage) {
    continuationManager.setContinuationCallback(this.context, {
      onContinue: (wantParam) => {
        console.info('[Continuation] onContinue called');
        // 可在此决定是否允许流转(如检查设备类型)
        return true;
      },

      onSaveData: (callback: Callback<string>) => {
        console.info('[Continuation] onSaveData');
        // 通知 Flutter 获取当前状态并序列化
        SoftBusPlugin.getInstance().requestAppState((state: string) => {
          callback.resolve(state); // 返回给目标设备
        });
      },

      onRestoreData: (restoreData: string, callback: Callback<boolean>) => {
        console.info('[Continuation] onRestoreData:', restoreData);
        // 将恢复数据传递给 Flutter
        SoftBusPlugin.getInstance().restoreAppState(restoreData);
        callback.resolve(true);
      },

      onComplete: () => {
        console.info('[Continuation] Transfer completed');
        // 源设备可选择退出或保持后台
      }
    });
  }

  // 提供给插件调用的方法
  static getInstance(): EntryAbility {
    return EntryAbility.instance;
  }

  startContinuation(deviceId: string): boolean {
    try {
      continuationManager.continueAbility(deviceId);
      return true;
    } catch (err) {
      console.error('[Ability] continueAbility failed:', err);
      return false;
    }
  }
}

3. 扩展 SoftBusPlugin.ets 支持 Continuation

ts 复制代码
// plugins/SoftBusPlugin.ets
import { EntryAbility } from '../entryability/EntryAbility';

class SoftBusPlugin {
  private static instance: SoftBusPlugin;
  private appStateCallback: ((state: string) => void) | null = null;
  private restoreCallback: ((data: string) => void) | null = null;

  static getInstance(): SoftBusPlugin {
    if (!SoftBusPlugin.instance) {
      SoftBusPlugin.instance = new SoftBusPlugin();
    }
    return SoftBusPlugin.instance;
  }

  init() {
    // ... 原有 MethodChannel / EventChannel 初始化
  }

  // 【新增】Flutter 请求当前应用状态(用于流转)
  requestAppState(callback: (state: string) => void) {
    this.appStateCallback = callback;
    // 通过 EventChannel 通知 Dart 层
    if (this.eventSink) {
      this.eventSink.success({ type: 'request_app_state' });
    }
  }

  // 【新增】恢复应用状态
  restoreAppState(data: string) {
    if (this.restoreCallback) {
      this.restoreCallback(data);
    }
  }

  // 暴露给 MethodChannel 的方法
  handleMethodCall(call: any): Promise<any> {
    switch (call.method) {
      // ... 其他方法

      case 'startContinuation':
        const deviceId = call.arguments['deviceId'];
        const success = EntryAbility.getInstance().startContinuation(deviceId);
        return Promise.resolve({ success });

      case 'sendAppState': // Flutter 主动上报状态
        if (this.appStateCallback) {
          this.appStateCallback(call.arguments['state']);
          this.appStateCallback = null;
        }
        return Promise.resolve({ success: true });

      default:
        return Promise.reject('Unknown method');
    }
  }

  setOnRestore(callback: (data: string) => void) {
    this.restoreCallback = callback;
  }
}

四、Dart 侧:Flutter 应用集成

1. 扩展 ContinuationService

dart 复制代码
// lib/services/continuation_service.dart
import 'package:flutter/services.dart';

class ContinuationService {
  static const _method = MethodChannel('com.example.flutter/continuation/method');
  static const _event = EventChannel('com.example.flutter/continuation/event');

  // 发起流转
  static Future<bool> startContinuation(String deviceId) async {
    final result = await _method.invokeMethod('startContinuation', {'deviceId': deviceId});
    return result['success'] == true;
  }

  // 上报当前应用状态(JSON 字符串)
  static Future<void> sendAppState(String state) async {
    await _method.invokeMethod('sendAppState', {'state': state});
  }

  // 监听原生层事件(如请求状态、恢复状态)
  static Stream<Map<String, dynamic>> watchEvents() {
    return _event.receiveBroadcastStream().map((e) => e as Map);
  }
}

2. 在 UI 中触发流转

dart 复制代码
// 在设备列表页添加"流转"按钮
void _showContinuationDialog(BuildContext context, String deviceId) {
  showDialog(
    context: context,
    builder: (_) => AlertDialog(
      title: Text('流转到此设备?'),
      actions: [
        TextButton(onPressed: Navigator.of(context).pop, child: Text('取消')),
        TextButton(
          onPressed: () async {
            // 序列化当前页面状态
            final state = jsonEncode({
              'currentScreen': 'todo_edit',
              'editingTodoId': _editingId,
              'inputText': _controller.text,
            });
            await ContinuationService.sendAppState(state);
            final success = await ContinuationService.startContinuation(deviceId);
            if (success) {
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('正在流转...')));
            }
            Navigator.of(context).pop();
          },
          child: Text('流转'),
        )
      ],
    ),
  );
}

3. 处理状态恢复

dart 复制代码
// 在 main.dart 初始化时监听恢复事件
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 监听 Continuation 恢复
  ContinuationService.watchEvents().listen((event) {
    if (event['type'] == 'restore_app_state') {
      final data = event['data'] as String;
      final json = jsonDecode(data);
      // 根据状态跳转页面或恢复输入框内容
      GlobalAppState.restoreFromJson(json);
    }
  });

  runApp(MyApp());
}

// 全局状态管理类(简化)
class GlobalAppState {
  static String? currentScreen;
  static String? editingTodoId;
  static String? inputText;

  static void restoreFromJson(Map<String, dynamic> json) {
    currentScreen = json['currentScreen'];
    editingTodoId = json['editingTodoId'];
    inputText = json['inputText'];
  }
}

并在 MaterialApphome 中根据 GlobalAppState 跳转:

dart 复制代码
home: Builder(builder: (context) {
  if (GlobalAppState.currentScreen == 'todo_edit') {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // 异步恢复编辑状态
      Navigator.push(context, MaterialPageRoute(builder: (_) => TodoEditPage(
        todoId: GlobalAppState.editingTodoId,
        initialText: GlobalAppState.inputText ?? '',
      )));
    });
  }
  return HomePage();
})

五、测试流程

  1. 在手机上打开 Flutter 应用,进入待办编辑页;
  2. 点击"流转"按钮,选择已配对的平板;
  3. 手机调用 continueAbility(),系统弹出确认框;
  4. 平板自动启动应用,并恢复编辑页面与输入内容;
  5. 手机端可选择退出或保持后台。

✅ 效果:用户感觉"任务从手机飞到了平板",毫无割裂感。


六、注意事项与限制

问题 说明 解决方案
Flutter 无法直接控制 Ability 生命周期 Continuation 必须由原生 Ability 发起 通过 MethodChannel 代理调用
状态序列化复杂 需手动将 Widget 状态转为 JSON 使用统一状态管理(如 Riverpod + toJson)
仅支持同应用流转 不能跨 BundleName 确保多端安装同一签名应用
设备需在线且信任 否则无法触发 提前完成设备配对

七、总结

本文实现了 Flutter 应用在 OpenHarmony 上的三大分布式能力融合

  1. 软总线:设备发现与通信;
  2. 分布式数据管理:状态持久化与同步;
  3. Continuation:任务无缝流转。

这标志着 Flutter 应用在 OpenHarmony 生态中,已具备构建真正分布式用户体验 的能力。虽然目前仍需较多桥接代码,但随着 OpenHarmony Flutter Plugin 生态 的完善,未来有望一键集成。


欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

相关推荐
庄雨山2 小时前
Flutter Bloc 状态管理深度解析与开源鸿蒙 ArkUI 对标分析
flutter·bloc·openharmonyos
晚霞的不甘2 小时前
Flutter + OpenHarmony 发布与运维指南:从上架 AppGallery 到线上监控的全生命周期管理
运维·flutter·harmonyos
安卓开发者2 小时前
第三课:Widget核心概念剖析 - Flutter界面构建的基石
flutter
遝靑2 小时前
Flutter 状态管理深度解析:从 Provider 到 Riverpod,再到 Bloc(附选型指南)
flutter
晚霞的不甘2 小时前
Flutter + OpenHarmony 插件开发指南:深度集成原生能力,打造高性能鸿蒙扩展
flutter·华为·harmonyos
她说彩礼65万2 小时前
WPF SynchronizationContext的使用
wpf
云雾J视界2 小时前
分布式AI框架选型困局:SintolRTOS vs Ray vs Horovod,性能压测全解析
tensorflow·wpf·horovod·ray·分布式ai·sintolrtos
庄雨山3 小时前
Flutter Provider 状态管理深度解析与开源鸿蒙 ArkUI 状态管理对比
flutter·provider·openharmonyos
song5013 小时前
鸿蒙 Flutter CI/CD 进阶:Jenkins + 鸿蒙打包自动化流程
分布式·python·flutter·3d·ci/cd·分类