当前位置: 首页 > news >正文

一个应用多个卡片——多 FormAbility 注册与 call 事件后台唤起完整指南

文章目录

      • 一个应用注册多个卡片
      • call 事件:静默后台唤起
      • call 事件:卡片 UI 端
      • call 事件:UIAbility 端注册 Callee
      • call 事件 vs router 事件:通过卡片刷新数据的完整对比
      • 多 FormAbility 组织原则
      • call 事件使用前提:权限配置
      • 常见坑
      • 写在最后

一个应用只有一个卡片?那太简单了。真实项目里,一个应用往往需要提供多种卡片(日程卡片、天气卡片、快捷操作卡片……),而且卡片点击后可能需要在不打扰用户的前提下唤起后台逻辑。
这篇基于StageServiceWidgetCards这个综合示例,讲两件事:

  1. 如何在一个应用里注册多个 FormAbility(多卡片类型)
  2. call事件如何静默唤起 UIAbility 后台方法

一个应用注册多个卡片

StageServiceWidgetCards是一个综合示例,里面有十几种卡片类型,每种都有独立的 FormAbility 和配置文件。

module.json5 的关键结构:

// entry/src/main/module.json5 { "module": { "abilities": [ // 主 UIAbility { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ts", ... }, // 卡片 router 事件的目标 UIAbility { "name": "WidgetEventRouterEntryAbility", "srcEntry": "./ets/widgetevententryability/WidgetEventRouterEntryAbility.ts", ... }, // 卡片 call 事件的目标 UIAbility(Callee 模式) { "name": "WidgetCalleeEntryAbility", "srcEntry": "./ets/widgetcalleeentryability/WidgetCalleeEntryAbility.ts", ... }, // 注册了 call 事件的 Ability { "name": "WidgetEventCallEntryAbility", "srcEntry": "./ets/widgeteventcallentryability/WidgetEventCallEntryAbility.ets", ... } ], "extensionAbilities": [ // 卡片1:基础卡片事件 { "name": "EntryFormAbility", "srcEntry": "./ets/entryformability/EntryFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_config" }] }, // 卡片2:图片更新卡片 { "name": "WgtImgUpdateEntryFormAbility", "srcEntry": "./ets/wgtimgupdateentryformability/WgtImgUpdateEntryFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_imgupdate_config" }] }, // 卡片3:定时刷新卡片 { "name": "UpdateByTimeFormAbility", "srcEntry": "./ets/updatebytimeformability/UpdateByTimeFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_updatebytime_config" }] }, // 卡片4:状态驱动刷新卡片 { "name": "UpdateByStatusFormAbility", "srcEntry": "./ets/updatebystatusformability/UpdateByStatusFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_updatebystatus_config" }] }, // 卡片5:JS 卡片 { "name": "JsCardFormAbility", "srcEntry": "./ets/jscardformability/JsCardFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_jscard_config" }] }, // 卡片6:Callee 卡片(call 事件刷新) { "name": "WidgetCalleeFormAbility", "srcEntry": "./ets/widgetcalleeformability/WidgetCalleeFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_widgetcallee_config" }] }, // 卡片7:持久化数据卡片 { "name": "PersistentDataFormAbility", "srcEntry": "./ets/persistentdataformability/PersistentDataFormAbility.ts", "type": "form", "metadata": [{ "name": "ohos.extension.form", "resource": "$profile:form_persistentdata_config" }] } // ... 更多卡片 ], "requestPermissions": [ { "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" }, // 后台任务权限 { "name": "ohos.permission.START_ABILITIES_FROM_BACKGROUND" }, // 后台启动权限 { "name": "ohos.permission.INTERNET", ... } // 网络权限(图片更新卡片需要) ] } }

关键结论:多少种卡片类型,就需要多少个 FormExtensionAbility,各自独立配置各自的卡片配置文件(profile)。

call 事件:静默后台唤起

call事件和router事件的最大区别:

事件目标应用是否到前台用途
routerUIAbility是(拉到前台)打开应用查看详情
callUIAbility(Callee 方法)否(在后台运行)静默执行操作、刷新卡片数据

大白话版:router是"打电话,对方得接听";call是"发短信,对方不需要看手机就能处理"。

call 事件:卡片 UI 端

