前端成功转鸿蒙开发者真实案例,教大家如何开发鸿蒙APP--ArkTS 卡片刷新机制

大家好,我是陈杨,一名有着8 年前端开发经验、6 年技术写作积淀的鸿蒙开发者,也是鸿蒙生态里的一名极客。

曾因前端行业的危机感居安思危,果断放弃饱和的 iOS、安卓赛道,在鸿蒙 API9 发布时,凭着前端技术底子,三天吃透 ArkTS 框架 ,快速上手鸿蒙开发。三年深耕,我不仅打造了鸿蒙开源图表组件库「莓创图表」,闯进过创新赛、极客挑战赛总决赛,更带着团队实实在在做出了成果 ------ 目前已成功上架11 款鸿蒙应用 ,涵盖工具、效率、创意等多个品类,包括JLPT、REFLEX PRO、国潮纸刻、Wss 直连、ZenithDocs Pro、圣诞相册、CSS 特效等,靠这些自研产品赚到了转型后的第一桶金。

从前端转型到鸿蒙掘金,靠的不是运气,而是选对赛道的眼光和快速落地的执行力。今天这篇文章,就接着上一篇的内容,和大家聊聊 [ArkTS 卡片刷新机制],这篇很重要,认真听。

在 ArkTS 卡片开发中,"内容刷新" 是核心功能之一 ------ 无论是实时数据更新(如天气、新闻)、图片替换,还是基于状态的内容切换,都依赖高效的刷新机制。本文基于 HarmonyOS 官方文档,系统梳理卡片刷新的两种核心模式(主动刷新、被动刷新)、特殊场景实现(图片刷新、状态关联刷新)及关键约束,结合完整代码示例,帮助开发者精准落地各类刷新需求。

一、刷新机制核心概述

ArkTS 卡片的刷新能力由系统框架提供,核心依赖两套接口和配置体系:

  • 主动刷新:由卡片提供方(应用)或使用方(如桌面)主动触发,适用于 "按需更新" 场景(如用户点击刷新按钮、应用数据变化);
  • 被动刷新:由系统根据预设规则自动触发,适用于 "周期性更新" 场景(如每天固定时间刷新、每隔 30 分钟更新);
  • 数据传递 :刷新时的数据通过formBindingData封装,卡片页面通过@LocalStorageProp接收(数据自动转为 string 类型),确保数据同步的一致性。

关键接口说明:

接口名 调用方 核心作用 约束
updateForm 卡片提供方 主动刷新自身卡片内容 仅能刷新当前应用的卡片,无法操作其他应用卡片
requestForm 卡片使用方(系统应用) 主动请求刷新已添加的卡片 仅能刷新当前宿主中的卡片
setFormNextRefreshTime 卡片提供方 设置下次刷新时间 最短刷新间隔 5 分钟

二、主动刷新:按需触发的精准更新

主动刷新是开发者最常使用的模式,核心是通过updateForm接口手动触发,可搭配用户交互、应用数据变化等场景使用。

2.1 核心实现:提供方主动刷新

卡片提供方(应用)通过formProvider.updateForm接口触发刷新,需传入目标卡片的formId(唯一标识)和更新后的数据。通常与onFormEvent(用户交互触发)、onUpdateForm(生命周期回调)搭配使用。

完整代码示例:用户点击按钮触发刷新

plain 复制代码
// 1. 卡片页面(WidgetCard.ets):提供刷新按钮,通过postCardAction传递事件
let storage = new LocalStorage();
@Entry(storage)
@Component
struct RefreshButtonCard {
  // 接收FormExtensionAbility传递的formId和数据
  @LocalStorageProp('formId') formId: string = '';
  @LocalStorageProp('currentData') currentData: string = '初始数据';

