HarmonyOS7 互动卡片架构深度解析

发布时间:2026/7/22 7:37:41
HarmonyOS7 互动卡片架构深度解析 互动卡片Live Form / LiveCard为服务卡片带来了质的飞跃——从信息的静态展示进化到了可交互、可动效、可感知的全新维度。本文以开源项目 LiveCard包名com.example.livecard为蓝本从LiveFormExtensionAbility生命周期出发深入剖析互动卡片的整体架构设计。LiveCard 项目包含四种互动卡片睡眠卡片三叶草动画 睡眠健康数据、快递卡片陀螺仪驱动的憨憨动画、运动卡片卡路里燃烧动画与状态切换、音乐卡片专辑封面同步动画 音乐播放。全文代码均取自该实战项目。互动卡片 vs 传统卡片在module.json5中我们可以同时声明两种卡片类型的 ExtensionAbility[entry/src/main/module.json5]{extensionAbilities:[{name:EntryFormAbility,srcEntry:./ets/entryformability/EntryFormAbility.ets,type:form,// 传统卡片metadata:[{name:ohos.extension.form,resource:$profile:form_config}]},{name:SleepLiveCardAbility,srcEntry:./ets/livecardability/SleepLiveCardAbility.ets,type:liveForm// 互动卡片}]}传统卡片对应FormExtensionAbility而互动卡片对应LiveFormExtensionAbility。二者最核心的差异体现在三个层面生命周期模型传统卡片通过onAddForm、onUpdateForm、onFormEvent等回调与卡片交互本质上是一种事件驱动的轻量模型。而互动卡片通过onLiveFormCreate获得一个UIExtensionContentSession拥有独立的 UI 渲染能力本质上是一个UIExtension的增强形态。渲染能力传统卡片的 UI 由 ArkTS widget 页面渲染不支持帧动画、传感器数据、实时媒体播放等能力。互动卡片则可以运行完整的帧动画如睡眠卡片的憨憨动画、集成陀螺仪传感器如快递卡片、使用 AVPlayer 播放音乐等。通信方式传统卡片通过postCardAction向指定的 Ability 发送事件接收方在onFormEvent中处理。互动卡片除了支持postCardAction还能通过LocalStorage与主 Ability 共享数据并通过callee.on(cardAction, ...)注册方法调用处理。从代码结构上看传统卡片的 UI 页面位于widget/pages/目录下而互动卡片的 UI 页面位于livecardability/pages/目录下——这不仅仅是路径的差异更代表了两种完全不同的运行环境。LiveFormExtensionAbility 生命周期onLiveFormCreate 的同步调用约束LiveFormExtensionAbility的核心生命周期方法是onLiveFormCreate它在用户将卡片添加到桌面时被调用。该方法接收两个参数LiveFormInfo携带卡片元数据和UIExtensionContentSession用于加载卡片 UI。在 LiveCard 项目中我们可以看到同步与异步两种写法[entry/src/main/ets/livecardability/SleepLiveCardAbility.ets]exportclassSleepLiveCardAbilityextendsLiveFormExtensionAbility{onLiveFormCreate(liveFormInfo:LiveFormInfo,session:UIExtensionContentSession):void{letstorage:LocalStoragenewLocalStorage();storage.setOrCreate(context,this.context);storage.setOrCreate(session,session);letformId:stringliveFormInfo.formId;storage.setOrCreate(formId,formId);letborderRadius:numberliveFormInfo.borderRadius;storage.setOrCreate(borderRadius,borderRadius);letformRect:formInfo.RectliveFormInfo.rect;storage.setOrCreate(formRect,formRect);try{session.loadContent(livecardability/pages/SleepLiveCard,storage);}catch(error){Logger.error(TAG,loadContent catch error, code:${error.code}, message:${error.message});}}}而音乐卡片则是异步版本[entry/src/main/ets/livecardability/MusicLiveCardAbility.ets]exportclassMusicLiveCardAbilityextendsLiveFormExtensionAbility{asynconLiveFormCreate(liveFormInfo:LiveFormInfo,session:UIExtensionContentSession):Promisevoid{// ... 初始化 LocalStorage ...session.loadContent(livecardability/pages/MusicLiveCard,storage);// 异步数据加载letactionDataMusicFileStore.getTriggerAction(this.context);letsongRdbHelperSongRdbHelper.getInstance(this.context);letinitSongs:SongItem[]awaitsongRdbHelper.queryAllSongs();// ... 继续初始化 ...}}这里有一个重要的设计考虑虽然onLiveFormCreate可以声明为async但session.loadContent()的调用应该尽早执行不应被await阻塞。音乐卡片的做法是正确的——先调用loadContent加载 UI再执行异步数据初始化数据通过LocalStorage传递给卡片组件。LiveFormInfo 的数据结构LiveFormInfo包含了互动卡片运行所需的核心参数formId卡片的唯一标识后续所有formProvider.updateForm()操作都需要它borderRadius卡片的圆角值用于与卡片 UI 保持一致rectformInfo.Rect类型包含width、height、left、top标识卡片在桌面的位置和尺寸这些参数通过LocalStorage传递到卡片组件中使得组件能够自适应桌面布局。sceneAnimationParams 机制abilityName 的配置与系统激活流程在form_config.json中每个卡片的配置都包含sceneAnimationParams字段[entry/src/main/resources/base/profile/form_config.json]{forms:[{name:SleepCard,src:./ets/widget/pages/SleepCard.ets,isDynamic:true,sceneAnimationParams:{abilityName:SleepLiveCardAbility}},{name:DeliveryCard,sceneAnimationParams:{abilityName:DeliveryLiveCardAbility,triggerTypes:[shake]}}]}sceneAnimationParams是传统卡片与互动卡片之间的桥梁。它的工作流程如下用户将卡片添加到桌面系统首先加载传统卡片widget/pages/SleepCard.ets这是一个普通的 Form 卡片当用户点击卡片或满足触发条件时系统检测sceneAnimationParams.abilityName配置系统激活对应的LiveFormExtensionAbility即SleepLiveCardAbilityonLiveFormCreate被调用互动卡片 UI 通过UIExtensionContentSession加载此时桌面上的卡片从静态 widget无缝切换为互动卡片这个机制实现了一种渐进式增强的卡片体验——用户看到的是同一张卡片但系统在后台完成了从轻量 widget 到全功能 UIExtension 的升级。对于快递卡片triggerTypes还包含shake这意味着除了点击触发外系统还可以通过摇一摇传感器事件来激活互动状态。多卡片协作架构LiveCard 项目中存在四种完全不同的互动卡片它们如何共享同一个主 Ability 却又各自独立运行答案藏在架构设计中。单 Ability 多 Extension 模式┌─────────────────────────────────────────────┐ │ EntryAbility │ │ (LiveCardAbility / UIAbility) │ │ │ │ ├─ CardActionHandler (callee 注册) │ │ ├─ MediaService (全局音乐服务) │ │ └─ FormUtils (数据管理) │ ├─────────────────────────────────────────────┤ │ │ │ ExtensionAbilities (各自独立进程上下文) │ │ │ │ SleepLiveCardAbility ─── SleepLiveCard.ets │ │ DeliveryLiveCardAbility ─── DeliveryLiveCard│ │ ExerciseLiveCardAbility ─── ExerciseLiveCard│ │ MusicLiveCardAbility ─── MusicLiveCard │ └─────────────────────────────────────────────┘每个LiveFormExtensionAbility都是独立的 Extension拥有自己的 Context。它们通过LocalStorage与各自的卡片页面通信互不干扰。跨进程通信的三条路径卡片与主 Ability 之间的通信主要通过以下三种方式路径一postCardAction CALL 模式[entry/src/main/ets/utils/ActionUtils.ets]publicplayByAction(component:object,type:PlayActionType,formId:string):void{postCardAction(component,{action:FormCarAction.CALL,abilityName:ENTRY_ABILITY,// LiveCardAbilityparams:{method:cardAction,actionType:CardActionType.PLAY_ACTION,playActionType:type,formId:formId,},});}主 Ability 通过this.callee.on(cardAction, CardActionHandler.getHandler())注册处理方法CardActionHandler根据actionType分发到不同的处理逻辑。路径二postCardAction MESSAGE 模式publicrequestOverFlow(component:object,widthRatio:number,heightRatio:number,duration:number):void{postCardAction(component,{action:FormCarAction.MESSAGE,abilityName:ENTRY_FORM_ABILITY,// EntryFormAbilityparams:{message:requestOverflow,widthRatio:widthRatio,heightRatio:heightRatio,duration:duration},});}MESSAGE 消息发送到EntryFormAbility在onFormEvent回调中处理。这主要用于触发卡片的溢出动画overflow animation。路径三formProvider.updateForm 推送数据publicasyncupdateExerciseCardState(context:Context,state:number):Promisevoid{letformList:FormInfo[]awaitFormRdbHelper.getInstance(context).queryFormByName(ExerciseCard);formList.forEach((formInfo){classExerciseUpdateData{publiccurrentState:numberstate;publiccalories:number0;}this.updateForm(formInfo.formId,newExerciseUpdateData());});}主 Ability 通过formProvider.updateForm()主动向所有同名卡片推送数据更新。卡片组件上使用LocalStorageProp或LocalStorageLink装饰器接收这些数据。数据持久化的统一入口虽然每种卡片各有独立的数据存储ExerciseFileStore、MusicFileStore、FormRdbHelper但所有数据操作都通过FormUtils这个统一入口类对外暴露。主页面、卡片、Extension Ability 都通过FormUtils来读写数据保持了数据层的整洁。关键配置解读form_config.json 中的关键配置项isDynamic: true启用卡片的动态能力。这是互动卡片的基础开关设置为true后卡片才能接收formProvider.updateForm()的更新。supportDimensions: [2*4]定义卡片支持的网格尺寸。睡眠卡片和音乐卡片使用2*4两列四行快递卡片和运动卡片使用2*2。这决定了卡片在桌面的布局占比。defaultDimension: 2*4卡片首次添加到桌面时的默认尺寸。updateEnabled: false禁用系统的自动定时更新机制。LiveCard 项目中的卡片通过formProvider.updateForm()手动触发数据更新因此不需要系统级的定时刷新。sceneAnimationParams.abilityName如前所述这是连接传统 widget 与互动卡片 Extension 的桥梁指定了激活哪一个LiveFormExtensionAbility。module.json5 中的关键配置项type: liveForm这是将 ExtensionAbility 标记为互动卡片类型的关键。没有这个字段系统不会将其识别为 LiveFormonLiveFormCreate也不会被调用。metadata的差异传统卡片需要metadata字段指定ohos.extension.form资源配置而互动卡片不需要——因为互动卡片的配置直接在form_config.json中通过sceneAnimationParams引用。Entry({ useSharedStorage: true })互动卡片页面组件上的装饰器参数。useSharedStorage: true表示使用共享存储这是与LiveFormExtensionAbility共享LocalStorage数据的前提。源码走读SleepLiveCardAbility 初始化流程最后我们以睡眠卡片为例完整走通一条初始化链路。Step 1传统卡片加载用户添加卡片系统加载widget/pages/SleepCard.ets。这个页面是静态的显示憨憨和三叶草的静态图片通过isSleep状态切换睡觉和起床两种样式。用户点击卡片时调用ActionUtils.requestOverFlow()通过 MESSAGE 通知EntryFormAbility.onClick((){if(this.isSleep){ActionUtils.requestOverFlow(this,LiveCardScale.SLEEP_WIDTH,LiveCardScale.SLEEP_HEIGHT,LIVE_CARD_DURATION);}else{ActionUtils.jumpAppPage(this,SleepReport);}})Step 2系统激活 LiveFormExtensionAbilityEntryFormAbility.onFormEvent接收到requestOverflow消息后调用formProvider.requestOverflow()触发溢出动画。同时因为form_config.json中SleepCard配置了sceneAnimationParams.abilityName: SleepLiveCardAbility系统自动激活SleepLiveCardAbility。Step 3onLiveFormCreate 执行onLiveFormCreate(liveFormInfo:LiveFormInfo,session:UIExtensionContentSession):void{// 1. 创建 LocalStorage建立数据通道letstorage:LocalStoragenewLocalStorage();storage.setOrCreate(context,this.context);storage.setOrCreate(session,session);// 2. 从 LiveFormInfo 提取卡片元数据letformId:stringliveFormInfo.formId;storage.setOrCreate(formId,formId);letborderRadius:numberliveFormInfo.borderRadius;storage.setOrCreate(borderRadius,borderRadius);letformRect:formInfo.RectliveFormInfo.rect;storage.setOrCreate(formRect,formRect);// 3. 加载互动卡片 UI 页面session.loadContent(livecardability/pages/SleepLiveCard,storage);}Step 4互动卡片 UI 启动SleepLiveCard组件通过LocalStorageProp从LocalStorage中获取formId、formRect、borderRadius。在aboutToAppear中初始化帧动画的累积时间表并在 1 秒后通过formProvider.updateForm()通知传统卡片更新状态aboutToAppear():void{this.initCumulativeTimes();setTimeout((){letformMsg:formBindingData.FormBindingDataformBindingData.createFormBindingData({isSleep:false});formProvider.updateForm(this.formId,formMsg);},1000);}Step 5帧动画驱动卡片通过setInterval每 16ms约 60fps更新一帧根据经过时间计算当前应显示的帧索引。帧列表通过FrameItem的weight属性支持停留时长权重使某些关键帧可以多停留一段时间形成更自然的动画节奏。startImageSync():void{this.animStartTimeDate.now();this.imageSyncTimersetInterval((){constelapsedDate.now()-this.animStartTime;// 根据经过时间计算帧索引constnewHanhanFramethis.getFrameByElapsed(elapsed,this.hanhanCumulativeTime,...);constnewCloverFramethis.getFrameByElapsed(elapsed,this.cloverCumulativeTime,...);// 更新状态驱动 UI 刷新this.currentHanhanFrameIndexnewHanhanFrame;this.currentCloverFrameIndexnewCloverFrame;},16);}当动画播放完毕达到LIVE_CARD_DURATION 3500ms憨憨从睡眠状态切换为起床状态同时通过formProvider.updateForm()反向同步传统卡片的状态——实现了互动卡片与传统卡片的数据联动。总结互动卡片的架构设计呈现了 HarmonyOS 服务卡片从静态展示到动态交互的演进路径。通过LiveFormExtensionAbilityUIExtensionContentSession的组合开发者获得了完整的 UI 渲染能力而sceneAnimationParams机制则巧妙地解决了传统 widget 与互动卡片之间的衔接过渡问题。LiveCard 项目展示的四种卡片各具特色在共享同一套架构基础的同时通过postCardAction通信、formProvider.updateForm数据推送、以及callee方法注册等机制实现了丰富的交互体验。