HarmonyOS 通过 router 或 call 事件刷新卡片内容指南

摘要:本文详细介绍 HarmonyOS 应用卡片中通过 router 事件跳转应用页面和 call 事件回调刷新卡片内容的完整实现方案,包含 callee 通信机制、参数传递、数据更新全链路代码示例。


效果

一、概述

HarmonyOS 应用卡片运行在桌面进程中,无法直接调用应用内的导航和状态管理。postCardAction 提供了两种关键的交互方式:

  • router 事件:点击卡片后打开应用页面,适合"查看详情"场景
  • call 事件:通过 callee 机制回调主进程方法,适合"刷新数据"场景

本文结合设备信息卡片案例,详细讲解这两种事件的使用方法和最佳实践。


二、callee 通信机制详解

2.1 核心概念

callee 是 HarmonyOS 提供的跨进程通信机制,允许卡片(运行在桌面进程)调用主应用(运行在应用进程)中注册的方法。

┌────────────────────┐         ┌────────────────────┐
│    桌面进程         │         │    应用进程          │
│                    │         │                    │
│  卡片 Widget       │  IPC    │  EntryAbility      │
│  ┌──────────────┐  │───────→ │  ┌──────────────┐  │
│  │ postCardAction│  │        │  │ callee.on()  │  │
│  │ action: call  │  │        │  │ callback     │  │
│  └──────────────┘  │         │  └──────────────┘  │
│                    │         │         │          │
│                    │         │    updateForm()     │
│                    │         │         │          │
│  卡片 UI 刷新      │ ←────── │  推送新数据        │
└────────────────────┘         └────────────────────┘

2.2 注册与注销

操作 API 时机
注册 this.callee.on('name', callback) onCreate 生命周期
注销 this.callee.off('name') onDestroy 生命周期

三、call 事件:刷新卡片数据

3.1 完整实现流程

步骤 1:卡片端发送 call 事件
let localStorage = new LocalStorage();

@Entry(localStorage)
@Component
struct DeviceInfoWidgetCard {
  @LocalStorageProp('formId') formId: string = '';
  @LocalStorageProp('batteryLevel') batteryLevel: number = 0;
  @LocalStorageProp('storageInfo') storageInfo: string = '';
  @LocalStorageProp('deviceName') deviceName: string = '';

  build() {
    Column({ space: 8 }) {
      Row() {
        Text('设备信息')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)

        Blank()

        // 刷新按钮
        Image($r('app.media.refresh_icon'))
          .width(20)
          .height(20)
          .onClick(() => {
            // 发送 call 事件
            postCardAction(this, {
              action: 'call',
              abilityName: 'EntryAbility',
              params: {
                formId: this.formId,
                method: 'refreshDeviceInfo'
              }
            });
          })
      }
      .width('100%')

      Text(`${this.deviceName}`)
        .fontSize(12)
        .fontColor('rgba(0,0,0,0.6)')

      Row({ space: 8 }) {
        Text(`电量:${this.batteryLevel}%`)
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
      }

      Text(`存储:${this.storageInfo}`)
        .fontSize(12)
        .fontColor('rgba(0,0,0,0.6)')
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(16)
  }
}
步骤 2:EntryAbility 注册 callee 并处理刷新
import { UIAbility } from '@kit.AbilityKit';
import { rpc } from '@kit.IPCKit';
import { formBindingData, formProvider } from '@kit.FormKit';
import { batteryInfo, deviceInfo } from '@kit.BasicServicesKit';
import { storageStatistics } from '@kit.CoreFileKit';
import { preferences } from '@kit.ArkData';
import { JSON } from '@kit.ArkTS';

const TAG = 'EntryAbility';