// entry/src/main/ets/widgeteventcall/pages/WidgetEventCallCard.etsletstorageEventCall=newLocalStorage();@Entry(storageEventCall)@Componentstruct WidgetEventCallCard{@LocalStorageProp('formId')formId:string='12400633174999288';privatefunA:Resource=$r('app.string.ButtonA_label');privatefunB:Resource=$r('app.string.ButtonB_label');build(){RelativeContainer(){// 按钮 A:触发 call 事件,调用后台 funA 方法Button(this.funA).id('funA__').fontSize(14).fontWeight(FontWeight.Bold).alignRules({center:{anchor:'__container__',align:VerticalAlign.Center},middle:{anchor:'__container__',align:HorizontalAlign.Center}}).onClick(()=>{postCardAction(this,{action:'call',// call 事件abilityName:'WidgetEventCallEntryAbility',// 目标 UIAbility(必须在同一应用内)params:{formId:this.formId,method:'funA'// 要调用的方法名}});})// 按钮 B:触发另一个方法Button(this.funB).id('funB__').fontSize(14).fontWeight(FontWeight.Bold).margin({top:10}).alignRules({top:{anchor:'funA__',align:VerticalAlign.Bottom},middle:{anchor:'__container__',align:HorizontalAlign.Center}}).onClick(()=>{postCardAction(this,{action:'call',abilityName:'WidgetEventCallEntryAbility',params:{formId:this.formId,method:'funB',num:1// 额外参数}});})}.height('100%').width('100%')}}

call 事件:UIAbility 端注册 Callee

call事件的接收方是 UIAbility 里的 Callee 机制,需要在onCreate里注册:

// entry/src/main/ets/widgeteventcallentryability/WidgetEventCallEntryAbility.etsimport{AbilityConstant,UIAbility,Want}from'@kit.AbilityKit';import{formBindingData,formProvider}from'@kit.FormKit';import{rpc}from'@kit.IPCKit';import{hilog}from'@kit.PerformanceAnalysisKit';import{BusinessError}from'@kit.BasicServicesKit';constTAG='WidgetEventCallEntryAbility';constDOMAIN_NUMBER=0xFF00;// funA 方法的实现constfunACallback=(data:rpc.MessageSequence):rpc.Parcelable=>{hilog.info(DOMAIN_NUMBER,TAG,'funA called');// 从 MessageSequence 里读取 formIdconstformId=data.readString();hilog.info(DOMAIN_NUMBER,TAG,`funA called with formId:${formId}`);// 通过 formProvider.updateForm 刷新卡片数据constformData:Record<string,string>={'calleeDetail':'FunACall param'// 对应卡片 @LocalStorageProp('calleeDetail')};formProvider.updateForm(formId,formBindingData.createFormBindingData(formData)).catch((error:BusinessError)=>{hilog.error(DOMAIN_NUMBER,TAG,`updateForm 失败:${JSON.stringify(error)}`);});// 必须返回 Parcelable 对象returnnewMyParcelable(0,'funA success');};constfunBCallback=(data:rpc.MessageSequence):rpc.Parcelable=>{hilog.info(DOMAIN_NUMBER,TAG,'funB called');constformId=data.readString();constnum=data.readInt();// 读取额外参数hilog.info(DOMAIN_NUMBER,TAG,`funB called: formId=${formId}, num=${num}`);constformData:Record<string,string>={'calleeDetail':`FunBCall param:${num}`};formProvider.updateForm(formId,formBindingData.createFormBindingData(formData));returnnewMyParcelable(0,'funB success');};// Parcelable 实现类(必须实现 marshalling 和 unmarshalling)classMyParcelableimplementsrpc.Parcelable{num:number=0;str:string='';constructor(num:number,str:string){this.num=num;this.str=str;}marshalling(messageSequence:rpc.MessageSequence):boolean{messageSequence.writeInt(this.num);messageSequence.writeString(this.str);returntrue;}unmarshalling(messageSequence:rpc.MessageSequence):boolean{this.num=messageSequence.readInt();this.str=messageSequence.readString();returntrue;}}exportdefaultclassWidgetEventCallEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{hilog.info(DOMAIN_NUMBER,TAG,'onCreate');// 注册 Callee 方法// 方法名要和卡片端 postCardAction 里的 method 字段一致try{this.callee.on('funA',funACallback);this.callee.on('funB',funBCallback);hilog.info(DOMAIN_NUMBER,TAG,'Callee 注册成功');}catch(error){consterr=errorasBusinessError;hilog.error(DOMAIN_NUMBER,TAG,`Callee 注册失败:${err.code}${err.message}`);}}onDestroy():void{// 销毁时注销 Calleetry{this.callee.off('funA');this.callee.off('funB');}catch(error){hilog.error(DOMAIN_NUMBER,TAG,`Callee 注销失败`);}}}

call 事件 vs router 事件:通过卡片刷新数据的完整对比

多 FormAbility 组织原则

管理多个卡片时,建议遵循这套组织方式:

entry/src/main/ets/ ├── 卡片类型1/ │ ├── widgetabilify/ │ │ └── Card1FormAbility.ets ← FormExtensionAbility │ └── pages/ │ └── Card1.ets ← 卡片 UI ├── 卡片类型2/ │ ├── widgetabilify/ │ │ └── Card2FormAbility.ets │ └── pages/ │ └── Card2.ets ├── 公共工具/ │ └── CommonFormUtils.ets ← 抽取公共逻辑:Preferences读写、错误处理 └── entryability/ └── EntryAbility.ets ← 主UIAbility