  build() {
    Column({ space: 20 })
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .padding(15) {

      Text(`当前内容:${this.currentData}`)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

      // 点击按钮触发刷新事件
      Button("手动刷新")
        .width("80%")
        .height(50)
        .backgroundColor("#5A5FFF")
        .fontColor("#FFFFFF")
        .onClick(() => {
          // 发送message事件给FormExtensionAbility,携带formId
          postCardAction(this, {
            action: "message",
            params: {
              formId: this.formId,
              refreshType: "manual"
            }
          });
        })
    }
  }
}

// 2. FormExtensionAbility(EntryFormAbility.ets):接收事件并执行刷新
import { 
  formBindingData, 
  FormExtensionAbility, 
  formProvider, 
  formInfo 
} from '@kit.FormKit';
import { Want, BusinessError } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG: string = 'EntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;

export default class EntryFormAbility extends FormExtensionAbility {
  // 卡片创建时,将formId存入LocalStorage(供页面使用)
  onAddForm(want: Want): formBindingData.FormBindingData {
    hilog.info(DOMAIN_NUMBER, TAG, '[onAddForm] 卡片创建');
    // 获取卡片唯一ID
    const formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string;
    // 初始化数据(包含formId)
    const initData = {
      formId: formId,
      currentData: '初始数据'
    };
    return formBindingData.createFormBindingData(initData);
  }

  // 接收卡片页面的message事件,执行刷新
  async onFormEvent(formId: string, message: string): Promise<void> {
    const params = JSON.parse(message);
    hilog.info(DOMAIN_NUMBER, TAG, `[onFormEvent] 触发手动刷新,formId: ${formId}`);

    // 模拟获取新数据(实际场景可替换为接口请求、数据库查询等)
    const newData = {
      currentData: `刷新于 ${new Date().toLocaleTimeString()}`
    };

    // 封装刷新数据并调用updateForm
    const formInfo = formBindingData.createFormBindingData(newData);
    try {
      await formProvider.updateForm(formId, formInfo);
      hilog.info(DOMAIN_NUMBER, TAG, `[onFormEvent] 刷新成功`);
    } catch (error) {
      const err = error as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `[onFormEvent] 刷新失败:code=${err.code}, msg=${err.message}`);
    }
  }
}

2.2 关键注意点

  • formId是核心标识:每个卡片实例的formId唯一,需通过want.parameters[formInfo.FormParam.IDENTITY_KEY]获取(卡片创建时);
  • 异常处理必须:updateForm可能因网络异常、权限不足失败,需通过try/catch捕获BusinessError
  • 数据格式限制:传递的数据需为 JSON 可序列化类型(字符串、数字、对象),复杂类型需手动转换。

三、被动刷新:系统驱动的自动更新

被动刷新无需手动触发,由系统根据配置自动执行,核心分为 "定时刷新""定点刷新""下次刷新" 三类,适用于周期性数据更新场景。

3.1 定时刷新:固定间隔自动更新

定时刷新通过form_config.jsonupdateDuration字段配置,单位为 "30 分钟",需配合updateEnabled: true启用。

配置示例(form_config.json)

plain 复制代码
{
  "forms": [
    {
      "name": "WeatherWidget",
      "src": "./ets/widget/pages/WeatherCard.ets",
      "uiSyntax": "arkts",
      "updateEnabled": true, // 启用周期性刷新
      "updateDuration": 2, // 刷新周期:2 * 30分钟 = 1小时
      "defaultDimension": "2*2",
      "supportDimensions": ["2*2"],
      "colorMode": "auto",
      "isDefault": true
    }
  ]
}

代码实现(EntryFormAbility.ets)

系统触发定时刷新时,会回调onUpdateForm方法,需在此方法中实现数据更新逻辑:

