鸿蒙|能力特性-卡片开发服务

0 阅读4分钟

概览:本文从静态、动态、互动三类卡片的特点与适用场景切入,系统介绍FormExtensionAbility生命周期、UI页面构建、form_config.json与module.json5配置要点,并深入演示卡片与应用间的三大交互方式:router事件(跳转页面)、message事件(传递消息并刷新卡片)和call事件(拉起后台任务)。此外,还涵盖API18+支持的应用内调用openFormManager直接添加卡片到桌面的能力。

服务卡片介绍

Form Kit(卡片开发服务)提供了一种在桌面、锁屏等系统应用上嵌入显示应用信息的开发框架和API,可以将应用内用户关注的重要信息或常用操作抽取到服务卡片(简称“卡片”)上,通过将卡片添加到桌面、锁屏等系统应用上,以达到信息展示、服务直达的便捷体验效果。

卡片常见使用步骤: 1、长按“桌面的应用图标”,弹出操作菜单。 2、点击“卡片”选项,进入卡片管理页面,可以预览卡片。 3、点击“添加到桌面”按钮,即可在桌面上看到新添加的卡片。

服务卡片类型

卡片类型支持的能力适用场景优缺点
静态卡片仅支持UI组件和布局能力。主要用于展示静态信息(UI相对固定),仅可以通过FormLink组件跳转到指定的UIAbility。功能简单但可以有效控制内存开销。
动态卡片除了支持UI组件和布局能力,还支持通用事件能力和自定义动效能力。用于有复杂业务逻辑和交互的场景。例如:卡片页面图片的刷新、卡片内容的刷新等。功能丰富但内存开销较大。
互动卡片在动态卡片基础上,额外支持溢出动效能力。用于有复杂业务逻辑和交互,需要执行溢出动效呈现更好视觉体验的场景。例如:桌面卡片游戏等。功能丰富但内存开销较大。

创建动态服务卡片

截屏2026-03-27 14.13.54.png

服务卡片相关文件及作用

截屏2026-03-27 14.45.28.png

1、FormExtensionAbility对象生命周期

import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';

export default class EntryFormAbility extends FormExtensionAbility {
  // 卡片提供方接收创建卡片的通知接口。当用户把卡片添加到桌面的那一刻被调用,完成一些卡片的初始化工作,比如卡片绑定一些初始化数据。
  onAddForm(want: Want) {
    const formData = '';
    return formBindingData.createFormBindingData(formData);
  }

  // 卡片提供方收到卡片使用方将临时卡片转常态卡片的通知接口。临时卡片、常态卡片是卡片使用方的概念。
  // 临时卡片是短期存在的,在特定事件或用户行为后显示,完成后自动消失。
  // 常态卡片是持久存在的,在用户未进行清除或更改的情况下,会一直存在,平时开发的功能卡片属于常态卡片。当前卡片使用方不会使用临时卡片。
  onCastToNormalForm(formId: string) {

  }

  // 卡片提供方接收携带参数的更新卡片的通知接口。获取最新数据后调用formProvider的updateForm接口刷新卡片数据。
  onUpdateForm(formId: string) {

  }

  // 卡片提供方接收处理卡片事件的通知接口。用户点击某个按钮,触发我们定义的一个事件的时候,这个方法就会接收,卡片所有的交互逻辑主要在这里面进行处理。
  onFormEvent(formId: string, message: string) {

  }

  // 卡片提供方接收销毁卡片的通知接口。
  onRemoveForm(formId: string) {

  }

  // 卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态(该方法可以选择性重写)
  onAcquireFormState(want: Want) {
    return formInfo.FormState.READY;
  }
}

2、卡片UI页面文件

