HarmonyOS技术精讲-Form Kit(卡片开发服务)第4篇:卡片数据更新机制——定时刷新与事件驱动

HarmonyOS技术精讲-Form Kit(卡片开发服务)第4篇:卡片数据更新机制——定时刷新与事件驱动
问题来了:卡片数据怎么动起来?
很多人第一次接触HarmonyOS卡片开发时,发现卡片做好了,但数据是死的。温度永远是25度,天气永远是晴天——即使外面的太阳已经晒得人发晕,卡片上的信息纹丝不动。
这个问题并不是个别案例。Form Kit(卡片开发服务) 提供了两种数据更新方式:定时刷新和事件驱动。前者通过配置 updateDuration 让系统自动拉新数据,后者通过 formProvider 在应用内部主动触发更新。但真正难的是:什么时候用定时,什么时候用手动,以及两者如何配合。
这篇文章用一个模拟天气卡片来拆解这个问题。
两种更新方式,适合的场景完全不同
| 特性 | 定时刷新 (updateDuration) | 事件驱动 (formProvider.updateForm) |
|---|---|---|
| 触发方式 | 系统周期拉取,与应用生命周期无关 | 应用主动触发,依赖进程存活 |
| 更新频率 | 固定时间间隔,最小30分钟 | 任意时间点,可秒级更新 |
| 适用场景 | 天气、新闻、日历等周期性数据 | 待办完成、开关切换、支付结果 |
| 缺点 | 频率受限,无法即时响应 | 应用被杀后失效 |
| 推荐用法 | 作为保底策略 | 作为即时响应策略 |
这句是重点:实际项目里很少有只用一种方式的场景。天气卡片既要每小时自动刷新温度,也要支持用户点击刷新按钮即时更新。两者不是二选一,而是互补。
环境说明
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:手机 / 平板
核心实现:天气卡片实时更新
第一步:创建卡片FormAbility
卡片的所有逻辑入口在 FormAbility 中。这里要重点理解:卡片不是常驻进程,它由 FormAbility 托管生命周期。定时更新和事件驱动最终都会落到 onUpdateForm 方法。
// EntryFormAbility.ts
import { FormBindingData, formProvider, FormAbility } from '@kit.FormKit';
import { JSON } from '@kit.ArkTS';
export default class EntryFormAbility extends FormAbility {
// 卡片被添加时触发
onAddForm(want): FormBindingData {
let formData = this.getMockWeatherData();
return formProvider.createFormBindingData({
temperature: formData.temperature,
weather: formData.weather,
city: formData.city,
updateTime: new Date().toLocaleString()
});
}
// 定时更新与事件驱动都会触发此方法
onUpdateForm(formId: string): void {
let formData = this.getMockWeatherData();
let newData = {
temperature: formData.temperature,
weather: formData.weather,
city: formData.city,
updateTime: new Date().toLocaleString()
};
formProvider.updateForm(formId,
formProvider.createFormBindingData(newData))
.then(() => {
console.info('卡片更新成功');
})
.catch((err: Error) => {
console.error('卡片更新失败: ' + JSON.stringify(err));
});
}
// 模拟天气数据
private getMockWeatherData(): object {
let temperature = Math.floor(Math.random() * 15 + 20); // 20-35度
let weathers = ['晴', '多云', '阴', '小雨'];
let weather = weathers[Math.floor(Math.random() * weathers.length)];
let cities = ['北京', '上海', '广州', '深圳'];
let city = cities[Math.floor(Math.random() * cities.length)];
return { temperature, weather, city };
}
}
代码解读:
onAddForm只在卡片首次添加到桌面时执行一次,用于初始化数据。onUpdateForm是核心入口,定时和手动都会走到这里。formProvider.updateForm是真正的更新动作,第2个参数必须传createFormBindingData返回的对象。- 模拟数据是为了演示,实际项目里一般从网络或本地数据库获取。
第二步:配置定时更新
定时更新不需要写任何定时器代码,只需在 module.json5 中配置 updateDuration。
// entry/src/main/module.json5
{
"module": {
"abilities": [
{
"name": "EntryFormAbility",
"type": "form",
"formConfig": {
"updateEnabled": true,
"updateDuration": 60, // 单位:分钟,最小30分钟
"formVisibleNotify": true
}
}
]
}
}
注意事项:
updateEnabled必须为true,否则定时更新不生效。updateDuration单位是分钟,最小值为30。设置60表示每小时更新一次,但实测可能会有几分钟的延迟,这是系统功耗管理机制导致的。formVisibleNotify建议设置为true,这样系统只会在卡片可见时触发更新,避免后台浪费资源。
第三步:添加点击更新按钮
定时更新解决了周期性问题,但用户点击卡片上的刷新按钮时,需要立即更新。这个需要在卡片UI中处理。
// widget/pages/WeatherCard.ets
import { formProvider, postCardAction } from '@kit.FormKit';
@Component
export default struct WeatherCard {
@State temperature: string = '--';
@State weather: string = '--';
@State city: string = '--';
@State updateTime: string = '--';
build() {
Column() {
// 城市名称
Text(this.city)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.margin({ top: 12 })
// 温度,支持点击触发事件
Text(this.temperature + '°C')
.fontSize(48)
.fontWeight(FontWeight.Bold)
.margin({ top: 8 })
.onClick(() => {
// 发送事件通知FormAbility更新数据
postCardAction(this, {
action: 'message',
params: {
type: 'refresh'
}
});
})
// 天气描述
Text(this.weather)
.fontSize(16)
.margin({ top: 4 })
// 更新时间
Text('更新于: ' + this.updateTime)
.fontSize(10)
.fontColor('#999999')
.margin({ top: 8 })
// 刷新按钮
Button('刷新')
.width(60)
.height(28)
.fontSize(12)
.margin({ top: 8 })
.onClick(() => {
postCardAction(this, {
action: 'message',
params: {
type: 'refresh'
}
});
})
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.backgroundColor('#F0F8FF')
}
}
然后需要在 FormAbility 中处理这个事件:
// EntryFormAbility.ts - 添加消息处理方法
export default class EntryFormAbility extends FormAbility {
// 处理卡片发送的事件
onFormEvent(formId: string, message: string): void {
let params: Record<string, string> = JSON.parse(message);
if (params.type === 'refresh') {
// 手动触发更新
this.onUpdateForm(formId);
}
}
}
这里有个关键点:卡片内的 onClick 不能直接调用 formProvider.updateForm,因为卡片运行在独立的渲染进程中。必须通过 postCardAction 将事件发送给 FormAbility,由它在宿主进程中执行更新逻辑。
第四步:实现数据获取逻辑
前面用的 getMockWeatherData 只是模拟,实际项目要从网络或本地获取。以下是一个带网络请求的版本:
// services/WeatherService.ts
import { http } from '@kit.NetworkKit';
export namespace WeatherService {
// 真实项目里使用真实API
export async function fetchWeather(city: string): Promise<object> {
try {
let response = await http.createHttp().request(
`https://api.weather.com/v1/${city}`,
{
method: http.RequestMethod.GET,
connectTimeout: 5000,
readTimeout: 5000
}
);
if (response.responseCode === 200) {
let body = JSON.parse(response.result);
return {
temperature: body.temperature,
weather: body.weather,
city: city
};
}
} catch (error) {
// 网络失败时返回兜底数据
console.error('网络请求失败: ' + JSON.stringify(error));
}
return { temperature: '--', weather: '--', city: city };
}
}
在 FormAbility 中使用:
// EntryFormAbility.ts
import { WeatherService } from '../services/WeatherService';
export default class EntryFormAbility extends FormAbility {
async onUpdateForm(formId: string): Promise<void> {
let formData = await WeatherService.fetchWeather('beijing');
formProvider.updateForm(formId,
formProvider.createFormBindingData(formData));
}
}
注意:onUpdateForm 现在是 async 函数,返回 Promise<void>。系统会等待 Promise 完成后才认为更新结束,所以网络请求的超时时间要合理设置,避免长时间阻塞。
常见问题 1:定时更新不生效
现象:配置了 updateDuration,但卡片数据从未自动更新。
原因:最常见的原因是应用进程被系统杀死。卡片 FormAbility 的定时更新依赖 Ability 进程存活,如果用户的设备内存不足,系统会回收后台进程。另外,updateDuration 是以分钟为单位,但系统实际触发时间可能与配置有偏差(±5分钟),这是功耗管理策略。
解决方案:
- 确保
updateEnabled和formVisibleNotify都配置正确。 - 如果定时更新无法满足业务需求,考虑使用后台任务
WorkScheduler保活。 - 实际项目中建议将定时更新作为保底策略,事件驱动作为主要更新方式。
常见问题 2:即使手动点击更新,UI也没有变化
现象:formProvider.updateForm 调用成功(日志输出"更新成功"),但卡片上的温度没有变化。
原因:updateForm 成功后,系统不会立即刷新卡片UI。ArkUI 卡片的数据绑定有一个延迟生效机制,大概在100-300ms后才能看到效果。另外,如果连续多次调用 updateForm,系统只会应用最后一次的结果,中间的更新会被丢弃。
解决方案:
- 不要短时间内频繁调用
updateForm,建议加防抖处理。 - 如果需要连续更新,每次调用之间至少间隔500ms。
- 卡片UI中的
@State变量名必须与createFormBindingData中的键名完全一致,否则绑定失效。
最佳实践
-
合理设置定时更新频率
官方文档说最小30分钟,但实际测试发现,如果用户的设备处于省电模式,更新周期可能延长到60分钟以上。对于天气类卡片,60分钟的更新频率已经足够;对于股票类卡片,30分钟是合理的折中。 -
事件驱动与定时更新配合使用
定时更新保证卡片在无人操作时也有新数据,事件驱动保证用户点击后立即反馈。两者结合才是完整的解决方案。不要在onFormEvent中重复实现数据获取逻辑,直接复用onUpdateForm。 -
数据获取失败要有兜底逻辑
网络不稳定时,fetchWeather可能失败。如果在onUpdateForm中抛出异常且未捕获,系统会认为更新失败,卡片数据保持不变。建议在所有异步方法中加入try-catch,失败时返回默认数据。 -
不要在主线程做耗时操作
onUpdateForm是在主线程中被调用,如果在这里执行网络请求,会阻塞其他事件处理。正确的做法是使用异步方法(async/await)或将耗时操作放到子线程。
Demo入口
// entry/src/main/ets/entryability/EntryAbility.ts
import { Ability } from '@kit.AbilityKit';
export default class EntryAbility extends Ability {
onCreate(want, launchParam): void {
// 应用启动逻辑
}
}
卡片UI入口:
// widget/pages/WeatherCard.ets
@Entry
@Component
struct Index {
build() {
WeatherCard()
}
}
示例代码地址:项目地址
FAQ
Q:updateDuration 设置15分钟可以吗?
A:不可以。官方限制最小值为30分钟,设置小于30的值会被系统强制调整为30。如果需要更快的更新频率,必须使用事件驱动方式。
Q:为什么真机上定时更新正常,模拟器上却不生效?
A:模拟器中的系统服务并不完整,尤其是功耗管理相关的服务。定时更新在模拟器上经常表现出随机行为,建议在所有开发阶段都以真机为准。
Q:应用进程被杀后,事件驱动更新就失效了,怎么办?
A:这是 Form Kit(卡片开发服务) 的设计限制。如果你需要应用被杀后卡片仍能更新,可以配置 formVisibleNotify 为 true,并在 onUpdateForm 中实现数据获取逻辑。系统在卡片可见时仍会尝试调用 onUpdateForm,前提是应用进程被重新拉起。但拉起会有延迟,不可控。
Q:createFormBindingData 中可以传递复杂对象吗?
A:可以,但要确保对象可以被序列化为JSON。不支持传递函数、Symbol等非序列化数据。对象中的Key对应的Value类型必须是字符串、数字、布尔值、数组或对象。
更多推荐


所有评论(0)