HarmonyOS 卡片更新与数据交互指南

摘要:本文系统介绍 HarmonyOS 应用卡片的更新机制与数据交互方案,涵盖定时更新、主动更新、call 事件刷新、Preferences 数据传递等核心知识点,配合完整的设备信息卡片案例代码,帮助开发者深入理解卡片数据流转的全链路。


效果

一、卡片更新机制概述

HarmonyOS 应用卡片支持多种数据更新方式,开发者可根据业务需求选择合适的更新策略。

1.1 更新方式总览

更新方式 触发条件 适用场景
定时更新 系统按配置时间自动触发 onUpdateForm 新闻、天气等周期数据
主动更新 应用通过 formProvider.updateForm 推送 实时数据变化
call 事件刷新 卡片点击触发 callee 回调更新 手动刷新按钮
message 事件 卡片发送消息到 FormExtensionAbility 简单交互通知
公共事件订阅 系统事件变化时更新 电量变化、网络变化

1.2 数据流架构图

┌────────────────────────────────────────────────────────┐
│                      数据源层                           │
│  batteryInfo │ deviceInfo │ storageStatistics │ 网络API  │
└──────┬───────┴──────┬─────┴────────┬──────────┴────────┘
       │              │              │
       ▼              ▼              ▼
┌────────────────────────────────────────────────────────┐
│                  数据持久化层                            │
│         Preferences (myStore)                          │
│   ┌──────────┬──────────┬──────────┐                  │
│   │batterySOC│storageInfo│marketName│                  │
│   └──────────┴──────────┴──────────┘                  │
└──────────────────────┬─────────────────────────────────┘
                       │
         ┌─────────────┼─────────────┐
         ▼             ▼             ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 主应用页面  │ │FormExt     │ │ UIAbility  │
│ (页面展示)  │ │Ability     │ │ (callee)   │
│            │ │(onAddForm) │ │ (call事件)  │
└────────────┘ └─────┬──────┘ └─────┬──────┘
                     │              │
                     ▼              ▼
              ┌────────────────────────┐
              │  formProvider.updateForm │
              │  (推送数据到卡片)        │
              └───────────┬────────────┘
                          ▼
              ┌────────────────────────┐
              │ 卡片 UI (Widget)       │
              │ @LocalStorageProp      │
              │ 自动刷新显示            │
              └────────────────────────┘

二、定时更新机制

2.1 配置定时更新

form_config.json 中设置:

{
  "name": "MyWidget",
  "updateEnabled": true,
  "scheduledUpdateTime": "10:30",
  "updateDuration": 1
}
配置项 说明
updateEnabled 设为 true 启用定时更新
scheduledUpdateTime 更新时间(HH:mm 格式)
updateDuration 更新频率(1=每30分钟,最小粒度)

2.2 实现 onUpdateForm

import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { preferences } from '@kit.ArkData';

export default class EntryFormAbility extends FormExtensionAbility {
  onUpdateForm(formId: string): void {
    // 从 Preferences 读取最新数据
    let prefs = preferences.getPreferencesSync(this.context, { name: 'myStore' });
    let formData = {
      formId: formId,
      batteryLevel: prefs.getSync('batterySOC', 0) as number,
      storageInfo: prefs.getSync('storageInfo', '') as string
    };

    // 推送更新到卡片
    let formMsg = formBindingData.createFormBindingData(formData);
    formProvider.updateForm(formId, formMsg).then(() => {
      console.info(`定时更新成功: ${formId}`);
    }).catch((err: Error) => {
      console.error(`定时更新失败: ${JSON.stringify(err)}`);
    });
  }
}

三、主动更新:应用内推送卡片数据

当应用在前台运行时,可以主动获取最新数据并推送给所有已添加的卡片。

3.1 管理卡片 formId 列表

为了批量更新所有卡片,需要维护一个 formId 列表:

import { preferences } from '@kit.ArkData';

class FormIdManager {
  private static instance: FormIdManager;
  private storeName: string = 'myStore';

  static getInstance(): FormIdManager {
    if (!FormIdManager.instance) {
      FormIdManager.instance = new FormIdManager();
    }
    return FormIdManager.instance;
  }

  // 获取 Preferences 实例
  getPrefs(context: Context): preferences.Preferences {
    return preferences.getPreferencesSync(context, { name: this.storeName });
  }