@Entry
@Component
struct WidgetCard {
  build() {
    Column() {
      
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#EBD9CC')
  }
}

3、form_config.json卡片信息配置

{
  "forms": [
    {
      "name": "widget",
      "displayName": "$string:widget_display_name",
      "description": "$string:widget_desc",
      "src": "./ets/widget/pages/WidgetCard.ets", // 指定卡片需要呈现的UI页面路径
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
      "isDynamic": true,
      "isDefault": true,
      "updateEnabled": false,
      "scheduledUpdateTime": "10:30", // 卡片数据刷新时间设定
      "updateDuration": 1,
      "defaultDimension": "2*4", // 不同尺寸卡片设定
      "supportDimensions": [
        "2*4"
      ]
    }
  ]
}

4、module.json5配置

{
  "module": {
    "extensionAbilities": [
      {
        "name": "EntryFormAbility",
        "srcEntry": "./ets/entryformability/EntryFormAbility.ets",
        "label": "$string:EntryFormAbility_label",
        "description": "$string:EntryFormAbility_desc",
        "type": "form",
        "metadata": [
          {
            "name": "ohos.extension.form",
            "resource": "$profile:form_config"
          }
        ]
      }
    ]
  }
}

服务卡片与页面之间的交互

ArkTS卡片提供页面交互能力,包括卡片与卡片提供方(例如:应用)的页面跳转、卡片拉起卡片提供方进程、卡片与卡片提供方的消息传递。

支持router、message和call三种类型的事件,具体使用场景如下:

router事件(进入应用):可以使用router事件跳转到指定UIAbility,以完成点击卡片跳转至应用内页面的功能。对于非系统应用仅支持跳转到自己应用内的UIAbility。
message事件(更新卡片状态):可以使用message拉起FormExtensionAbility,通过onFormEvent接口回调通知,以完成点击卡片控件后传递消息给应用的功能。
call事件(后台任务):可以使用call事件拉起指定UIAbility到后台,再通过UIAbility申请对应后台长时任务完成音乐播放等功能。

1、卡片跳转到应用页面(router事件)

卡片中调用postCardAction方法打开EntryAbility,传递参数,并设定action为router类型。
在EntryAbility的生命周期onCreate或onNewWant中接收参数并按参数条件载入不同页面。

WidgetCard.ets

@Entry
@Component
struct WidgetCard {
  build() {
    Column() {
      Button('router事件:打开PageA')
        .height(30)
        .onClick(() => {
          postCardAction(this, {
            action: 'router',
            abilityName: 'EntryAbility',
            params: { targetPage: 'funA' }
          })
        })
      Button('router事件:打开PageB')
        .height(30)
        .onClick(() => {
          postCardAction(this, {
            action: 'router',
            abilityName: 'EntryAbility',
            params: { targetPage: 'funB' }
          })
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#EBD9CC')
  }
}

EntryAbility.ets

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

export default class EntryAbility extends UIAbility {
  private selectPage: string = '';
  private currentWindowStage: window.WindowStage | null = null;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 1-1. 获取router事件中传递的targetPage参数
    if (want?.parameters?.params) {
      // want?.parameters.params对应postCardAction()中params内容
      let params: Record<string, Object> = JSON.parse(want?.parameters.params as string);
      this.selectPage = params.targetPage as string;
    }
  }

  // 1-2. 如果UIAbility已经在后台运行,在收到router事件后会触发onNewWant生命周期回调
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    if (want?.parameters?.params) {
      // want?.parameters.params对应postCardAction()中params内容
      let params: Record<string, Object> = JSON.parse(want?.parameters.params as string);
      this.selectPage = params.targetPage as string;
    }
    if (this.currentWindowStage !== null) {
      this.onWindowStageCreate(this.currentWindowStage)
    }
  }

  onDestroy(): void {
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    let targetPage: string;
    // 2. 根据传递的targetPage不同,选择拉起不同的页面
    switch (this.selectPage) {
      case 'funA':
        targetPage = 'pages/PageA';
        break;
      case 'funB':
        targetPage = 'pages/PageB';
        break;
      default:
        targetPage = 'pages/Index';
    }
    if (this.currentWindowStage === null) {
      this.currentWindowStage = windowStage;
    }
    windowStage.loadContent(targetPage, (err) => {

    });
  }

  onWindowStageDestroy(): void {
  }

  onForeground(): void {
  }

  onBackground(): void {
  }
}

2、卡片传递消息给应用(message事件)

卡片中调用postCardAction方法,并设定action为message类型。
在EntryFormAbility的生命周期onFormEvent中接收message,并触发卡片刷新。

WidgetCard.ets

@LocalStorageProp('randomNumber') num: number = 0

Text('num: ' + this.num)
Button('message事件:生成随机数')
  .height(30)
  .onClick(() => {
    postCardAction(this, {
      action: 'message',
      abilityName: 'EntryAbility',
      params: { msgText: 'messageEvent' }
    })
  })

EntryFormAbility.ets

// 卡片提供方接收处理卡片事件的通知接口。用户点击某个按钮,触发我们定义的一个事件的时候,这个方法就会接收,卡片所有的交互逻辑主要在这里面进行处理。
onFormEvent(formId: string, message: string) {
  class FormDataClass {
    randomNumber: number = Math.random() // 和卡片布局中randomNumber对应,@LocalStorageProp('randomNumber') num: number = 0
  }

  let formData = new FormDataClass()
  // formInfo:专门更新卡片数据的数据包
  let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData);

