前言
在 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
{"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
// ets/entryability/EntryAbility.etsimportUIAbilityfrom'@ohos.app.ability.UIAbility';importcontinuationManagerfrom'@ohos.continuationManager';importtype{Callback}from'@ohos.base';exportdefaultclassEntryAbilityextendsUIAbility{privatestaticinstance:EntryAbility;onCreate(){EntryAbility.instance=this;console.info('[Ability] onCreate');}// 【关键】注册 Continuation 回调onWindowStageCreate(windowStage){continuationManager.setContinuationCallback(this.context,{onContinue:(wantParam)=>{console.info('[Continuation] onContinue called');// 可在此决定是否允许流转(如检查设备类型)returntrue;},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);// 将恢复数据传递给 FlutterSoftBusPlugin.getInstance().restoreAppState(restoreData);callback.resolve(true);},onComplete:()=>{console.info('[Continuation] Transfer completed');// 源设备可选择退出或保持后台}});}// 提供给插件调用的方法staticgetInstance():EntryAbility{returnEntryAbility.instance;}startContinuation(deviceId:string):boolean{try{continuationManager.continueAbility(deviceId);returntrue;}catch(err){console.error('[Ability] continueAbility failed:',err);returnfalse;}}}3. 扩展SoftBusPlugin.ets支持 Continuation
// plugins/SoftBusPlugin.etsimport{EntryAbility}from'../entryability/EntryAbility';classSoftBusPlugin{privatestaticinstance:SoftBusPlugin;privateappStateCallback:((state:string)=>void)|null=null;privaterestoreCallback:((data:string)=>void)|null=null;staticgetInstance():SoftBusPlugin{if(!SoftBusPlugin.instance){SoftBusPlugin.instance=newSoftBusPlugin();}returnSoftBusPlugin.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':constdeviceId=call.arguments['deviceId'];constsuccess=EntryAbility.getInstance().startContinuation(deviceId);returnPromise.resolve({success});case'sendAppState':// Flutter 主动上报状态if(this.appStateCallback){this.appStateCallback(call.arguments['state']);this.appStateCallback=null;}returnPromise.resolve({success:true});default:returnPromise.reject('Unknown method');}}setOnRestore(callback:(data:string)=>void){this.restoreCallback=callback;}}四、Dart 侧:Flutter 应用集成
1. 扩展ContinuationService
// lib/services/continuation_service.dartimport'package:flutter/services.dart';classContinuationService{staticconst_method=MethodChannel('com.example.flutter/continuation/method');staticconst_event=EventChannel('com.example.flutter/continuation/event');// 发起流转staticFuture<bool>startContinuation(String deviceId)async{finalresult=await_method.invokeMethod('startContinuation',{'deviceId':deviceId});returnresult['success']==true;}// 上报当前应用状态(JSON 字符串)staticFuture<void>sendAppState(String state)async{await_method.invokeMethod('sendAppState',{'state':state});}// 监听原生层事件(如请求状态、恢复状态)staticStream<Map<String,dynamic>>watchEvents(){return_event.receiveBroadcastStream().map((e)=>easMap);}}2. 在 UI 中触发流转
// 在设备列表页添加“流转”按钮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{// 序列化当前页面状态finalstate=jsonEncode({'currentScreen':'todo_edit','editingTodoId':_editingId,'inputText':_controller.text,});awaitContinuationService.sendAppState(state);finalsuccess=awaitContinuationService.startContinuation(deviceId);if(success){ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text('正在流转...')));}Navigator.of(context).pop();},child:Text('流转'),)],),);}3. 处理状态恢复
// 在 main.dart 初始化时监听恢复事件voidmain()async{WidgetsFlutterBinding.ensureInitialized();// 监听 Continuation 恢复ContinuationService.watchEvents().listen((event){if(event['type']=='restore_app_state'){finaldata=event['data']asString;finaljson=jsonDecode(data);// 根据状态跳转页面或恢复输入框内容GlobalAppState.restoreFromJson(json);}});runApp(MyApp());}// 全局状态管理类(简化)classGlobalAppState{staticString?currentScreen;staticString?editingTodoId;staticString?inputText;staticvoidrestoreFromJson(Map<String,dynamic>json){currentScreen=json['currentScreen'];editingTodoId=json['editingTodoId'];inputText=json['inputText'];}}并在MaterialApp的home中根据GlobalAppState跳转:
home:Builder(builder:(context){if(GlobalAppState.currentScreen=='todo_edit'){WidgetsBinding.instance.addPostFrameCallback((_){// 异步恢复编辑状态Navigator.push(context,MaterialPageRoute(builder:(_)=>TodoEditPage(todoId:GlobalAppState.editingTodoId,initialText:GlobalAppState.inputText??'',)));});}returnHomePage();})五、测试流程
- 在手机上打开 Flutter 应用,进入待办编辑页;
- 点击“流转”按钮,选择已配对的平板;
- 手机调用
continueAbility(),系统弹出确认框; - 平板自动启动应用,并恢复编辑页面与输入内容;
- 手机端可选择退出或保持后台。
✅ 效果:用户感觉“任务从手机飞到了平板”,毫无割裂感。
六、注意事项与限制
| 问题 | 说明 | 解决方案 |
|---|---|---|
| Flutter 无法直接控制 Ability 生命周期 | Continuation 必须由原生 Ability 发起 | 通过 MethodChannel 代理调用 |
| 状态序列化复杂 | 需手动将 Widget 状态转为 JSON | 使用统一状态管理(如 Riverpod + toJson) |
| 仅支持同应用流转 | 不能跨 BundleName | 确保多端安装同一签名应用 |
| 设备需在线且信任 | 否则无法触发 | 提前完成设备配对 |
七、总结
本文实现了 Flutter 应用在 OpenHarmony 上的三大分布式能力融合:
- 软总线:设备发现与通信;
- 分布式数据管理:状态持久化与同步;
- Continuation:任务无缝流转。
这标志着 Flutter 应用在 OpenHarmony 生态中,已具备构建真正分布式用户体验的能力。虽然目前仍需较多桥接代码,但随着 OpenHarmony Flutter Plugin 生态 的完善,未来有望一键集成。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。