开天辟地 HarmonyOS(鸿蒙) - 卡片: FormLink(为静态卡片提供与应用交互的能力)

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 卡片: FormLink(为静态卡片提供与应用交互的能力)

示例如下:

pages\widget\FormLinkDemo.ets

/*
 * FormLink - 为静态卡片提供与应用交互的功能
 */

import { TitleBar } from '../TitleBar';

@Entry
@Component
struct FormLinkDemo {

  @State message: string = "FormLink 的演示请参见:\n" +
    "/entryformability/EntryFormAbility.ets\n" +
    "/widget/pages/FormLinkCard.ets" +
    "/entryability/EntryAbility.ets"

  build() {
    Column({space:10}) {
      TitleBar()
      Text(this.message)
    }
  }
}

\entry\src\main\ets\entryformability\EntryFormAbility.ets

/*
 * 卡片对应的 extension ability
 * 用于管理卡片的生命周期,以及和卡片做数据交互
 */

import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
import { Helper } from '../utils/Helper';
import { MyLog } from '../utils/MyLog';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';

export default class EntryFormAbility extends FormExtensionAbility {

  // 将指定的资源文件复制到指定的沙箱目录
  copyFile(src: Resource, dst: string) {
    let resourceManager = this.context.getApplicationContext().resourceManager
    let buffer = resourceManager.getMediaContentSync(src.id).buffer
    let outputStream = fs.createStreamSync(dst, 'w+');
    let writeLength = outputStream.writeSync(buffer, {
      offset: 0,
      length: buffer.byteLength
    })
    outputStream.closeSync();
  }

  // 卡片创建时的回调
  onAddForm(want: Want) {

    // 通过 want.parameters 可以获取卡片的规格,以及宽和高等
    MyLog.d(`onAddForm ${JSON.stringify(want.parameters)}`)

    // FormExtensionContext - FormExtensionAbility 的上下文(在 FormExtensionAbility 内,可以通过 this.context 获取 FormExtensionContext 对象)
    let context = this.context
    let abilityName = context.extensionAbilityInfo.name
    MyLog.d(`extension ability name: ${abilityName}`)

    // 将指定的资源文件复制到指定的沙箱目录
    // 如果需要卡片显示一个网络图片,则可以先将图片下载到沙箱目录(注:卡片每次活过来后,最多在后台存在 5 秒),然后再参照本例后续的方法
    let imagePath = this.context.getApplicationContext().filesDir + '/icon.png'
    this.copyFile($r('app.media.app_icon'), imagePath)

    // 创建一个保存多张图片的字典表,用于在卡片中显示图片
    // key 代表图片的标识
    // value 代表图片的文件描述符,打开文件到内存后,把文件描述符传给卡片,然后卡片再根据文件描述符显示内存中的图片
    let formImages: Record<string, number> = {};
    let file = fs.openSync(imagePath);
    formImages['myImage_0'] = file.fd; // 注意:如果图片更新了,则这里需要指定一个和之前不同的 key 以便卡片可以显示更新后的图片

    /*
     * formBindingData.createFormBindingData() - 创建一个 FormBindingData 对象
     * formBindingData.FormBindingData - 需要传递给卡片的数据
     *   此对象中的字段的值,可以在卡片中通过 @LocalStorageProp 引用
     */
    let formData: Record<string, string | Record<string, number>> = {
      'formId': `${want.parameters!['ohos.extra.param.key.form_identity']}`, // 卡片 id
      'dimension': `${want.parameters!['ohos.extra.param.key.form_dimension']}`, // 卡片的规格(1代表1*2, 2代表2*2, 3代表2*4, 4代表4*4, 7代表6*4)
      'width': `${want.parameters!['ohos.extra.param.key.form_width']}`, // 卡片的宽
      'height': `${want.parameters!['ohos.extra.param.key.form_height']}`, // 卡片的高
      'content': Helper.getTimestampString(), // 卡片中可以通过 @LocalStorageProp('content') 引用此值
      'myImage': 'myImage_0', // 指定在 formImages 中的指定 key 的图片,然后在卡片中可以通过 @LocalStorageProp('myImage') 引用此图片
      'formImages': formImages // 保存多张图片的字典表
    };
    return formBindingData.createFormBindingData(formData);
  }