plain 复制代码
export default class EntryFormAbility extends FormExtensionAbility {
  // 定时刷新触发时执行
  async onUpdateForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onUpdateForm] 定时刷新触发,formId: ${formId}`);

    // 模拟请求天气数据(实际场景替换为真实接口)
    const newWeatherData = {
      city: "北京",
      temperature: "25℃",
      updateTime: new Date().toLocaleTimeString()
    };

    const formInfo = formBindingData.createFormBindingData(newWeatherData);
    try {
      await formProvider.updateForm(formId, formInfo);
    } catch (error) {
      const err = error as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `定时刷新失败:${err.message}`);
    }
  }
}

定时刷新核心约束

  • 刷新周期规则:updateDuration为自然数,0 表示不生效;API 11 及以上版本,若应用市场配置了刷新周期,取 "配置值" 和 "应用配置值" 的较大者(如应用配置 1 小时,市场配置 2 小时,则按 2 小时刷新);
  • 配额限制:每张卡片每天最多定时刷新 50 次(包含updateDurationsetFormNextRefreshTime两种方式),0 点重置;
  • 可见性影响:卡片不可见时,系统仅记录刷新动作,待卡片可见后统一刷新布局。

3.2 定点刷新:指定时间自动更新

定点刷新支持 "单时间点" 和 "多时间点" 配置,通过form_config.jsonscheduledUpdateTime(单时间点)或multiScheduledUpdateTime(多时间点)字段设置,需关闭定时刷新(updateDuration: 0`)。

配置示例(多时间点刷新)

plain 复制代码
{
  "forms": [
    {
      "name": "NewsWidget",
      "src": "./ets/widget/pages/NewsCard.ets",
      "uiSyntax": "arkts",
      "updateEnabled": true,
      "updateDuration": 0, // 关闭定时刷新,优先定点刷新
      "scheduledUpdateTime": "10:30", // 单时间点(兼容旧版本)
      "multiScheduledUpdateTime": "08:00,12:00,18:00", // 多时间点(最多24个)
      "defaultDimension": "2*4",
      "supportDimensions": ["2*4"],
      "isDefault": true
    }
  ]
}

关键说明

  • 优先级:多时间点配置(multiScheduledUpdateTime)优先级高于单时间点(scheduledUpdateTime),两者同时配置时仅多时间点生效;
  • 时间格式:采用 24 小时制,精确到分钟(如08:0016:30),多时间点用英文逗号分隔;
  • 触发逻辑:系统在指定时间点回调onUpdateForm方法,数据更新逻辑与定时刷新一致。

3.3 下次刷新:自定义延迟更新

通过formProvider.setFormNextRefreshTime接口设置下次刷新时间,适用于 "延迟更新" 场景(如用户操作后 5 分钟刷新),最短间隔为 5 分钟。

代码示例

plain 复制代码
export default class EntryFormAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string): void {
    const params = JSON.parse(message);
    if (params.action === "setNextRefresh") {
      // 设置5分钟后刷新(参数单位:分钟)
      const delayMinutes = 5;
      formProvider.setFormNextRefreshTime(formId, delayMinutes, (err: BusinessError) => {
        if (err) {
          hilog.error(DOMAIN_NUMBER, TAG, `设置下次刷新失败:${err.message}`);
          return;
        }
        hilog.info(DOMAIN_NUMBER, TAG, `已设置5分钟后刷新`);
      });
    }
  }

  // 下次刷新时间到后,触发onUpdateForm
  async onUpdateForm(formId: string): void {
    hilog.info(DOMAIN_NUMBER, TAG, `[onUpdateForm] 下次刷新触发`);
    // 执行数据更新逻辑...
  }
}

四、特殊场景刷新:图片与状态关联

除基础文本数据刷新外,卡片常见的复杂场景包括 "图片刷新"(本地 / 网络图片)和 "状态关联刷新"(根据卡片配置刷新不同内容),需针对性处理。

4.1 图片刷新:本地与网络图片更新

卡片展示图片需通过formImages字段传递文件描述符(fd),页面通过memory://fileName协议加载,支持本地图片和网络图片两种场景。

4.1.1 本地图片刷新(卡片创建时加载)

plain 复制代码
// EntryFormAbility.ets:onAddForm中加载本地图片
import { fileIo } from '@kit.CoreFileKit';
import { Want, BusinessError } from '@kit.AbilityKit';