export default class EntryAbility extends UIAbility {
  // callee 回调:刷新设备信息卡片
  refreshDeviceInfo = (data: rpc.MessageSequence) => {
    // 1. 解析参数
    let params: Record<string, string> =
      JSON.parse(data.readString()) as Record<string, string>;
    let formId = params.formId;

    if (!formId) {
      return null;
    }

    // 2. 获取最新设备数据
    let batteryLevel = batteryInfo.batterySOC;
    let totalGB = storageStatistics.getTotalSizeSync() / (1024 * 1024 * 1024);
    let freeGB = storageStatistics.getFreeSizeSync() / (1024 * 1024 * 1024);
    let usedGB = totalGB - freeGB;
    let storageInfo = `${usedGB.toFixed(1)} / ${totalGB.toFixed(1)} GB`;
    let marketName = deviceInfo.marketName;
    let brand = deviceInfo.brand;

    // 3. 保存到 Preferences(供后续 onAddForm 读取)
    let prefs = preferences.getPreferencesSync(this.context, { name: 'myStore' });
    prefs.putSync('batteryLevel', batteryLevel);
    prefs.putSync('storageInfo', storageInfo);
    prefs.putSync('totalGB', totalGB);
    prefs.putSync('usedGB', usedGB);
    prefs.putSync('marketName', marketName);
    prefs.putSync('brand', brand);
    prefs.flush();

    // 4. 构造更新数据并推送卡片
    let formData = {
      formId: formId,
      batteryLevel: batteryLevel,
      storageInfo: storageInfo,
      deviceName: marketName
    };

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

    return null;
  };

  onCreate(): void {
    console.info(`${TAG}: onCreate`);
    // 注册 callee
    try {
      this.callee.on('refreshDeviceInfo', this.refreshDeviceInfo);
    } catch (err) {
      console.error(`${TAG}: 注册 callee 失败: ${JSON.stringify(err)}`);
    }
  }

  onDestroy(): void {
    console.info(`${TAG}: onDestroy`);
    // 注销 callee
    try {
      this.callee.off('refreshDeviceInfo');
    } catch (err) {
      console.error(`${TAG}: 注销 callee 失败: ${JSON.stringify(err)}`);
    }
  }
}
步骤 3:批量刷新所有卡片

如果应用有多个卡片实例,需要遍历所有 formId 进行更新:

refreshAllForms = (data: rpc.MessageSequence) => {
  let params = JSON.parse(data.readString()) as Record<string, string>;
  let formId = params.formId;

  if (!formId) return null;

  // 获取最新数据
  let batteryLevel = batteryInfo.batterySOC;
  let totalGB = storageStatistics.getTotalSizeSync() / (1024 ** 3);
  let usedGB = totalGB - storageStatistics.getFreeSizeSync() / (1024 ** 3);

  // 保存到 Preferences
  let prefs = preferences.getPreferencesSync(this.context, { name: 'myStore' });
  prefs.putSync('batteryLevel', batteryLevel);
  prefs.putSync('totalGB', totalGB);
  prefs.putSync('usedGB', usedGB);
  prefs.flush();

  // 获取所有已添加的卡片 ID
  let formIds = prefs.getSync('formIdList', []) as string[];

  // 批量更新
  formIds.forEach((id: string) => {
    let formData = {
      formId: id,
      batteryLevel: batteryLevel,
      storageInfo: `${usedGB.toFixed(1)} / ${totalGB.toFixed(1)} GB`
    };
    let formMsg = formBindingData.createFormBindingData(formData);
    formProvider.updateForm(id, formMsg);
  });

  return null;
};

四、router 事件:跳转打开应用页面

4.1 卡片端发送 router 事件

@Entry(localStorage)
@Component
struct DeviceInfoWidgetCard {
  @LocalStorageProp('formId') formId: string = '';

  build() {
    Column({ space: 12 }) {
      Text('设备信息')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)

      Text('点击查看完整信息')
        .fontSize(12)
        .fontColor('#4FC3F7')
        .underline(true)
        .onClick(() => {
          postCardAction(this, {
            action: 'router',
            abilityName: 'EntryAbility',
            params: {
              formId: this.formId,
              page: 'pages/DeviceInfoPage',
              from: 'widget'
            }
          });
        })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(16)
  }
}

