前言
在 OpenHarmony 的分布式能力矩阵中,除了分布式软总线(DSoftBus) 和 分布式数据管理(DDM) ,还有一个高阶特性------跨设备任务无缝流转(Continuation)。它允许用户在一个设备上启动任务(如编辑文档、看视频),然后"接力"到另一个设备继续操作,体验如同在同一台设备上。
然而,Flutter 作为跨平台 UI 框架,并不原生支持 Continuation 机制 。本文将深入剖析 OpenHarmony Continuation 的工作原理,并手把手教你如何在 Flutter 应用中实现从手机到平板的"待办事项编辑"无缝流转,同时结合软总线完成状态同步与设备发现。
这是目前社区首篇完整实现 Flutter + OpenHarmony Continuation 的实战教程。

一、什么是 Continuation?
Continuation 是 OpenHarmony 提供的一种跨设备任务迁移能力,其核心流程如下:
- 源设备 (如手机)调用
continueAbility(); - 系统通过软总线向目标设备(如平板)发送迁移请求;
- 目标设备收到请求后,启动相同 Ability(或指定 Ability);
- 源设备传递上下文数据(如当前编辑内容、页面状态);
- 目标设备恢复任务,用户无感知切换。
⚠️ 注意: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'];
}
}
并在 MaterialApp 的 home 中根据 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();
})
五、测试流程
- 在手机上打开 Flutter 应用,进入待办编辑页;
- 点击"流转"按钮,选择已配对的平板;
- 手机调用
continueAbility(),系统弹出确认框; - 平板自动启动应用,并恢复编辑页面与输入内容;
- 手机端可选择退出或保持后台。
✅ 效果:用户感觉"任务从手机飞到了平板",毫无割裂感。
六、注意事项与限制
| 问题 | 说明 | 解决方案 |
|---|---|---|
| Flutter 无法直接控制 Ability 生命周期 | Continuation 必须由原生 Ability 发起 | 通过 MethodChannel 代理调用 |
| 状态序列化复杂 | 需手动将 Widget 状态转为 JSON | 使用统一状态管理(如 Riverpod + toJson) |
| 仅支持同应用流转 | 不能跨 BundleName | 确保多端安装同一签名应用 |
| 设备需在线且信任 | 否则无法触发 | 提前完成设备配对 |
七、总结
本文实现了 Flutter 应用在 OpenHarmony 上的三大分布式能力融合:
- 软总线:设备发现与通信;
- 分布式数据管理:状态持久化与同步;
- Continuation:任务无缝流转。
这标志着 Flutter 应用在 OpenHarmony 生态中,已具备构建真正分布式用户体验 的能力。虽然目前仍需较多桥接代码,但随着 OpenHarmony Flutter Plugin 生态 的完善,未来有望一键集成。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。