  // 添加卡片 ID
  addFormId(prefs: preferences.Preferences, formId: string): void {
    let formIds = prefs.getSync('formIdList', []) as string[];
    if (!formIds.includes(formId)) {
      formIds.push(formId);
      prefs.putSync('formIdList', formIds);
      prefs.flush();
    }
  }

  // 获取所有卡片 ID
  getFormIds(prefs: preferences.Preferences): string[] {
    return prefs.getSync('formIdList', []) as string[];
  }

  // 移除卡片 ID
  removeFormId(prefs: preferences.Preferences, formId: string): void {
    let formIds = prefs.getSync('formIdList', []) as string[];
    let index = formIds.indexOf(formId);
    if (index !== -1) {
      formIds.splice(index, 1);
      prefs.putSync('formIdList', formIds);
      prefs.flush();
    }
  }
}

3.2 批量更新所有卡片

import { formBindingData, formProvider } from '@kit.FormKit';
import { batteryInfo, deviceInfo } from '@kit.BasicServicesKit';
import { storageStatistics } from '@kit.CoreFileKit';

function updateAllForms(context: Context): void {
  let manager = FormIdManager.getInstance();
  let prefs = manager.getPrefs(context);

  // 获取最新数据
  let data = {
    batteryLevel: batteryInfo.batterySOC,
    storageInfo: `${(storageStatistics.getTotalSizeSync() - storageStatistics.getFreeSizeSync()) / (1024 ** 3)} / ${storageStatistics.getTotalSizeSync() / (1024 ** 3)}GB`,
    marketName: deviceInfo.marketName,
    brand: deviceInfo.brand
  };

  // 保存到 Preferences
  prefs.putSync('batterySOC', data.batteryLevel);
  prefs.putSync('storageInfo', data.storageInfo);
  prefs.putSync('marketName', data.marketName);
  prefs.putSync('brand', data.brand);
  prefs.flush();

  // 批量更新所有卡片
  let formIds = manager.getFormIds(prefs);
  for (let formId of formIds) {
    let formData = { formId: formId, ...data };
    let formMsg = formBindingData.createFormBindingData(formData);
    formProvider.updateForm(formId, formMsg).then(() => {
      console.info(`更新成功: ${formId}`);
    }).catch((err: Error) => {
      console.error(`更新失败: ${formId}, ${JSON.stringify(err)}`);
    });
  }
}

四、call 事件触发卡片刷新

卡片内的刷新按钮通过 postCardActioncall 事件通知主进程更新数据。

4.1 完整数据流

用户点击卡片刷新按钮
       ↓
postCardAction({ action: 'call', params: { formId, method } })
       ↓
EntryAbility.callee.on('method') 被触发
       ↓
获取最新数据 → 保存到 Preferences
       ↓
formProvider.updateForm(formId, formData)
       ↓
卡片 UI 自动刷新

4.2 EntryAbility 注册 callee

export default class EntryAbility extends UIAbility {
  // 刷新卡片数据的 callee 回调
  refreshCardData = (data: rpc.MessageSequence) => {
    let params = JSON.parse(data.readString()) as Record<string, string>;
    let formId = params.formId;

    if (formId) {
      // 获取最新数据
      let batteryLevel = batteryInfo.batterySOC;
      let storageUsed = (storageStatistics.getTotalSizeSync() -
        storageStatistics.getFreeSizeSync()) / (1024 * 1024 * 1024);
      let storageTotal = storageStatistics.getTotalSizeSync() / (1024 * 1024 * 1024);

      // 更新 Preferences
      let prefs = FormIdManager.getInstance().getPrefs(this.context);
      prefs.putSync('batterySOC', batteryLevel);
      prefs.putSync('storageUsed', storageUsed);
      prefs.putSync('storageTotal', storageTotal);
      prefs.flush();

      // 推送更新
      let formData = {
        formId: formId,
        batteryLevel: batteryLevel,
        storageUsed: storageUsed,
        storageTotal: storageTotal
      };
      let formMsg = formBindingData.createFormBindingData(formData);
      formProvider.updateForm(formId, formMsg);
    }
    return null;
  };

  onCreate(): void {
    this.callee.on('refreshCardData', this.refreshCardData);
  }

  onDestroy(): void {
    this.callee.off('refreshCardData');
  }
}