  // 刷新时会执行 onUpdateForm() 回调
  // 当时间符合 scheduledUpdateTime 或 updateDuration 的条件时会触发刷新
  // 当系统语言或深色浅色模式发生变化时也会触发刷新
  // 卡片每次活过来后,最多在后台存在 5 秒
  onUpdateForm(formId: string) {

    MyLog.d(`onUpdateForm: ${formId}`)

    /*
     * formBindingData.createFormBindingData() - 创建一个 FormBindingData 对象
     * formBindingData.FormBindingData - 需要传递给卡片的数据
     *   此对象中的字段的值,可以在卡片中通过 @LocalStorageProp 引用
     * formProvider.updateForm() - 更新指定的卡片
     *   formId - 卡片 id
     *   formBindingData - 为卡片绑定的 FormBindingData 对象
     * formProvider.setFormNextRefreshTime() - 指定下一次刷新的时间(注:卡片每天最多刷新 50 次)
     *   formId - 卡片 id
     *   minute - 此时间后刷新(单位:分钟),最短为 5 分钟
     */
    let formData: Record<string, string> = {
      'content': Helper.getTimestampString() // 卡片中可以通过 @LocalStorageProp('content') 引用此值
    };
    let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
    formProvider.updateForm(formId, formInfo)

    try {
      // 设置过 5 分钟后刷新卡片
      formProvider.setFormNextRefreshTime(formId, 5, (err: BusinessError) => {

      })
    } catch (err) {

    }
  }

  // 卡片通过 message 的方式传递数据时,会触发此回调
  onFormEvent(formId: string, message: string) {
    MyLog.d(`onFormEvent: ${formId}, ${message}`) // 此处的 message 参数就是 message 方式传递过来的数据

    // 更新指定的卡片
    let formData: Record<string, string> = {
      'content': Helper.getTimestampString() // 卡片中可以通过 @LocalStorageProp('content') 引用此值
    };
    let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
    formProvider.updateForm(formId, formInfo)
  }

  // 卡片销毁时的回调
  onRemoveForm(formId: string) {
    MyLog.d(`onRemoveForm: ${formId}`)
  }
};

\entry\src\main\ets\widget\pages\FormLinkCard.ets

/*
 * FormLink - 为静态卡片提供与应用交互的能力
 */

import { Helper } from '../../utils/Helper'

@Entry
@Component
struct FormLinkCard {

  @LocalStorageProp('content') content: string = ''

  /*
   * FormLink - 为静态卡片提供与应用交互的能力
   *   bundleName - bundle 的名称
   *   moduleName - module 的名称
   *   abilityName - ability 的名称
   *   uri - deep linking 或 app linking 地址
   *   params - 需要传递的数据
   *   action - 交互方式
   *     router - 拉起目标应用
   *       在目标应用的 onCreate() 或 onNewWant() 中通过 Want 获取卡片传递给应用的数据
   *       在目标应用中通过 formProvider.updateForm() 将数据传递给卡片
   *     message - 触发 FormExtensionAbility 的 onFormEvent 回调
   *       在 FormExtensionAbility 的 onFormEvent 回调中通过 message 参数获取卡片传递过来的数据
   *       在 FormExtensionAbility 的 onFormEvent 回调中通过 formProvider.updateForm() 将数据传递给卡片
   *     call - 拉起目标应用,只能是冷启动的方式,且不会出现在最近任务列表中
   *       在目标应用的 onCreate() 中通过 Want 获取卡片传递给应用的数据
   *       如果目标应用已经启动了,则可以通过 Caller/Callee 的方式将数据从卡片传递给应用
   *       在 params 中必须要传递名为 method 的参数,其用于作为 Caller/Callee 之间约定的消息通知字符串
   *       在目标应用中通过 this.callee.on() 监听指定的消息通知字符串,如果监听到,则可以获取卡片传递给目标应用的数据
   *       在目标应用中通过 formProvider.updateForm() 将数据传递给卡片
   *       目标应用必须具有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限(参见 module.json5 中的相关说明)
   */