export default class EntryFormAbility extends FormExtensionAbility {
  onAddForm(want: Want): formBindingData.FormBindingData {
    // 获取应用临时目录(存放本地图片)
    const tempDir = this.context.getApplicationContext().tempDir;
    const imgMap: Record<string, number> = {};

    try {
      // 打开本地图片(假设tempDir下有head.png)
      const file = fileIo.openSync(`${tempDir}/head.png`);
      imgMap['avatar'] = file.fd; // fd为文件描述符,作为图片标识
    } catch (e) {
      const err = e as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `打开本地图片失败:${err.message}`);
    }

    // 封装图片数据(formImages为固定字段,不可改名)
    class FormData {
      text: string = "我的头像";
      imgName: string = "avatar"; // 与formImages的key一致
      formImages: Record<string, number> = imgMap; // 存储fd
    }

    return formBindingData.createFormBindingData(new FormData());
  }
}

// 卡片页面(WidgetCard.ets):加载本地图片
let storage = new LocalStorage();
@Entry(storage)
@Component
struct LocalImageCard {
  @LocalStorageProp('text') text: string = "加载中...";
  @LocalStorageProp('imgName') imgName: string = "";

  build() {
    Column()
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center) {

      // 通过memory://协议加载图片(imgName对应formImages的key)
      Image(`memory://${this.imgName}`)
        .width(100)
        .height(100)
        .borderRadius(50)
        .objectFit(ImageFit.Cover)

      Text(this.text)
        .margin({ top: 10 })
        .fontSize(14)
    }
  }
}

4.1.2 网络图片刷新(用户点击触发)

网络图片需先下载到本地临时目录,再通过 fd 传递,需申请ohos.permission.INTERNET权限。

plain 复制代码
// 1. 配置权限(module.json5)
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "用于下载网络图片",
        "usedScene": { "abilities": ["EntryFormAbility"], "when": "always" }
      }
    ]
  }
}

// 2. EntryFormAbility.ets:onFormEvent中下载并刷新图片
import { http } from '@kit.NetworkKit';

export default class EntryFormAbility extends FormExtensionAbility {
  async onFormEvent(formId: string, message: string): Promise<void> {
    // 先更新状态为"刷新中"
    let loadingData = { text: "刷新中..." };
    await formProvider.updateForm(formId, formBindingData.createFormBindingData(loadingData));

    // 网络图片地址(替换为真实链接)
    const imgUrl = "https://example.com/new-avatar.jpg";
    const tempDir = this.context.getApplicationContext().tempDir;
    const fileName = `img_${Date.now()}`; // 文件名唯一(确保图片刷新)
    const tempFile = `${tempDir}/${fileName}`;
    const imgMap: Record<string, number> = {};

    try {
      // 1. 下载网络图片
      const httpRequest = http.createHttp();
      const response = await httpRequest.request(imgUrl);
      if (response.responseCode !== http.ResponseCode.OK) {
        throw new Error(`下载失败:${response.responseCode}`);
      }

      // 2. 写入临时文件
      const imgFile = fileIo.openSync(tempFile, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      await fileIo.write(imgFile.fd, response.result as ArrayBuffer);
      imgMap[fileName] = imgFile.fd;

      // 3. 刷新卡片图片
      const formData = {
        text: "刷新成功",
        imgName: fileName,
        formImages: imgMap
      };
      await formProvider.updateForm(formId, formBindingData.createFormBindingData(formData));

      // 4. 关闭文件(需在刷新后执行)
      fileIo.closeSync(imgFile);
      httpRequest.destroy();
    } catch (error) {
      const err = error as BusinessError;
      hilog.error(DOMAIN_NUMBER, TAG, `网络图片刷新失败:${err.message}`);
      // 刷新失败状态
      const failData = { text: "刷新失败" };
      await formProvider.updateForm(formId, formBindingData.createFormBindingData(failData));
    }
  }
}

图片刷新关键约束