五、公共事件订阅实现自动更新

通过订阅系统公共事件(如电量变化),可以在数据变化时自动更新卡片。

5.1 订阅电量变化

import { batteryInfo, BusinessError, commonEventManager } from '@kit.BasicServicesKit';

private subscriber?: commonEventManager.CommonEventSubscriber;

subscribeBatteryChange(): void {
  let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
    events: ['usual.event.BATTERY_CHANGED']
  };

  commonEventManager.createSubscriber(subscribeInfo,
    (err: BusinessError, subscriber: commonEventManager.CommonEventSubscriber) => {
      if (err) return;

      this.subscriber = subscriber;
      commonEventManager.subscribe(this.subscriber,
        (err: BusinessError, data: commonEventManager.CommonEventData) => {
          if (err) return;
          // 电量变化,更新所有卡片
          updateAllForms(this.context);
        });
    });
}

unsubscribeBatteryChange(): void {
  if (this.subscriber) {
    commonEventManager.unsubscribe(this.subscriber);
    this.subscriber = undefined;
  }
}

六、Preferences 数据传递最佳实践

Preferences 是卡片数据传递的核心媒介,以下是使用建议:

6.1 数据模型设计

// 定义卡片数据模型
class WidgetData {
  formId: string = '';
  batteryLevel: number = 0;
  storageUsedGB: number = 0;
  storageTotalGB: number = 0;
  storageInfo: string = '';
  deviceName: string = '';
  brand: string = '';
}

6.2 读写封装

class WidgetDataStore {
  static save(prefs: preferences.Preferences, data: Partial<WidgetData>): void {
    if (data.batteryLevel !== undefined) prefs.putSync('batteryLevel', data.batteryLevel);
    if (data.storageInfo !== undefined) prefs.putSync('storageInfo', data.storageInfo);
    if (data.storageUsedGB !== undefined) prefs.putSync('storageUsedGB', data.storageUsedGB);
    if (data.storageTotalGB !== undefined) prefs.putSync('storageTotalGB', data.storageTotalGB);
    if (data.deviceName !== undefined) prefs.putSync('deviceName', data.deviceName);
    if (data.brand !== undefined) prefs.putSync('brand', data.brand);
    prefs.flush();
  }

  static load(prefs: preferences.Preferences): WidgetData {
    return {
      formId: '',
      batteryLevel: prefs.getSync('batteryLevel', 0) as number,
      storageInfo: prefs.getSync('storageInfo', '') as string,
      storageUsedGB: prefs.getSync('storageUsedGB', 0) as number,
      storageTotalGB: prefs.getSync('storageTotalGB', 0) as number,
      deviceName: prefs.getSync('deviceName', '') as string,
      brand: prefs.getSync('brand', '') as string
    };
  }
}

七、更新策略选择建议

场景 推荐策略 原因
天气卡片 定时更新(每小时) 数据变化频率适中
设备信息卡片 call 事件 + 公共事件 用户手动刷新 + 电量自动变化
新闻卡片 定时更新 + 主动更新 定时拉取 + 推送通知
运动步数卡片 公共事件(计步器变化) 实时数据变化

八、常见问题

8.1 卡片不刷新?

  • 检查 isDynamic 是否为 true
  • 检查 formProvider.updateForm 是否调用成功
  • 检查 formId 是否正确

8.2 多个卡片只更新了一个?

  • 确保遍历所有 formId 并逐一调用 updateForm
  • 每次 updateForm 只更新指定 formId 的卡片

8.3 Preferences 读取不到数据?

  • 确保 flush() 已被调用
  • 确保读取和写入使用相同的 store 名称
  • 卡片进程与应用进程共享 Preferences,但需要 removePreferencesFromCacheSync 刷新缓存

九、总结

知识点 内容
定时更新 form_config.json 配置 + onUpdateForm 回调
主动更新 formProvider.updateForm(formId, formBindingData)
call 刷新 postCardActioncallee.onupdateForm
数据媒介 Preferences 持久化存储
自动更新 commonEventManager.subscribe 订阅系统事件
批量更新 维护 formId 列表,遍历调用 updateForm

理解卡片更新与数据交互机制,是开发高质量动态应用卡片的关键。合理的更新策略能平衡数据实时性与系统资源消耗。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