  build() {
    Column() {
      // 通过 router 和 deep linking 的方式拉起指定的应用(目标应用为 /harmonydemo2 项目)
      // 关于 deep linking 和 app linking 请参见 /ipc/DeepLinkingDemo.ets 和 /ipc/AppLinkingDemo.ets 中的说明
      FormLink({
        action: 'router',
        uri: "webabcd://a.b.c/api?p1=xyz",
        params: {
          'k1': 'v1',
          'k2': `${Helper.getTimestampString()}`,
        }
      }) {
        Column().backgroundColor(Color.Red).width('100%').height('100%')
      }
      .width('100%').layoutWeight(1)

      // 通过 router 和 abilityName 的方式拉起指定的应用
      // params 中的数据可以在目标应用的 onCreate() 或 onNewWant() 中通过 Want 获取到,详见 /entryability/EntryAbility.ets 中的说明
      // 目标应用中通过 formProvider.updateForm() 将数据传递给卡片,详见 /entryability/EntryAbility.ets 中的说明
      FormLink({
        action: 'router',
        bundleName: 'com.webabcd.harmonydemo',
        moduleName: 'entry',
        abilityName: 'com.webabcd.harmonydemo.EntryAbility',
        params: {
          'k1': 'v1',
          'k2': `${Helper.getTimestampString()}`,
        }
      }) {
        Column().backgroundColor(Color.Green).width('100%').height('100%')
      }
      .width('100%').layoutWeight(1)

      // 通过 message 的方式触发 FormExtensionAbility 的 onFormEvent 回调
      // params 中的数据可以在 onFormEvent 回调中获取到,详见 /entryformability/EntryFormAbility.ets 中的说明
      // 在 onFormEvent 回调中通过 formProvider.updateForm() 将数据传递给卡片,详见 /entryformability/EntryFormAbility.ets 中的说明
      FormLink({
        action: 'message',
        params: {
          'k1': 'v1',
          'k2': `${Helper.getTimestampString()}`,
        }
      }) {
        Column() {
          Text(this.content).fontColor(Color.White)
        }.backgroundColor(Color.Blue).width('100%').height('100%').justifyContent(FlexAlign.Center)
      }
      .width('100%').layoutWeight(1)

      // 通过 call 和 abilityName 的方式拉起指定的应用(params 中必须要包含名为 method 的参数)
      // 目标应用未启动时,则会拉起目标应用,但是目标应用不会出现在最近任务列表中,此时 params 中的数据可以在目标应用的 onCreate() 中通过 Want 获取到,详见 /entryability/EntryAbility.ets 中的说明
      // 目标应用已启动时,则通过 Caller/Callee 的方式将数据从卡片传递给应用(前提是目标应用注册了监听,详见 /entryability/EntryAbility.ets 中的说明)
      // 目标应用中通过 formProvider.updateForm() 将数据传递给卡片,详见 /entryability/EntryAbility.ets 中的说明
      // 目标应用必须具有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限
      FormLink({
        action: 'call',
        abilityName: 'com.webabcd.harmonydemo.EntryAbility',
        params: {
          'method': 'myMethod', // 如需 call 方式生效,则必须要有 method 参数,其会作为 Caller/Callee 之间约定的消息通知字符串
          'k1': 'v1',
          'k2': `${Helper.getTimestampString()}`,
        }
      }) {
        Column().backgroundColor(Color.Orange).width('100%').height('100%')
      }
      .width('100%').layoutWeight(1)
    }
    .width('100%')
  }
}

\entry\src\main\ets\entryability\EntryAbility.ets

/*
 * UIAbility - 用于为应用提供绘制界面的窗口
 * UIAbility 的相关配置,请参见 module.json5 配置文件中的 abilities 标签
 */