每种卡片类型对应一个目录,FormAbility 和 UI 放在一起,方便维护。公共逻辑(Preferences 工具类、错误处理)抽取出来复用。

call 事件使用前提:权限配置

call事件的目标 UIAbility 在后台启动,需要声明权限:

// entry/src/main/module.json5 { "module": { "requestPermissions": [ // call 事件:从后台启动 Ability 需要此权限 { "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" }, { "name": "ohos.permission.START_ABILITIES_FROM_BACKGROUND" } ] } }

少了这两个权限,call事件会失败,但不一定报显眼的错误,排查起来比较麻烦。

常见坑

坑1:call 事件的 abilityName 必须是同一应用内的 UIAbility

不能跨应用,这是系统限制。如果填了其他应用的 abilityName,postCardAction会默默失败。

坑2:Callee.on 要在 onCreate 里注册,而不是 onForeground

call事件启动的 UIAbility 不会走onForeground,只走onCreateonNewWant。如果注册在onForeground里,call 事件会收不到。

坑3:MessageSequence 读写顺序必须严格对应

发送端用params传递,接收端用MessageSequence.readString()/readInt()读取,读写顺序必须严格一致,否则数据错乱。建议先传 formId(String),再传其他参数,并在代码里注释清楚。

坑4:多个 FormAbility 的 form_config.json 文件名不能重复

每个 FormAbility 的metadata.resource指向不同的配置文件,比如$profile:form_config$profile:form_imgupdate_config。文件名重复了会互相覆盖,导致某些卡片类型找不到配置。

写在最后

多卡片开发是 FormKit 的"进阶"场景,主要考验的是项目结构的组织能力和各种配置的准确性。StageServiceWidgetCards这个项目展示了几乎所有卡片开发模式,如果你遇到了某个具体场景,直接去里面找对应的示例。

call事件是一个被低估的功能。很多开发者习惯用router做所有交互,但对于"点卡片触发刷新"这类场景,call明显更合适——用户不会被带离桌面,体验更流畅。

http://www.jsqmd.com/news/844504/

相关文章:

  • 5个phpenv实用技巧:让你的PHP开发效率提升300%
  • 洛谷 B4359:[GESP202506 三级] 分糖果 ← 贪心算法
  • 【Python】腾讯云短信验证码接入完整教程,从申请模板到发送只需10分钟
  • 如何快速上手Nintendo Switch大气层破解系统:新手完整指南
  • 双核Delfino架构解析:如何解决复杂实时控制系统的性能瓶颈
  • 别再为SAP HTTPS接口报错头疼了!一份超全的CL_HTTP_CLIENT调试与排错指南
  • CLI-Anything未来展望:即将支持的10大新功能与软件集成
  • 从新手到认证专家:NotebookLM总结能力跃迁路径图(含Google官方未公开的评估矩阵V2.1)
  • 1Panel面板下ghcr.io镜像加速全攻略:以Open WebUI为例
  • 告别无效运营!2026 私域效率实测:AI SCRM 如何提升 300% 人效? - 行业产品测评专家
  • 如何为直播添加实时字幕:OBS字幕插件深度解析
  • 全新UI 阅后即焚V2正式版系统源码_全开源_安全加密传输
  • 【免费下载】 微波工程第四版 - Microwave Engineering
  • 告别C盘焦虑!手把手教你将VS2013完整安装到D盘(附阿里云盘下载)
  • postgresql的SQL或MED
  • 让旧款iPhone/iPad重获新生:Legacy-iOS-Kit终极使用指南
  • 【Android】CloneTTS最强朗读听书引擎-可克隆一切音色
  • Windows/Mac通用教程:用venv隔离环境,一步步安装Playwright并解决‘浏览器下载失败’问题
  • 终极指南:如何用VideoDownloadHelper免费下载网页视频
  • 如何轻松备份微信聊天记录:WeChatMsg完全免费的数据守护方案
  • 视觉暂留灯绘DIY:从硬件焊接、图像编程到光绘摄影全解析
  • 别再只盯着RRT了!关节空间六次多项式规划,可能是更简单的机械臂避障方案
  • PPTTimer:让每一场演示都精准掌控的智能时间管家
  • NoFences:彻底告别Windows桌面混乱,打造高效工作空间的免费开源神器
  • ESJsonFormat-Xcode泛型支持:Xcode 7及以上版本的优化特性
  • 【免费下载】 ArcGIS勘测定界软件自动化工具
  • 武汉买猫狗推荐 本地头部十年老店 武汉老牌购宠 - 范德萨的得到
  • 长期使用 Taotoken Token Plan 套餐的成本节约感受
  • 【免费下载】 Gmsh 4.11.1 资源包
  • 【免费下载】 探索双面神技:STM32G474的USB跨界应用