  formProvider.updateForm(formId, formInfo).then(() => {
  }).catch((error: BusinessError) => {
  })
}

3、卡片拉起应用UIAbility到后台(call事件))

申请ohos.permission.KEEP_BACKGROUND_RUNNING后台运行权限。
卡片中调用postCardAction方法,并设定action为call类型。
在EntryAbility的生命周期onCreate中监听call事件,并拉起应用后台任务。

module.json5

"requestPermissions": [
  {
    // 保持应用在后台持续运行(用于长/短时任务退后台,允许Service Ability在后台持续运行)
    "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
    "reason": "$string:permission_keep_background_running_desc",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  }
],

resources > base > element > string.json

{
  "name": "permission_keep_background_running_desc",
  "value": "It is used to backtrack long/short tasks, allowing Service Ability to run continuously in the background"
},

WidgetCard.ets

Button('call事件')
  .height(30)
  .onClick(() => {
    postCardAction(this, {
      action: 'call',
      abilityName: 'EntryAbility',
      params: {
        num: 1,
        method: 'funA'
      }
    })
  })

EntryAbility.ets

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  try {
    // 卡片事件监听
    this.callee.on('funA', (data: rpc.MessageSequence) => {
      console.log('funA触发了', JSON.stringify(data.readString()));
      return new MyParcelable(1, '') // 要求返回一个特殊数据类型
    })
  } catch (err) {
  }
}

// ipc通信返回类型的实现,用于数据序列化和反序列化
class MyParcelable implements rpc.Parcelable {
  num: number;
  str: string;

  constructor(num: number, str: string) {
    this.num = num;
    this.str = str;
  }

  marshalling(messageSequence: rpc.MessageSequence): boolean {
    messageSequence.writeInt(this.num);
    messageSequence.writeString(this.str);
    return true;
  }

  unmarshalling(messageSequence: rpc.MessageSequence): boolean {
    this.num = messageSequence.readInt();
    this.str = messageSequence.readString();
    return true;
  }
}

应用内请求卡片加桌

从API version 18开始,Form Kit提供在应用内将ArkTS卡片添加到桌面的能力。
通过openFormManager方法在应用内添加拉起卡片管理页面入口。
want参数中的bundleName需跟当前应用包名保持一致。
Button('拉起卡片管理面板')
  .height(30)
  .onClick(() => {
    const want: Want = {
      bundleName: "com.example.myapplication", // 与bundleName一致
      abilityName: 'EntryFormAbility',
      parameters: {
        'ohos.extra.param.key.form_dimension': 1,
        'ohos.extra.param.key.form_name': 'widget',
        'ohos.extra.param.key.module_name': 'entry'
      },
    };
    try {
      // 点击按钮后调用openFormManager方法,拉起卡片管理页面
      formProvider.openFormManager(want);
    } catch (error) {
    }
  })