  • 图片大小限制:展示的图片需控制在 2MB 以内,避免占用过多内存;
  • 文件名唯一性:每次刷新需使用不同的imgName(如加时间戳),否则卡片不会感知图片变化;
  • 下载超时限制:FormExtensionAbility后台仅存活 5 秒,需确保网络图片快速下载完成。

4.2 状态关联刷新:根据卡片配置更新内容

同一卡片可能存在多种状态(如两张天气卡片分别显示北京、上海天气),需通过持久化存储记录卡片状态,刷新时根据状态返回对应内容。

完整实现示例

plain 复制代码
// 1. form_config.json:配置定时刷新(每30分钟)
{
  "forms": [
    {
      "name": "CityWeatherWidget",
      "src": "./ets/widget/pages/WeatherCard.ets",
      "uiSyntax": "arkts",
      "updateEnabled": true,
      "updateDuration": 1, // 30分钟刷新一次
      "defaultDimension": "2*2",
      "supportDimensions": ["2*2"],
      "isDefault": true
    }
  ]
}

// 2. 卡片页面(WeatherCard.ets):选择城市状态
let storage = new LocalStorage();
@Entry(storage)
@Component
struct CityWeatherCard {
  @LocalStorageProp('beijingWeather') beijingWeather: string = "待刷新";
  @LocalStorageProp('shanghaiWeather') shanghaiWeather: string = "待刷新";
  @State selectBeijing: boolean = false;
  @State selectShanghai: boolean = false;

  build() {
    Column({ space: 15 })
      .width('100%')
      .height('100%')
      .padding(15) {

      // 城市选择复选框
      Row({ space: 8 }) {
        Checkbox({ name: 'bj', group: 'cityGroup' })
          .onChange((value) => {
            this.selectBeijing = value;
            // 通知FormExtensionAbility更新状态
            this.notifyStateChange();
          });
        Text("北京")
          .fontSize(14);
      }

      Row({ space: 8 }) {
        Checkbox({ name: 'sh', group: 'cityGroup' })
          .onChange((value) => {
            this.selectShanghai = value;
            this.notifyStateChange();
          });
        Text("上海")
          .fontSize(14);
      }

      // 天气展示
      Text(`北京天气:${this.beijingWeather}`)
        .fontSize(14)
        .margin({ top: 10 });
      Text(`上海天气:${this.shanghaiWeather}`)
        .fontSize(14);
    }
  }

  // 发送状态变更事件
  private notifyStateChange() {
    postCardAction(this, {
      action: "message",
      params: {
        selectBeijing: JSON.stringify(this.selectBeijing),
        selectShanghai: JSON.stringify(this.selectShanghai)
      }
    });
  }
}

// 3. EntryFormAbility.ets:持久化状态并刷新对应内容
import { preferences } from '@kit.ArkData';

export default class EntryFormAbility extends FormExtensionAbility {
  // 卡片创建时初始化状态(存入preferences数据库)
  async onAddForm(want: Want): formBindingData.FormBindingData {
    const formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string;
    const isTempCard = want.parameters?.[formInfo.FormParam.TEMPORARY_KEY] as boolean;

    // 仅常态卡片持久化状态
    if (!isTempCard) {
      const store = await preferences.getPreferences(this.context, 'cityWeatherStore');
      await store.put(`bj_${formId}`, 'false'); // 北京状态:未选中
      await store.put(`sh_${formId}`, 'false'); // 上海状态:未选中
      await store.flush();
    }

    return formBindingData.createFormBindingData({});
  }

  // 接收状态变更事件,更新数据库
  async onFormEvent(formId: string, message: string): Promise<void> {
    const params = JSON.parse(message);
    const store = await preferences.getPreferences(this.context, 'cityWeatherStore');

    if (params.selectBeijing !== undefined) {
      await store.put(`bj_${formId}`, params.selectBeijing);
    }
    if (params.selectShanghai !== undefined) {
      await store.put(`sh_${formId}`, params.selectShanghai);
    }
    await store.flush();
  }

