第4.7篇:服务卡片开发------form_config 与 CreationFormAbility
难度 :⭐⭐⭐ 高级
前置知识 :第 1.6 篇 页面路由与生命周期
涉及源文件 :
products/default/src/main/ets/creationformability/CreationFormAbility.ets、products/default/form_config.json

概述
服务卡片(Service Widget)是 HarmonyOS 最具特色的系统级能力之一。它允许应用在桌面上以"卡片"形态展示关键信息,用户无需打开应用即可获取实时内容或执行快捷操作。在"画伴梦工厂"中,服务卡片用于在桌面上展示用户的创作作品概览------每次抬手看桌面,都能看到最新的或随机推荐的一幅画作标题,营造"每次都是惊喜"的体验。
服务卡片的开发涉及两个核心文件:
form_config.json:卡片的声明式配置文件,定义卡片的名称、尺寸、UI 文件路径、更新策略等元信息。CreationFormAbility.ets:卡片的后端逻辑入口,继承FormExtensionAbility,负责卡片生命周期管理、数据绑定和刷新。
本文将围绕这两个文件,从配置到代码,逐层拆解服务卡片的完整开发流程。
一、form_config.json 卡片配置详解
form_config.json 是服务卡片的"身份证",它必须放置在 products/default/ 目录下,与 src/ 同级。系统在应用安装时读取该文件,注册所有声明的服务卡片。
1.1 项目中的完整配置
json
{
"forms": [
{
"name": "creation",
"displayName": "$string:creation_form_display_name",
"description": "$string:creation_form_desc",
"src": "./ets/creationcard/pages/CreationCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDynamic": true,
"isDefault": true,
"updateEnabled": false,
"scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*4",
"supportDimensions": ["2*4"]
}
]
}
1.2 字段逐项解析
| 字段 | 值 | 说明 |
|---|---|---|
| name | "creation" |
卡片唯一标识名,在代码中通过此名称引用该卡片 |
| displayName | $string:creation_form_display_name |
卡片在桌面添加菜单中的显示名称,通过 $string 引用资源文件 |
| description | $string:creation_form_desc |
卡片的描述文本,同样通过资源引用 |
| src | "./ets/creationcard/pages/CreationCard.ets" |
卡片 UI 的 ArkTS 源文件路径,相对于 src/main/ets/ 目录 |
| uiSyntax | "arkts" |
UI 语法类型,"arkts" 表示使用 ArkTS 声明式语法开发卡片 |
| window | { designWidth: 720, autoDesignWidth: true } |
卡片的设计尺寸基准,designWidth 为设计稿宽度(720vp),autoDesignWidth 自动适配屏幕 |
| colorMode | "auto" |
卡片颜色模式,可选 "auto"(跟随系统)、"light"(浅色)、"dark"(深色) |
| isDynamic | true |
是否支持动态刷新。true 表示卡片数据可以动态更新 |
| isDefault | true |
是否为默认卡片。用户在桌面添加卡片时,默认选中此规格 |
| updateEnabled | false |
是否启用定时更新。设为 false 时,scheduledUpdateTime 和 updateDuration 不生效 |
| scheduledUpdateTime | "10:30" |
定时的更新时间(HH:mm),updateEnabled 为 true 时生效 |
| updateDuration | 1 |
定时更新周期(单位:小时)。设为 1 表示每小时刷新一次 |
| defaultDimension | "2*4" |
默认卡片尺寸,格式为 "行数*列数",系统网格中每格约 30vp |
| supportDimensions | ["2*4"] |
支持的卡片尺寸列表,可配置多个规格,如 ["1*2", "2*2", "2*4"] |
1.3 关键配置设计说明
为什么不启用定时更新(updateEnabled: false)?
在"画伴梦工厂"中,卡片数据来源于用户的作品列表。当用户在应用内创作了新作品后,才需要刷新卡片。定时刷新(如每小时一次)既不必要,也浪费系统资源。因此项目采用了事件驱动 的刷新方式------在 onUpdateForm 和 onFormEvent 生命周期回调中主动触发更新,而非依赖系统定时器。
为什么只支持 2*4 一种尺寸?
2×4 规格(约 60vp × 120vp)是桌面卡片中最适合展示单条作品信息的尺寸。它足够显示作品标题,又不会占用过多桌面空间。对于"画伴梦工厂"的使用场景------展示一条作品标题和图标------2×4 恰到好处。如需扩展,可添加 "1*2"(紧凑型)或 "4*4"(丰富信息型)等规格。
window 配置的设计意图
json
"window": { "designWidth": 720, "autoDesignWidth": true }
designWidth: 720:以 720vp 宽度为设计基准,所有尺寸属性基于此缩放。autoDesignWidth: true:卡片在不同屏幕密度的设备上自动缩放,确保显示效果一致。
二、FormExtensionAbility 生命周期
FormExtensionAbility 是服务卡片的后端能力入口,它继承自 ExtensionAbility,负责管理卡片从创建到销毁的完整生命周期。
2.1 生命周期全景
用户添加卡片
│
▼
onAddForm(want) → 创建卡片表单 → 返回 FormBindingData
│
├──→ onCastToNormalForm(formId) ← 临时卡片转常驻(非必须)
│
├──→ onUpdateForm(formId) ← 系统/主动触发更新
│
├──→ onFormEvent(formId, msg) ← 卡片内交互事件
│
├──→ onAcquireFormState(want) ← 查询卡片状态
│
└──→ onRemoveForm(formId) ← 用户删除卡片
2.2 onAddForm:创建卡片
当用户在桌面添加"画伴梦工厂"的服务卡片时,系统调用 onAddForm 方法。这是卡片生命中最重要的起点------它负责返回卡片的初始数据。
typescript
onAddForm(want: Want): formBindingData.FormBindingData {
return formBindingData.createFormBindingData(this.getCardData());
}
参数说明:
want:包含调用方信息的Want对象,可从want.parameters获取卡片参数(如卡片 ID、尺寸等)。
返回值:
- 必须返回
formBindingData.FormBindingData类型,包含卡片 UI 需要渲染的数据。
项目中 onAddForm 的实现非常简洁------它委托给 getCardData() 方法获取最新的作品数据,然后通过 formBindingData.createFormBindingData 将其封装为可发送给卡片 UI 的数据包。
2.3 onUpdateForm:更新卡片数据
当需要刷新卡片数据时,系统或应用调用 onUpdateForm。项目中响应此回调,通过 formProvider.updateForm 主动推送新数据:
typescript
onUpdateForm(formId: string): void {
formProvider.updateForm(formId,
formBindingData.createFormBindingData(this.getCardData()));
}
formProvider.updateForm 是 @kit.FormKit 中用于更新指定卡片的 API。它接收两个参数:
- formId:目标卡片的唯一标识符,由系统分配。
- FormBindingData:新的绑定数据。
每当卡片需要刷新时(如用户创作了新作品),只需调用 onUpdateForm 即可触达所有已添加的卡片实例。
2.4 onFormEvent:响应卡片交互
当用户在卡片上执行交互操作(如点击按钮)时,卡片 UI 可以通过 postCardAction 发送事件,系统会将该事件路由到 onFormEvent:
typescript
onFormEvent(formId: string, message: string): void {
formProvider.updateForm(formId,
formBindingData.createFormBindingData(this.getCardData()));
}
在项目中,onFormEvent 和 onUpdateForm 的处理逻辑完全一致------都是获取最新数据并刷新卡片。这种设计使得无论以何种方式触发刷新,卡片都能展示最新的作品信息。
message 参数的典型内容:
json
{
"action": "router",
"bundleName": "com.example.drawing",
"abilityName": "EntryAbility",
"params": { "workIndex": "3" }
}
卡片 UI 通过 postCardAction 发送的事件字符串,此处可根据 message 内容实现不同的交互逻辑(如路由跳转到指定作品详情页)。
2.5 onRemoveForm:卡片被移除
当用户从桌面删除服务卡片时,系统调用此方法:
typescript
onRemoveForm(formId: string): void {
// 可在此清理与该卡片相关的资源
}
虽然项目中的实现为空,但在复杂场景下,可以在此处清理为该卡片分配的资源(如取消定时器、释放监听器等),避免资源泄漏。
2.6 onAcquireFormState:查询卡片状态
当系统需要了解卡片当前是否可以正常使用时,调用此方法:
typescript
onAcquireFormState(want: Want): formInfo.FormState {
return formInfo.FormState.READY;
}
返回值 formInfo.FormState 枚举:
| 枚举值 | 说明 |
|---|---|
FormState.READY |
卡片可用 |
FormState.UNKNOWN |
卡片状态未知 |
项目中始终返回 READY,表示卡片随时可用。
2.7 onCastToNormalForm:临时卡片转常驻
typescript
onCastToNormalForm(formId: string): void {
}
当一张临时卡片(如从服务卡片预览添加的卡片)被转为常驻卡片时触发。项目中暂未涉及此场景,实现为空。
三、卡片数据绑定与 formBindingData
服务卡片的数据流遵循"后端提供数据,前端渲染 UI"的模型。formBindingData 是连接后端(FormExtensionAbility)和前端(ArkTS 卡片 UI)的桥梁。
3.1 createFormBindingData 方法
typescript
formBindingData.createFormBindingData(this.getCardData())
createFormBindingData 接收一个普通对象,将其序列化为卡片 UI 可消费的数据结构。在项目中,这个数据对象遵循 CreationCardData 接口:
typescript
interface CreationCardData {
title: string; // 作品标题
workIndex: string; // 作品索引号
}
这两个字段将被传递给卡片 UI 文件 CreationCard.ets,在 ArkTS 声明式 UI 中通过 @Consume 或 @LocalStorageProp 等装饰器接收:
typescript
// CreationCard.ets(示意)
@Entry
@Component
struct CreationCard {
@LocalStorageProp('title') title: string = '';
@LocalStorageProp('workIndex') string = '';
build() {
Column() {
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
// ... 更多 UI 组件
}
}
}
3.2 数据流示意图
CreationFormAbility.ets CreationCard.ets
┌──────────────────────┐ ┌──────────────────┐
│ getCardData() │ │ @LocalStorageProp │
│ → { title, workIdx }│───绑定数据──→│ title │
│ │ │ workIndex │
│ createFormBindingData│ │ │
│ → FormBindingData │ │ build() { │
│ │ │ Text(title) │
│ onAddForm → return │ │ } │
└──────────────────────┘ └──────────────────┘
四、preferences 数据读取与作品数据集成
服务卡片的核心数据来源是用户已保存的视频作品。项目使用 preferences (首选项)进行本地持久化存储,CreationFormAbility 在 onAddForm 和 onUpdateForm 时从 preferences 中读取最新作品。
4.1 读取最新作品
typescript
private getLatestSavedWork(): VideoWork | undefined {
try {
const store = preferences.getPreferencesSync(this.context, { name: PREFERENCE_NAME });
const rawValue = store.getSync(WORKS_KEY, '[]') as string;
if (typeof rawValue !== 'string') {
return undefined;
}
const works = JSON.parse(rawValue) as VideoWork[];
if (works.length === 0) {
return undefined;
}
return works.sort(
(left: VideoWork, right: VideoWork): number => right.createdAt - left.createdAt
)[0];
} catch {
return undefined;
}
}
关键步骤拆解:
| 步骤 | 代码 | 说明 |
|---|---|---|
| 1. 打开存储 | preferences.getPreferencesSync(this.context, { name: 'video_work_repository' }) |
获取应用首选项存储实例,name 为存储文件标识 |
| 2. 读取数据 | store.getSync('works', '[]') |
从 works 键读取 JSON 字符串,默认返回空数组 '[]' |
| 3. 类型校验 | typeof rawValue !== 'string' |
确保读取到的值是字符串类型,防御非预期数据 |
| 4. 反序列化 | JSON.parse(rawValue) |
将 JSON 字符串解析为 VideoWork 对象数组 |
| 5. 空值检查 | works.length === 0 |
无作品时返回 undefined |
| 6. 排序取最新 | .sort((a, b) => b.createdAt - a.createdAt)[0] |
按 createdAt 降序排列,取第一条(最新) |
4.2 VideoWork 数据模型
typescript
interface VideoWork {
title: string; // 作品标题
createdAt: number; // 创建时间戳(毫秒)
}
createdAt 字段用于排序------时间戳越大的作品越新,确保卡片始终展示用户最新创作的画作。
4.3 与 WorkRepository 的集成关系
CreationFormAbility 读取的 preferences 存储与 WorkRepository(作品仓库服务)共享同一存储实例。当用户在应用内通过 WorkRepository 创建或更新作品时,数据被写入 video_work_repository 存储的 works 键下。服务卡片通过读取相同的存储键,自然获得了最新的作品数据。
这种存储层共享的设计模式,使得服务卡片与主应用之间无需额外的通信机制即可保持数据一致------它们只是从同一个"数据池"中读取数据。
五、兜底策略:随机示例作品
当用户尚未创作任何作品时(即 preferences 中 works 为空数组或不存在),getLatestSavedWork 返回 undefined。此时 getCardData 方法会退而使用预置的示例作品列表:
typescript
private getCardData(): CreationCardData {
const latestWork = this.getLatestSavedWork();
if (latestWork) {
return {
title: latestWork.title,
workIndex: '1'
};
}
const selectedWork = this.getRandomWork();
return {
title: selectedWork.title,
workIndex: selectedWork.workIndex
};
}
5.1 示例作品数据源
typescript
const CARD_WORKS: CreationCardWork[] = [
{ title: '蜡笔森林小恐龙', workIndex: '1' },
{ title: '蜜糖色海岛寻宝', workIndex: '2' },
{ title: '彩虹城堡剧场', workIndex: '3' },
{ title: '星空飞船旅行', workIndex: '4' },
{ title: '奇妙动物伙伴', workIndex: '5' }
];
这五个作品标题充满了童趣和想象力,与"画伴梦工厂"的品牌调性高度一致。它们的作用不仅是占位,更是在新用户首次添加卡片时,展示应用的核心主题风格,吸引用户去探索和创作。
5.2 随机选择逻辑
typescript
private getRandomWork(): CreationCardWork {
const index = Math.floor(Math.random() * CARD_WORKS.length);
return CARD_WORKS[index];
}
使用 Math.random() 生成 [0, 1) 间的随机数,乘以数组长度后向下取整,得到 0~4 之间的随机索引。每次添加卡片或刷新时,可能展示不同的示例作品,增加了卡片的趣味性。
5.3 策略执行流程
getCardData()
│
├── getLatestSavedWork()
│ ├── 打开 preferences 存储
│ ├── 读取 works 键的 JSON
│ ├── works.length > 0 → 按 createdAt 排序 → 返回最新作品
│ └── 异常/空 → 返回 undefined
│
├── latestWork 存在 → { title: latestWork.title, workIndex: '1' }
│
└── latestWork 为 undefined → getRandomWork()
├── Math.random() × CARD_WORKS.length
├── 取随机索引
└── 返回随机示例作品
这种"有用户数据就展示用户数据,没有就展示示例数据"的兜底策略,保证了卡片在任何情况下都能展示有意义的内容------用户永远不会看到空卡片。
六、卡片更新机制
6.1 更新触发方式
服务卡片的更新有四种触发方式:
| 触发方式 | 对应回调 | 项目中的应用 |
|---|---|---|
| 系统定时更新 | onUpdateForm |
未启用(updateEnabled: false) |
| 应用主动推送 | formProvider.updateForm |
主应用中新作品创作完成后调用 |
| 卡片交互事件 | onFormEvent |
用户点击卡片上的刷新按钮 |
| 卡片生命周期 | onUpdateForm |
系统在某些场景下自动触发 |
6.2 formProvider.updateForm
formProvider 是 @kit.FormKit 中用于操作服务卡片的提供者。updateForm 方法是最核心的更新 API:
typescript
formProvider.updateForm(formId, formBindingData.createFormBindingData(this.getCardData()));
参数详解:
- formId:系统在创建卡片时分配的字符串 ID,用于唯一标识一张卡片实例。同一应用可以添加多张同类型卡片到桌面,每张卡片拥有独立的 formId。
- FormBindingData:新的绑定数据,替换卡片当前的 UI 数据。
6.3 更新流程时序
主应用(Index.ets) CreationFormAbility 桌面卡片 UI
│ │ │
│ 用户创作新作品 │ │
│ 保存到 preferences │ │
│ │ │
│ 调用 updateForm │ │
│──────→ onUpdateForm(formId) │ │
│ │ │ │
│ ├── getCardData() │ │
│ │ ├── getLatestSavedWork() │ │
│ │ └── 获取最新作品 │ │
│ │ │ │
│ └── formProvider.updateForm() │ │
│ │─────────────│────→ 卡片 UI 刷新 │
│ │ │ Text(title) 更新 │
│ │ │ │
6.4 更新时机选择
项目中,卡片更新并不由系统定时器驱动,而是在以下时机触发:
- 卡片被添加时 (
onAddForm)→ 返回当前最新数据 - 主应用中有新作品生成时 → 调用
formProvider.updateForm - 卡片交互事件 (
onFormEvent)→ 用户操作后刷新
这种"按需更新"模式相比定时更新,具有更低的功耗和更及时的数据一致性。
七、2×4 卡片尺寸与 ArkTS 卡片 UI
7.1 网格系统与卡片尺寸
HarmonyOS 的服务卡片采用网格(Grid)系统进行尺寸管理。标准网格中每个单元格的基准尺寸约为 30vp × 30vp。2×4 表示卡片宽度占 4 列、高度占 2 行:
| 尺寸规格 | 宽度 | 高度 | 适用场景 |
|---|---|---|---|
| 1×2 | ~60vp | ~30vp | 紧凑型信息(如天气、日期) |
| 2×2 | ~60vp | ~60vp | 小型功能卡片 |
| 2×4 | ~120vp | ~60vp | 中等信息量(如作品展示) |
| 4×4 | ~120vp | ~120vp | 丰富信息型(如图片墙) |
项目中选用 2×4 规格,可以舒适地展示作品图标和标题,在桌面空间和信息密度之间取得良好平衡。
7.2 ArkTS 卡片 UI 语法
uiSyntax: "arkts" 表明卡片使用 ArkTS 声明式语法开发。卡片 UI 文件 CreationCard.ets 是一个标准的 @Entry @Component 结构:
typescript
// CreationCard.ets(示意结构)
@Entry
@Component
struct CreationCard {
@LocalStorageProp('title') title: string = '';
@LocalStorageProp('workIndex') workIndex: string = '';
build() {
Column() {
// 卡片头部图标区域
Image($r('app.media.app_icon'))
.width(40)
.height(40)
.margin({ top: 8 })
// 作品标题
Text(this.title)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Center)
.width('100%')
.padding({ left: 8, right: 8 })
// 提示文字
Text('点击查看详情')
.fontSize(10)
.fontColor('#888888')
}
.width('100%')
.height('100%')
.padding(12)
.borderRadius(12)
.backgroundColor('#FFFFFF')
}
}
关键点解析:
@LocalStorageProp:卡片专用的状态装饰器,用于接收formBindingData.createFormBindingData传递的数据。key 名称需与数据对象中的字段名一一对应。@Entry:标识该组件是卡片入口,与普通页面类似。- 限制 :卡片 UI 中可用的组件和 API 是受限制的,不支持所有 ArkUI 组件。常见可用组件包括
Text、Image、Column、Row、Button等基础组件。
八、完整代码架构总览
8.1 CreationFormAbility 完整实现
typescript
import { Want } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';
import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit';
interface CreationCardData {
title: string;
workIndex: string;
}
interface CreationCardWork {
title: string;
workIndex: string;
}
interface VideoWork {
title: string;
createdAt: number;
}
const PREFERENCE_NAME: string = 'video_work_repository';
const WORKS_KEY: string = 'works';
const CARD_WORKS: CreationCardWork[] = [
{ title: '蜡笔森林小恐龙', workIndex: '1' },
{ title: '蜜糖色海岛寻宝', workIndex: '2' },
{ title: '彩虹城堡剧场', workIndex: '3' },
{ title: '星空飞船旅行', workIndex: '4' },
{ title: '奇妙动物伙伴', workIndex: '5' }
];
export default class CreationFormAbility extends FormExtensionAbility {
onAddForm(want: Want): formBindingData.FormBindingData {
return formBindingData.createFormBindingData(this.getCardData());
}
private getCardData(): CreationCardData {
const latestWork = this.getLatestSavedWork();
if (latestWork) {
return { title: latestWork.title, workIndex: '1' };
}
const selectedWork = this.getRandomWork();
return { title: selectedWork.title, workIndex: selectedWork.workIndex };
}
private getLatestSavedWork(): VideoWork | undefined {
try {
const store = preferences.getPreferencesSync(this.context, { name: PREFERENCE_NAME });
const rawValue = store.getSync(WORKS_KEY, '[]') as string;
if (typeof rawValue !== 'string') { return undefined; }
const works = JSON.parse(rawValue) as VideoWork[];
if (works.length === 0) { return undefined; }
return works.sort((a, b) => b.createdAt - a.createdAt)[0];
} catch { return undefined; }
}
private getRandomWork(): CreationCardWork {
const index = Math.floor(Math.random() * CARD_WORKS.length);
return CARD_WORKS[index];
}
onCastToNormalForm(formId: string): void {}
onUpdateForm(formId: string): void {
formProvider.updateForm(formId,
formBindingData.createFormBindingData(this.getCardData()));
}
onFormEvent(formId: string, message: string): void {
formProvider.updateForm(formId,
formBindingData.createFormBindingData(this.getCardData()));
}
onRemoveForm(formId: string): void {}
onAcquireFormState(want: Want): formInfo.FormState {
return formInfo.FormState.READY;
}
}
8.2 模块依赖关系
@kit.FormKit (formBindingData, FormExtensionAbility, formInfo, formProvider)
│
├── formBindingData.createFormBindingData() ← 封装卡片数据
├── FormExtensionAbility ← 卡片能力基类
├── formInfo.FormState ← 卡片状态枚举
└── formProvider.updateForm() ← 推送卡片更新
@kit.AbilityKit (Want) ← 卡片参数传递
@kit.ArkData (preferences) ← 作品数据持久化
8.3 与主应用的数据流关系
主应用 WorkRepository CreationFormAbility 桌面卡片
┌─────────────────────┐ ┌──────────────────────┐ ┌────────────┐
│ preferences 存储 │ │ │ │ │
│ ┌───────────────┐ │ 读取同一 │ getLatestSavedWork()│ │ 卡片 UI │
│ │ video_work_ │←│───存储─────│ → 排序取最新作品 │──数据──→│ 标题展示 │
│ │ repository │ │ │ │ │ │
│ │ └ works ──┐ │ │ │ 或 getRandomWork() │ │ │
│ │ │ │ │ │ → 随机预置作品 │ │ │
│ │ JSON 数组 │ │ │ │ │ │ │
│ └───────────────┘ │ └──────────────────────┘ └────────────┘
│ │
│ 写入新作品 → flush │
└─────────────────────┘
九、服务卡片的注册与配置
要让 CreationFormAbility 生效,还需要在 module.json5 中注册 ExtensionAbility。这是连接 form_config.json 与 CreationFormAbility.ets 的关键环节:
json
{
"module": {
"extensionAbilities": [
{
"name": "CreationFormAbility",
"srcEntry": "./ets/creationformability/CreationFormAbility.ets",
"description": "服务卡片能力",
"type": "form",
"metadata": [
{
"name": "ohos.extension.form",
"resource": "$profile:form_config"
}
]
}
]
}
}
配置要点:
- type :
"form"表示这是一个服务卡片 ExtensionAbility。 - srcEntry : 指向
CreationFormAbility.ets的路径。 - metadata : 通过
"ohos.extension.form"名称和$profile:form_config资源引用,将form_config.json与当前 ExtensionAbility 关联起来。 form_config.json文件通常放置在resources/base/profile/目录下(项目中的路径为products/default/form_config.json,构建时会被复制到 profile 资源目录)。
十、最佳实践与注意事项
10.1 数据量控制
服务卡片可携带的数据量有限,FormBindingData 的序列化大小不宜过大。建议仅传输核心展示字段(如 title),避免将完整的作品 JSON 数据(含图片 URI、故事文本等)全部塞入卡片。
10.2 异常处理
getLatestSavedWork 中的 try-catch 保护至关重要------preferences 操作可能因存储损坏、权限不足等原因抛出异常。不加保护的话,卡片添加时可能直接崩溃。
10.3 资源释放
onRemoveForm 虽然项目中未使用,但在实际开发中应养成习惯:若在卡片生命周期中注册了监听器或开启了定时器,务必在 onRemoveForm 中对称释放。
10.4 卡片 UI 限制
ArkTS 卡片 UI 相比普通页面有较多限制:
- 不支持所有 ArkUI 组件(仅支持基础组件)
- 不支持
@State、@Link等装饰器,数据只能通过@LocalStorageProp/@LocalStorageLink接收 - 不支持网络请求、文件读写等耗时操作
- 交互能力有限(主要通过
postCardAction与onFormEvent通信)
10.5 formId 的管理
每张添加到桌面的卡片都有一个唯一的 formId,应用需要妥善管理这些 ID。当需要全量刷新所有卡片时,可遍历 formId 列表逐一调用 updateForm。
总结
本文通过"画伴梦工厂"的服务卡片实现,完整呈现了 HarmonyOS 服务卡片的开发全流程:
| 知识点 | 实现方式 |
|---|---|
| form_config.json 配置 | 定义卡片名称、尺寸(2×4)、UI 路径、更新策略等元信息 |
| FormExtensionAbility 生命周期 | onAddForm 创建 → onUpdateForm 更新 → onRemoveForm 销毁 |
| 卡片数据绑定 | formBindingData.createFormBindingData 封装数据,@LocalStorageProp 在 UI 中消费 |
| preferences 集成 | 从 video_work_repository 存储读取最新作品,按 createdAt 排序 |
| 兜底策略 | 无用户数据时退而使用随机预置作品(5 个童趣标题) |
| 卡片更新 | onUpdateForm + formProvider.updateForm 组合,事件驱动而非定时驱动 |
| ArkTS 卡片 UI | @Entry @Component 声明式卡片,受限组件集 |
| 注册配置 | module.json5 中 extensionAbilities 配置 type: "form",metadata 关联 form_config |
服务卡片的设计哲学是"轻量、聚焦、实时"------用最轻量的数据展示最聚焦的信息,在用户需要时提供最新的内容。这种"桌面即服务"的体验,正是 HarmonyOS 全场景智慧生态的生动体现。
参考源码
本文所有代码均来自项目文件:
products/default/src/main/ets/creationformability/CreationFormAbility.ets--- 服务卡片 ExtensionAbility 的完整实现,包含生命周期回调、preferences 数据读取、随机作品兜底等核心方法products/default/form_config.json--- 服务卡片的 JSON 配置文件,定义所有卡片规格和元数据