import { AbilityConstant, Configuration, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { MyLog } from '../utils/MyLog';
import { formBindingData, formInfo, formProvider } from '@kit.FormKit';
import { Helper } from '../utils/Helper';
import { rpc } from '@kit.IPCKit';

export default class EntryAbility extends UIAbility {

  // 用于保存通过卡片的 call 的方式拉起了 ability 时,传递过来的名为 method 的参数
  private cardCallMethod = ""

  // UIAbility 实例创建完成时(冷启动)
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    MyLog.d(`ability onCreate, parameters:${JSON.stringify((want.parameters))}`);

    this.handleCard(want)
  }

  // 当 UIAbility 已经启动了,之后再次启动时(热启动)
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    MyLog.d(`ability onNewWant, parameters:${JSON.stringify((want.parameters))}`);

    this.handleCard(want)
  }

  // 当通过卡片的 router 或 call 方式拉起了此 ability 时
  handleCard(want: Want) {

    // 有 method 参数,所以是 call 方式拉起的
    // 当卡片通过 call 的方式调用此应用时:
    // 1、此应用未启动时,则会拉起此应用,可以在 onCreate() 中通过 Want 获取到卡片传递过来的数据,但是此应用不会出现在最近任务列表中
    // 2、此应用已启动时,则在 callee.on() 中通过 data.readString() 获取卡片传递过来的数据
    // 3、此应用必须具有 ohos.permission.KEEP_BACKGROUND_RUNNING 权限
    if (want.parameters && want.parameters["method"] !== undefined) {
      this.cardCallMethod = want.parameters["method"].toString(); // 通过卡片的 call 的方式拉起了 ability 时,肯定有名为 method 的参数

      // 监听 Callee 与 Caller 约定的消息通知字符串(即通过卡片的 call 的方式拉起了 ability 时,其中的名为 method 的参数)
      // 注:本例是在 call 方式拉起应用时监听的,如果不是 call 方式拉起的则监听不到,要避免此问题,则别管谁拉起的都按约定值监听即可
      this.callee.on(this.cardCallMethod, (data: rpc.MessageSequence) => {

        // 通过 data.readString() 可以获取卡片通过 call 方式传递过来的数据
        MyLog.d(`this.callee.on, ${this.cardCallMethod}, ${data.readString()}}`)

        // 1 秒后更新卡片的信息
        setTimeout(() => {
          let formId = want.parameters!['ohos.extra.param.key.form_identity'].toString();
          let formData: Record<string, string> = {
            'content': Helper.getTimestampString()
          };
          let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
          formProvider.updateForm(formId, formInfo)
        }, 1000)

        // 这里必须要返回一个 rpc.Parcelable 对象(空的 rpc.Parcelable 对象即可)
        return new MyParcelable();
      });
    }

    // 有 ohos.extra.param.key.form_identity 参数,所以是 router 或 call 方式拉起的
    if (want.parameters && want.parameters['ohos.extra.param.key.form_identity'] !== undefined) {
      // 获取卡片 id 并更新卡片的信息(关于这部分的说明,请参见 /entryformability/EntryFormAbility.ets)
      let formId = want.parameters['ohos.extra.param.key.form_identity'].toString();
      let formData: Record<string, string> = {
        'content': Helper.getTimestampString()
      };
      let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)
      formProvider.updateForm(formId, formInfo)
    }
  }

  // UIAbility 实例销毁时
  onDestroy(): void {
    MyLog.d('ability onDestroy');

    if (this.cardCallMethod != '') {
      // 取消监听
      this.callee.off(this.cardCallMethod)
    }
  }

  // UIAbility 的窗口创建完成时
  // 在这里需要通过 windowStage.loadContent() 加载当前 UIAbility 的首页
  onWindowStageCreate(windowStage: window.WindowStage): void {
    MyLog.d('ability onWindowStageCreate');

    let record: Record<string, number> = {
      'myNumber': 5000
    };
    let storage: LocalStorage = new LocalStorage(record);

    // 加载当前 UIAbility 的首页
    //   必须是 @Entry 组件
    //   必须在 main_pages.json 文件(在 module.json5 中通过 "pages":"$profile:main_pages" 指定的)中声明
    // 这里指定的 LocalStorage 对象的数据,会自动合并到当前 UIAbility 的 LocalStorage.getShared() 中
    windowStage.loadContent('pages/Index', storage, (err) => {
      if (err.code) {
        MyLog.d(`windowStage.loadContent() failed, ${JSON.stringify(err)}`)
        return;
      }
      MyLog.d(`windowStage.loadContent() succeeded`)
    });
  }

  // UIAbility 的窗口销毁之前
  onWindowStageWillDestroy(windowStage: window.WindowStage) {
    MyLog.d('ability onWindowStageWillDestroy');
  }

  // UIAbility 的窗口销毁时
  onWindowStageDestroy(): void {
    MyLog.d('ability onWindowStageDestroy');
  }

  // 切换到前台时
  onForeground(): void {
    MyLog.d('ability onForeground');
  }

  // 切换到后台时
  onBackground(): void {
    MyLog.d('ability onBackground');
  }

  // 内存占用级别发生变化时的回调
  // 注:在 AbilityStage 中也有此回调
  onMemoryLevel(level: AbilityConstant.MemoryLevel): void {
    // MEMORY_LEVEL_MODERATE - 内存占用适中
    // MEMORY_LEVEL_LOW - 内存占用低
    // MEMORY_LEVEL_CRITICAL - 内存占用高
    MyLog.d(`ability onMemoryLevel ${level}`);
  }

  // 全局的环境配置发生变化时的回调(比如系统语言,深色浅色模式等)
  // 注:在 AbilityStage 中也有此回调
  onConfigurationUpdate(newConfig: Configuration): void {
    MyLog.d(`ability onConfigurationUpdate ${JSON.stringify(newConfig)}`);
  }
}

class MyParcelable implements rpc.Parcelable {
  num: number;
  str: string;

  constructor()
  constructor(num: number, str: string)

  constructor(num?: number, str?: string) {
    this.num = num ?? 0;
    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;
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

posted @ 2025-04-15 09:50  webabcd  阅读(104)  评论(0)    收藏  举报