卡片 message 事件全解——onFormEvent 是怎么工作的 文章目录message 事件的本质是什么卡片端postCardAction 发送 messageFormAbility 端onFormEvent 接收并处理进阶用 message 实现带状态的刷新message 事件 vs router 事件 vs call 事件message 参数传递流程图常见坑写在最后卡片跑在一个独立的渲染进程里没法直接调用应用代码。想让卡片上的按钮点击后触发一些复杂逻辑比如刷新数据、写入存储必须走message事件这个桥梁。这篇把 message 事件的完整链路讲清楚包括怎么发、怎么收、收到后怎么处理。message 事件的本质是什么大白话版卡片 UI 是一个沙盒自己没法做副作用操作网络请求、读写文件等。postCardAction message是它唯一能往外喊话的方式——发一条消息给 FormExtensionAbility让 FormAbility 帮它干活干完了再把结果推回来。整个通信链路卡片UI点击按钮 → postCardAction(message) → FormAbility.onFormEvent() → formProvider.updateForm() → 卡片UI更新卡片端postCardAction 发送 message// entry/src/main/ets/updatebymessage/pages/UpdateByMessageCard.etsletstoragenewLocalStorage();Entry(storage)Componentstruct UpdateByMessageCard{LocalStorageProp(title)title:ResourceStr$r(app.string.default_title);LocalStorageProp(detail)detail:ResourceStr$r(app.string.DescriptionDefault);build(){Column(){Column(){Text(this.title).fontColor(#FFFFFF).opacity(0.9).fontSize(14).margin({top:8%,left:10%})Text(this.detail).fontColor(#FFFFFF).opacity(0.6).fontSize(12).margin({top:5%,left:10%})}.width(100%).height(50%).alignItems(HorizontalAlign.Start)Row(){Button(){Text(刷新卡片).fontColor(#45A6F4).fontSize(12)}.width(120).height(32).margin({top:30%,bottom:10%}).backgroundColor(#FFFFFF).borderRadius(16).onClick((){// 通过 message 事件把参数传给 FormAbilitypostCardAction(this,{action:message,// 固定值触发 onFormEventparams:{msgTest:messageEvent// 自定义参数FormAbility 里解析}});})}.width(100%).height(40%).justifyContent(FlexAlign.Center)}.width(100%).height(100%).alignItems(HorizontalAlign.Start).backgroundImage($r(app.media.CardEvent)).backgroundImageSize(ImageSize.Cover)}}FormAbility 端onFormEvent 接收并处理// entry/src/main/ets/updatebymessage/widgetability/UpdateByMessageFormAbility.etsimport{formBindingData,FormExtensionAbility,formProvider}fromkit.FormKit;import{Want}fromkit.AbilityKit;import{BusinessError}fromkit.BasicServicesKit;import{hilog}fromkit.PerformanceAnalysisKit;constTAGUpdateByMessageFormAbility;constDOMAIN_NUMBER0xFF00;exportdefaultclassUpdateByMessageFormAbilityextendsFormExtensionAbility{onAddForm(want:Want):formBindingData.FormBindingData{constformData:Recordstring,string{};returnformBindingData.createFormBindingData(formData);}// 关键处理来自卡片 UI 的 message 事件onFormEvent(formId:string,message:string):void{hilog.info(DOMAIN_NUMBER,TAG,onFormEvent called, formId:${formId});hilog.info(DOMAIN_NUMBER,TAG,message:${message});// message 是一个 JSON 字符串需要 JSON.parseconstmsg:Recordstring,stringJSON.parse(message);// 根据 params 里的不同字段执行不同逻辑if(msg.msgTestmessageEvent){// 处理完业务逻辑后把新数据推送给卡片classFormDataClass{title:stringTitle Update.;detail:stringDescription update success.;}constformDatanewFormDataClass();constformInfoformBindingData.createFormBindingData(formData);// 用 updateForm 把新数据推回卡片 UIformProvider.updateForm(formId,formInfo).then((){hilog.info(DOMAIN_NUMBER,TAG,卡片数据更新成功);}).catch((error:BusinessError){hilog.error(DOMAIN_NUMBER,TAG,更新失败:${JSON.stringify(error)});});}}}进阶用 message 实现带状态的刷新WidgetUpdateByStatusCard展示了一个更实用的场景卡片上有两个 Checkbox勾选哪个只刷新对应的数据。// entry/src/main/ets/widgetupdatebystatus/pages/WidgetUpdateByStatusCard.etsletstorageUpdateByStatusnewLocalStorage();Entry(storageUpdateByStatus)Componentstruct WidgetUpdateByStatusCard{LocalStorageProp(textA)textA:Resource$r(app.string.to_be_refreshed);LocalStorageProp(textB)textB:Resource$r(app.string.to_be_refreshed);// 本地状态仅控制当前 UI 展示不跨进程StateselectA:booleanfalse;StateselectB:booleanfalse;build(){Column(){Column(){// Checkbox ARow(){Checkbox({name:checkbox1,group:checkboxGroup}).padding(0).select(false).margin({left:26}).onChange((value:boolean){this.selectAvalue;// 把选中状态发给 FormAbilitypostCardAction(this,{action:message,params:{selectA:JSON.stringify(value)// 告诉 FA 状态A的选中情况}});})Text(状态A).fontColor(#000000).opacity(0.9).fontSize(14).margin({left:8})}.width(100%).padding(0).justifyContent(FlexAlign.Start)// Checkbox BRow(){Checkbox({name:checkbox2,group:checkboxGroup}).padding(0).select(false).margin({left:26}).onChange((value:boolean){this.selectBvalue;postCardAction(this,{action:message,params:{selectB:JSON.stringify(value)// 告诉 FA 状态B的选中情况}});})Text(状态B).fontColor(#000000).opacity(0.9).fontSize(14).margin({left:8})}.width(100%).position({y:32}).padding(0).justifyContent(FlexAlign.Start)}.position({y:12})// 展示区根据 FormAbility 推送的数据显示Column(){Row(){Text(状态A: ).fontColor(#000000).opacity(0.4).fontSize(12)Text(this.textA)// 来自 FormAbility 的更新.fontColor(#000000).opacity(0.4).fontSize(12)}.margin({top:12px,left:26})Row(){Text(状态B: ).fontColor(#000000).opacity(0.4).fontSize(12)Text(this.textB).fontColor(#000000).opacity(0.4).fontSize(12)}.margin({top:12px,bottom:21px,left:26})}.margin({top:80}).width(100%).alignItems(HorizontalAlign.Start)}.width(100%).height(100%).backgroundImage($r(app.media.CardUpdateByStatus)).backgroundImageSize(ImageSize.Cover)}}FormAbility 侧的状态识别// 对应的 FormAbilityonFormEvent(formId:string,message:string):void{constmsg:Recordstring,stringJSON.parse(message);// 根据传来的状态选择性刷新if(msg.selectA!undefined){constisSelectedJSON.parse(msg.selectA)asboolean;if(isSelected){// 只更新 textAconstformData{textA:AAA 状态A已刷新};formProvider.updateForm(formId,formBindingData.createFormBindingData(formData));}}if(msg.selectB!undefined){constisSelectedJSON.parse(msg.selectB)asboolean;if(isSelected){// 只更新 textBconstformData{textB:BBB 状态B已刷新};formProvider.updateForm(formId,formBindingData.createFormBindingData(formData));}}}message 事件 vs router 事件 vs call 事件卡片的postCardAction有三种 action别搞混action 类型目标用途卡片到前台吗messageFormExtensionAbility.onFormEvent刷新卡片数据、后台操作不会routerUIAbility前台打开应用、跳转页面会callUIAbility后台后台调用 Callee 方法不会核心区别message卡片自己的内部通信不打开应用适合点一下刷新数据router拉起应用到前台适合点一下打开 App 看详情call让应用在后台干活不打扰用户适合静默后台操作message 参数传递流程图常见坑坑1message 参数是字符串不是对象onFormEvent收到的message参数是 JSON 字符串必须JSON.parse才能用。不 parse 直接当对象访问拿到的都是undefined。// 错误onFormEvent(formId:string,message:string){console.log(message.key);// undefinedmessage 是字符串不是对象}// 正确onFormEvent(formId:string,message:string){constmsgJSON.parse(message);// 先 parseconsole.log(msg.key);// 正确拿到值}坑2FormExtensionAbility 只有 5 秒onFormEvent触发后FormExtensionAbility进程只会存活约 5 秒。如果你在里面做网络请求要用async/await确保在进程销毁前完成或者用后台任务。坑3updateForm要在 onFormEvent 的上下文里调用formId是onFormEvent传进来的要在函数作用域内用不要把它存到全局变量再异步调用进程可能已经销毁了。写在最后message事件是卡片和 FormAbility 之间最常用的通信方式。只要记住一点卡片是客户FormAbility 是跑腿的客户说一声postCardAction message跑腿的去干活onFormEvent干完了把结果带回来updateForm。