元服务卡片定时刷新不执行?FormExtensionAbility 定时更新完整排查与实战

前言

元服务(原原子化服务)的桌面卡片是用户获取信息的第一入口,很多开发者反馈「FormExtensionAbility 配置了定时刷新,但卡片内容半天不更新」。这个现象背后并不是某一个 API 失效,而是 HarmonyOS 的卡片刷新机制由刷新方式、卡片可见性、应用冻结状态、最小刷新周期四重约束共同决定。本文结合实际踩坑,给出一套可落地的排查清单与可运行代码。

问题描述

典型现象:

  • FormExtensionAbility.onUpdateForm 里写了刷新逻辑,但卡片加到桌面后只在首次添加时执行一次,之后不再触发;
  • 调用 formProvider.setFormNextRefreshTime 后,约定的时间点到了内容却没变;
  • 真机静置几小时后,卡片时间/数据明显过期;
  • 后台被系统回收后,卡片彻底停在最后一次快照。

开发者容易误以为「设置了 updateDuration 就会准点刷新」,但系统对卡片刷新有严格的节流与可见性门控。

细节解析

HarmonyOS 卡片刷新有三类入口,行为各不相同:

  1. 定时刷新(updateDurationmodule.json5formConfigupdateDuration 以 30 分钟为最小粒度("1" = 30min,"2" = 1h……)。注意它不是「每 N 分钟」的精确定时器,系统会在接近该周期时合并触发,且受后台管控影响。
  2. 下次刷新时刻(setFormNextRefreshTime:主动指定某时刻刷新,最小间隔同样受系统约束(默认不低于 5 分钟,过低会被忽略)。必须在 onAddForm/onUpdateForm 之后调用才生效。
  3. 主动推送(formProvider.updateForm:由你的业务在任意时刻推数据,最可靠,但要求应用进程存活(或被 formProvider 拉起)。

最容易踩的三个坑:

  • 卡片不可见时不刷新:系统仅在卡片处于桌面可见区域时触发定时刷新;被移到其他屏、藏在文件夹深层、或被设为「不通知」的卡片,定时刷新会被暂停。
  • 应用被冻结/回收:非长驻后台的元服务在静置后进程冻结,定时刷新依赖系统拉起 FormExtensionAbility,若 extensionAbility 配置错误或 onUpdateForm 抛异常,刷新静默失败。
  • updateDurationsetFormNextRefreshTime 互斥:设置了 updateDuration 后,再调用的 setFormNextRefreshTime 会被系统忽略,二者只能取其一作为「周期性」来源。

示例代码

entry/src/main/ets/entryformability/EntryFormAbility.ts

import { formBindingData, formProvider, FormExtensionAbility } from '@kit.FormKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN = 0xfeed;

export default class EntryFormAbility extends FormExtensionAbility {
  // 首次添加卡片:立即给一帧数据,并规划下一次刷新
  onAddForm(want: Want): formBindingData.FormBindingData {
    const data = this.buildBindingData();
    this.scheduleNext(Number(want.parameters?.formId));
    return formBindingData.createFormBindingData(data);
  }

  // 系统到达刷新点会回调这里——不要在这里做网络请求后“直接 return”,
  // 必须调用 updateForm 把新数据推回卡片
  onUpdateForm(formId: string): void {
    const data = this.buildBindingData();
    formProvider.updateForm(formId, formBindingData.createFormBindingData(data))
      .then(() => this.scheduleNext(Number(formId)))
      .catch((err: BusinessError) => {
        hilog.error(DOMAIN, 'Form', `updateForm failed: ${err.code} ${err.message}`);
      });
  }

  // 主动规划下一次刷新(最小间隔 5 分钟,低于此值系统忽略)
  private scheduleNext(formId: number): void {
    const next = Date.now() + 30 * 60 * 1000; // 30 分钟后
    formProvider.setFormNextRefreshTime(formId, next)
      .then(() => hilog.info(DOMAIN, 'Form', `next refresh @ ${next}`))
      .catch((err: BusinessError) => {
        hilog.error(DOMAIN, 'Form', `setNextRefreshTime err: ${err.code}`);
      });
  }

  private buildBindingData(): Record<string, Object> {
    return {
      title: '实时天气',
      temperature: `${20 + Math.floor(Math.random() * 8)}`,
      updatedAt: new Date().toLocaleTimeString(),
    };
  }
}

module.json5 中卡片配置(二选一,不要同时写 updateDuration 又调 setFormNextRefreshTime):

"forms": [
  {
    "name": "widget",
    "src": "./ets/widget/pages/WidgetCard.ets",
    "uiSyntax": "arkts",
    "window": { "designWidth": 720, "autoDesignWidth": true },
    "colorMode": "auto",
    "isDynamic": true,
    "updateDuration": 1,
    "defaultDimension": "2*2",
    "supportDimensions": ["2*2", "2*4"]
  }
]

WidgetCard.ets 卡片页面用 LocalStorage 接收数据:

let storage = new LocalStorage();
@Entry(storage)
@Component
struct WidgetCard {
  @LocalStorageProp('temperature') temperature: string = '--';
  @LocalStorageProp('updatedAt') updatedAt: string = '';

  build() {
    Column() {
      Text(this.temperature).fontSize(24).fontWeight(FontWeight.Bold)
      Text(`更新于 ${this.updatedAt}`).fontSize(12).fontColor('#999')
    }.padding(12)
  }
}

关键修正:如果你需要「准点且可控」的刷新,请删掉 updateDuration,改用 setFormNextRefreshTime + onUpdateForm 自调度,并在 onUpdateForm 里始终 updateForm 回推;若数据来自网络,把网络请求放到 onUpdateForm 内并用 updateForm 回填,避免依赖已被冻结的常驻进程。

总结

  • 定时刷新 ≠ 精确定时器:updateDuration 最小 30 分钟且会被系统合并/节流,且仅卡片可见时触发
  • updateDurationsetFormNextRefreshTime 互斥,选其一;
  • 可靠刷新靠「onUpdateForm 内调用 formProvider.updateForm 自调度 + 网络数据回填」;
  • 排查顺序:卡片是否在桌面可见 → 进程是否被冻结 → onUpdateForm 是否抛异常 → 是否同时配了两种刷新方式冲突。
Logo

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

更多推荐