4.2 EntryAbility 处理路由跳转

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 检查是否从卡片 router 跳转
    let wantParams = this.context.want?.parameters;
    let targetPage = 'pages/Index';

    if (wantParams?.page) {
      targetPage = wantParams.page as string;
    }

    windowStage.loadContent(targetPage, (err) => {
      if (err.code) {
        console.error(`加载页面失败: ${JSON.stringify(err)}`);
        return;
      }
      console.info(`已加载页面: ${targetPage}`);
    });
  }
}

4.3 router 事件特点

特点 说明
唤醒应用 会将应用从后台切到前台
无需 callee 不需要注册 callee 回调
参数传递 通过 Want 的 parameters 传递
适用场景 “查看详情”、“打开设置”

五、call 与 router 对比

维度 call 事件 router 事件
目的 后台刷新数据 跳转打开页面
应用行为 后台处理,不显示界面 前台显示应用
接收端 callee.on() 回调 onWindowStageCreate 中的 Want
数据返回 通过 formProvider.updateForm
是否需要注册 需要 callee.on 不需要
典型场景 刷新按钮 查看详情按钮

六、综合实战:同时支持刷新和跳转

6.1 卡片组件

let localStorage = new LocalStorage();

@Entry(localStorage)
@Component
struct CombinedWidgetCard {
  @LocalStorageProp('formId') formId: string = '';
  @LocalStorageProp('batteryLevel') batteryLevel: number = 0;
  @LocalStorageProp('deviceName') deviceName: string = '';

  build() {
    Column({ space: 8 }) {
      Row() {
        Text('设备信息')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)

        Blank()

        // 刷新按钮 - call 事件
        Image($r('app.media.refresh_icon'))
          .width(20)
          .height(20)
          .onClick(() => {
            postCardAction(this, {
              action: 'call',
              abilityName: 'EntryAbility',
              params: {
                formId: this.formId,
                method: 'refreshDeviceInfo'
              }
            });
          })
      }
      .width('100%')

      Text(`${this.deviceName}`)
        .fontSize(12)

      Text(`电量:${this.batteryLevel}%`)
        .fontSize(14)
        .fontWeight(FontWeight.Medium)

      // 查看详情 - router 事件
      Text('查看完整信息 >')
        .fontSize(12)
        .fontColor('#4FC3F7')
        .width('100%')
        .textAlign(TextAlign.End)
        .onClick(() => {
          postCardAction(this, {
            action: 'router',
            abilityName: 'EntryAbility',
            params: {
              formId: this.formId,
              page: 'pages/DeviceInfoPage'
            }
          });
        })
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(16)
  }
}

七、常见问题

7.1 callee 注册失败?

  • 确保 callee.ononCreate 中调用
  • 检查回调名称是否与 postCardActionparams.method 对应
  • 确保 abilityNamemodule.json5 一致

7.2 router 跳转后页面空白?

  • 检查 pages 目标路径是否正确
  • 确保目标页面已注册到 main_pages.json
  • 检查 Want 参数是否正确传递

7.3 卡片刷新后数据未变化?

  • 确认 formProvider.updateForm 的 Promise 是否成功
  • 确认传递的数据字段名与卡片 @LocalStorageProp 名称一致
  • 确认 formId 正确

八、总结

知识点 内容
call 事件 卡片 → callee → 数据刷新 → updateForm
router 事件 卡片 → 打开应用页面
callee 注册 this.callee.on('name', callback) in onCreate
callee 注销 this.callee.off('name') in onDestroy
参数解析 rpc.MessageSequence.readString()JSON.parse()
数据推送 formProvider.updateForm(formId, formBindingData)

掌握 router 和 call 两种卡片交互方式,可以实现刷新数据与跳转页面的完整卡片体验,是 HarmonyOS 应用卡片开发的核心技能。

Logo

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

更多推荐