  // 定时刷新时,根据状态返回对应天气数据
  async onUpdateForm(formId: string): void {
    const store = await preferences.getPreferences(this.context, 'cityWeatherStore');
    const selectBeijing = await store.get(`bj_${formId}`, 'false');
    const selectShanghai = await store.get(`sh_${formId}`, 'false');

    const updateData: Record<string, string> = {};
    // 模拟获取天气数据
    if (selectBeijing === 'true') {
      updateData.beijingWeather = `25℃ 晴(${new Date().toLocaleTimeString()})`;
    }
    if (selectShanghai === 'true') {
      updateData.shanghaiWeather = `28℃ 多云(${new Date().toLocaleTimeString()})`;
    }

    await formProvider.updateForm(formId, formBindingData.createFormBindingData(updateData));
  }

  // 卡片删除时,清理数据库数据
  async onRemoveForm(formId: string): void {
    const store = await preferences.getPreferences(this.context, 'cityWeatherStore');
    await store.delete(`bj_${formId}`);
    await store.delete(`sh_${formId}`);
  }
}

五、刷新机制核心约束与最佳实践

5.1 关键约束(避坑重点)

  1. 刷新次数限制:定时刷新每天最多 50 次,超出后当天无法触发;
  2. 进程存活限制FormExtensionAbility后台仅存活 5 秒(触发回调后),耗时操作(如下载大文件)需拉起主应用处理;
  3. 权限限制 :网络图片刷新需申请INTERNET权限,本地文件操作需确保路径合法;
  4. 资源限制:图片大小≤2MB,自定义字体总大小≤20MB,避免内存溢出。

5.2 最佳实践

  1. 选择合适的刷新方式
  • 实时交互场景(如点击刷新)→ 主动刷新;
  • 周期性数据(如天气、新闻)→ 被动刷新(定时 / 定点);
  1. 优化刷新性能
  • 减少刷新频率,非必要不使用高频定时刷新;
  • 刷新数据仅传递变更部分,避免全量更新;
  1. 异常处理兜底
  • 网络请求失败时,保留上次有效数据;
  • 图片加载失败时,显示默认占位图;
  1. 清理冗余数据:卡片删除时,同步清理数据库、临时文件等,避免存储冗余。

总结

ArkTS 卡片的刷新机制围绕 "主动按需" 和 "被动自动" 两大核心,覆盖了文本、图片、状态关联等各类场景。开发时需重点关注formId唯一性、数据传递格式、系统约束(次数、内存、进程存活),并根据业务场景选择合适的刷新方式。通过本文的代码示例和约束说明,可快速落地稳定、高效的卡片刷新功能,提升用户体验。

相关推荐
go_caipu2 小时前
Vben Admin管理系统集成qiankun微服务(二)
前端·javascript
唐叔在学习2 小时前
insertAdjacentHTML踩坑实录:AI没搞定的问题,我给搞定啦
前端·javascript·html
超绝大帅哥2 小时前
Promise为什么比回调函数更好
前端
幸福小宝2 小时前
uniapp 异型无缝轮播图
前端
wordbaby2 小时前
TanStack Router 实战: 如何设置基础认证和受保护路由
前端
智算菩萨2 小时前
Anthropic Claude 4.5:AI分层编排的革命,成本、速度与能力的新平衡
前端·人工智能
程序员Agions2 小时前
程序员武学修炼手册(三):融会贯通——从写好代码到架构设计
前端·程序员·强化学习
哈__2 小时前
从入门小白到精通,玩转 React Native 鸿蒙跨平台开发:TouchableOpacity 触摸反馈组件
react native·react.js·harmonyos
zhouzhouya2 小时前
我和TRAE的这一年:从"看不懂"到"玩得转"的AI学习进化史
前端·程序员·trae