HarmonyOS 互动卡片通信架构:三方跨进程数据传递与状态回推全解析
前言
互动卡片里最容易出 bug 的不是动画,而是数据从哪来到哪去。动态卡片、宿主应用、互动卡片(激活态 UI)三者不在同一进程、生命周期也各管各的,通信方式选错轻则收不到数据、重则直接失效。
本文作为互动卡片系列的第三篇,将系统拆解三方通信架构——从动态卡片到应用页面跳转、从应用到卡片数据推送、从互动卡片到动态卡片状态回推,再到跨进程持久化存储,逐一讲解每种通信方式的核心 API 和适用场景。
一、三方通信架构全景
1.1 通信角色与关系
互动卡片涉及三个独立角色,它们之间需要多种通信方式协作:
图:互动卡片三方通信架构——动态卡片、应用、互动卡片之间的多种通信方式
| 角色 | 进程 | 生命周期 | 管理方 |
|---|---|---|---|
| 动态卡片(非激活态) | 独立进程 | 卡片在桌面时存活 | FormExtensionAbility |
| 宿主应用 | 主进程 | 应用前台/后台 | EntryAbility |
| 互动卡片(激活态) | 独立进程 | 激活期间存活 | LiveFormExtensionAbility |
1.2 七种通信方式总览
| 通信方向 | 方式 | 核心 API | 说明 |
|---|---|---|---|
| 动态卡片 → 应用 | 页面跳转 | postCardAction(ROUTER) | 点击卡片跳转到应用页面 |
| 动态卡片 → 应用 | 方法调用 | postCardAction(CALL) | 通过callee监听调用应用方法 |
| 动态卡片 → FormExtensionAbility | 发送消息 | postCardAction(MESSAGE) | 通过onFormEvent接收处理 |
| 应用 → 动态卡片 | 数据推送 | formProvider.updateForm+formBindingData | 键值对自动映射到@LocalStorageProp |
| 应用 → 互动卡片 | 数据持久化 | 用户首选项/RDB/文件存储 | 跨进程通信,LiveFormExtensionAbility 读取 |
| 互动卡片内部 | 共享存储 | LocalStorage | 同进程内传递初始数据 |
| 互动卡片 → 动态卡片 | 数据回推 | formProvider.updateForm | 实况卡片状态变化回推到动态卡片 |
二、动态卡片 → 应用通信
2.1 页面跳转(ROUTER)
最常用的通信方式,用户点击卡片跳转到应用指定页面:
// 在卡片 UI 中调用postCardAction(this,{action:'router',abilityName:'EntryAbility',params:{targetPage:'DeliveryPage'// 目标页面名称}});封装为工具方法:
exportclassActionUtils{/** * 卡片跳转到应用页面 */staticjumpAppPage(component:object,pageName:string):void{postCardAction(component,{action:'router',abilityName:'EntryAbility',params:{targetPage:pageName}});}}2.2 方法调用(CALL)
卡片直接调用应用中的方法,通过callee监听实现:
// 1. 卡片端:发送 CALL 请求postCardAction(this,{action:'call',abilityName:'EntryAbility',params:{method:'playMusic',songId:'12345'}});// 2. 应用端:在 EntryAbility 中注册 callee 监听import{UIAbility,Want}from'@kit.AbilityKit';exportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want):void{this.callee.on('call',(indata)=>{constmethod=indata.parameters.methodasstring;switch(method){case'playMusic':this.handlePlayMusic(indata.parameters.songId);break;case'pauseMusic':this.handlePauseMusic();break;}});}privatehandlePlayMusic(songId:string):void{console.info(`播放音乐:${songId}`);}privatehandlePauseMusic():void{console.info('暂停音乐');}}三、动态卡片 → FormExtensionAbility 通信
3.1 消息发送(MESSAGE)
卡片通过postCardAction(MESSAGE)发送消息到FormExtensionAbility,这是互动卡片激活的关键路径:
// 卡片端:发送消息constparams={message:'requestOverflow',widthRatio:1.3,heightRatio:1.3,duration:5000};postCardAction(this,{action:'message',message:JSON.stringify(params)});// FormExtensionAbility 端:接收消息asynconFormEvent(formId:string,message:string):Promise<void>{constparams=JSON.parse(message);constmsgType=params.messageasstring;if(msgType==='requestOverflow'){awaitthis.requestOverflow(formId,params.widthRatio,params.heightRatio,params.duration);}}四、应用 → 动态卡片数据推送
4.1 formProvider.updateForm
应用通过formProvider.updateForm向动态卡片推送数据,数据自动映射到卡片的@LocalStorageProp变量:
import{formProvider,formBindingData}from'@kit.FormKit';/** * 应用向动态卡片推送数据 */asyncfunctionupdateCardData(formId:string,data:Record<string,Object>):Promise<void>{try{constbindingData=formBindingData.createFormBindingData(data);awaitformProvider.updateForm(formId,bindingData);console.info('卡片数据更新成功');}catch(err){console.error(`卡片数据更新失败:${JSON.stringify(err)}`);}}// 使用示例:更新睡眠卡片状态awaitupdateCardData(formId,{isSleep:false,sleepTime:'07:30',wakeStatus:'按时起床'});// 卡片端:接收数据@Entry@Componentstruct SleepCard{@LocalStorageProp('isSleep')isSleep:boolean=true;@LocalStorageProp('sleepTime')sleepTime:string='07:00';@LocalStorageProp('wakeStatus')wakeStatus:string='';build(){Column(){Image(this.isSleep?$rawfile('sleep/sleep_hanhan.png'):$rawfile('sleep/wake_hanhan.png')).width('60%').height('60%')Text(this.isSleep?'睡眠中':this.wakeStatus).fontSize(14).fontColor('#666')}}}五、应用 → 互动卡片跨进程数据持久化
5.1 三种持久化方案
应用与互动卡片(激活态 UI)在不同进程,需要使用持久化存储传递数据:
| 方案 | API | 适用场景 | 性能 |
|---|---|---|---|
| 用户首选项(Preferences) | @kit.ArkData | 简单键值对(开关状态) | 快 |
| 关系型数据库(RDB) | @kit.ArkData | 结构化数据(歌曲列表) | 中 |
| 文件存储 | @kit.CoreFileKit | 大量数据或上下文 | 中 |
5.2 用户首选项存储
import{preferences}from'@kit.ArkData';/** * 应用端:保存运动状态 */exportclassExerciseStateManager{privatestaticreadonlyPREF_NAME='exercise_state';staticasyncsaveExerciseState(state:ExerciseState):Promise<void>{constprefs=awaitpreferences.getPreferences(getContext(),ExerciseStateManager.PREF_NAME);awaitprefs.put('isExercising',state.isExercising);awaitprefs.put('calories',state.calories);awaitprefs.flush();}staticasyncgetExerciseState():Promise<ExerciseState>{constprefs=awaitpreferences.getPreferences(getContext(),ExerciseStateManager.PREF_NAME);return{isExercising:prefs.get('isExercising',false)asboolean,calories:prefs.get('calories',0)asnumber};}}interfaceExerciseState{isExercising:boolean;calories:number;}5.3 关系型数据库存储
音乐卡片使用 RDB 存储歌曲列表和收藏状态,实现跨进程数据同步:
import{relationalStore}from'@kit.ArkData';/** * 音乐卡片数据管理器(RDB) */exportclassMusicDataManager{privatestaticstore:relationalStore.RdbStore|null=null;staticasyncinitDatabase(context:Context):Promise<void>{constconfig:relationalStore.StoreConfig={name:'MusicCard.db',securityLevel:relationalStore.SecurityLevel.S1};MusicDataManager.store=awaitrelationalStore.getRdbStore(context,config);// 创建歌曲表awaitMusicDataManager.store.executeSql(`CREATE TABLE IF NOT EXISTS songs ( id TEXT PRIMARY KEY, title TEXT NOT NULL, artist TEXT NOT NULL, isFavorite INTEGER DEFAULT 0 )`);}staticasyncgetSongs():Promise<SongInfo[]>{constresultSet=awaitMusicDataManager.store!.querySql('SELECT * FROM songs');constsongs:SongInfo[]=[];while(resultSet.goToNextRow()){songs.push({id:resultSet.getString(0),title:resultSet.getString(1),artist:resultSet.getString(2),isFavorite:resultSet.getLong(3)===1});}resultSet.close();returnsongs;}staticasyncupdateFavorite(songId:string,isFavorite:boolean):Promise<void>{awaitMusicDataManager.store!.executeSql('UPDATE songs SET isFavorite = ? WHERE id = ?',[isFavorite?1:0,songId]);}}interfaceSongInfo{id:string;title:string;artist:string;isFavorite:boolean;}六、互动卡片 → 动态卡片状态回推
6.1 状态回推机制
互动卡片(激活态)中状态变化后,需要通过formProvider.updateForm将状态回推到动态卡片,确保非激活态显示正确的状态:
// 在互动卡片 UI 中回推状态import{formProvider,formBindingData}from'@kit.FormKit';/** * 互动卡片状态回推到动态卡片 */asyncfunctionpushStateBackToCard(formId:string,state:Record<string,Object>):Promise<void>{try{constbindingData=formBindingData.createFormBindingData(state);awaitformProvider.updateForm(formId,bindingData);console.info(`状态回推成功:${JSON.stringify(state)}`);}catch(err){console.error(`状态回推失败:${JSON.stringify(err)}`);}}// 睡眠卡片:动画结束后回推 isSleep 状态awaitpushStateBackToCard(this.formId,{isSleep:false,wakeStatus:'按时起床'});// 运动卡片:运动结束后回推卡路里数据awaitpushStateBackToCard(this.formId,{calories:300,isExercising:false});6.2 四种卡片的状态回推
| 卡片 | 回推内容 | 触发时机 |
|---|---|---|
| 睡眠卡片 | isSleep、wakeStatus | 起床动画结束后 |
| 快递卡片 | 无需回推(状态无变化) | - |
| 运动卡片 | calories、isExercising | 运动结束后 |
| 音乐卡片 | 当前歌曲、播放状态 | 切歌、播放状态变化时 |
七、互动卡片内部 LocalStorage 传递
7.1 LocalStorage 传递初始数据
LiveFormExtensionAbility通过LocalStorage向互动卡片 UI 传递初始数据:
// LiveFormExtensionAbility 中设置 LocalStorageonLiveFormCreate(liveFormInfo:LiveFormInfo,session:UIExtensionContentSession):void{conststorage:LocalStorage=newLocalStorage();// 传递上下文storage.setOrCreate('context',this.context);storage.setOrCreate('session',session);// 传递卡片信息storage.setOrCreate('formId',liveFormInfo.formId);storage.setOrCreate('borderRadius',liveFormInfo.borderRadius);storage.setOrCreate('formRect',liveFormInfo.rect);// 加载 UIsession.loadContent('livecardability/pages/DeliveryLiveCard',storage);}// 互动卡片 UI 中读取@Entry({useSharedStorage:true})@Componentstruct DeliveryLiveCard{@LocalStorageProp('formRect')rect?:formInfo.Rect=undefined;@LocalStorageProp('borderRadius')radius:number=0;@LocalStorageProp('formId')formId:string='';build(){Stack({alignContent:Alignment.TopStart}){Image($rawfile('delivery/background.png')).borderRadius(this.radius).width(this.rect?.width||0).height(this.rect?.height||0)}}}八、通信方式选型指南
8.1 选型决策树
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 卡片跳转应用页面 | postCardAction(ROUTER) | 直接、简单 |
| 卡片调用应用方法 | postCardAction(CALL) | 支持方法调用和返回 |
| 卡片触发激活动画 | postCardAction(MESSAGE) | 必须经过 FormExtensionAbility |
| 应用推送数据到卡片 | formProvider.updateForm | 唯一方式 |
| 应用传递数据到互动卡片 | 用户首选项/RDB/文件 | 跨进程,持久化 |
| 互动卡片回推状态 | formProvider.updateForm | 确保非激活态正确 |
8.2 各卡片使用的通信方式
| 卡片 | 通信方式组合 |
|---|---|
| 睡眠卡片 | ROUTER(跳转页面)+ MESSAGE(触发动画)+updateForm(回推 isSleep)+ LocalStorage(内部传递) |
| 音乐卡片 | CALL(播放控制)+ ROUTER(跳转页面)+ MESSAGE(触发动画)+ RDB(歌曲数据)+updateForm(同步状态) |
| 运动卡片 | CALL(运动控制)+ ROUTER(跳转页面)+ MESSAGE(触发动画)+ 文件存储(运动状态)+updateForm(回推卡路里) |
| 快递卡片 | ROUTER(跳转页面)+ MESSAGE(触发动画)+ LocalStorage(内部传递) |
九、总结
本文系统拆解了互动卡片的三方通信架构:
- 七种通信方式:ROUTER、CALL、MESSAGE、
updateForm、持久化存储、LocalStorage、状态回推 - 动态卡片 → 应用:页面跳转(ROUTER)和方法调用(CALL)两种方式
- 应用 → 动态卡片:唯一方式
formProvider.updateForm+formBindingData - 应用 → 互动卡片:三种持久化方案(Preferences / RDB / 文件存储)
- 互动卡片 → 动态卡片:
formProvider.updateForm状态回推,确保非激活态正确显示
下一篇将以快递卡片和睡眠卡片为主线,深入讲解完整开发流程与帧动画、陀螺仪交互、破框效果的实战实现。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 互动卡片开发实践文档:华为开发者文档
- 互动卡片示例代码:GitCode
- 用户首选项开发:华为开发者文档
- 关系型数据库开发:华为开发者文档
- 开源鸿蒙跨平台社区:CSDN 社区