本文是系列文章,其他文章见:
敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(1)
敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(3)

本文完整源码查看funny-widget

简介

因为工作需要,准备开发元服务,所以就想着搞一个电子木鱼的DEMO学习一下元服务以及桌面卡片的功能开发知识。

详细了解HarmonyOS的元服务,可查看官方介绍

涉及知识点

  • 元服务开发流程
  • 加载图片
  • 播放音频
  • 开发调试
  • 组件代码在卡片和元服务间共享
  • 数据在卡片和元服务间共享

开发卡片

桌面卡片转存失败,建议直接上传图片文件

新建卡片

卡片displayName竟然不能用中文?

The display name must contain 1 to 30 characters, start with a letter, and include only digits, letters, underscores (_), spaces, commas (,) and periods (.).

description也不能用中文?

The description must contain 1 to 127 characters, start with a letter, and include only digits, letters, underscores (_), spaces, commas () and periods (.).

widget名会用作代码类名,格式为驼峰命名,因此建议widgetName也使用驼峰命名法,以免不一致。

卡片名称后面可以通过修改配置文件进行修改,前期IDE问题较多。

组件代码共享

在卡片代码中引用电子木鱼组件。

import { ElectronicWoodenFishComponent } from '../../components/ElectronicWoodenFishComponent';

@Entry
@Component
struct ElectronicWoodenFishCard {

  @State meritCount: number = 0;

  build() {
    Row() {
      Column() {
        ElectronicWoodenFishComponent({
          meritCount: this.meritCount,
          onClickWoodenFish: () => {
            
          }
        })
      }
      .width(this.FULL_WIDTH_PERCENT)
    }
    .height(this.FULL_HEIGHT_PERCENT)
    .onClick(() => {
      postCardAction(this, {
        action: this.ACTION_TYPE,
        abilityName: this.ABILITY_NAME,
        params: {
          message: this.MESSAGE
        }
      });
    })
  }
}

点击卡片上的木鱼会走onClickWoodenFish逻辑,点击剩余卡片区域会走最外层的onClick方法拉起元服务。

关于UI代码共享:

不是所有的UI组件都可以在卡片中使用,卡片中可以使用的组件其实在源代码中有标明:

/**
 * Defines Image Component.
 *
 * @syscap SystemCapability.ArkUI.ArkUI.Full
 * @crossplatform
 * @form
 * @atomicservice
 * @since 11
 */
declare const Image: ImageInterface;

也就是注释中有@form标志的才支持在卡片中使用。

但是如果是在ElectronicWoodenFishCard嵌入其他自定义组件,IDE的代码检查貌似就失效了,所以官方似乎也不建议在卡片中嵌套自定义组件。

数据共享

EntryFormAbility中的onAddForm方法获取meritCount并用FormDataClass初始化卡片功德值。

export default class EntryFormAbility extends FormExtensionAbility {
  onAddForm(want: Want) {
    hilog.info(0x0000, '卡片', '%{public}s', 'EntryFormAbility onAddForm');
    console.debug(`[卡片]onAddForm`)
    // Called to return a FormBindingData object.
    DataManager.shared.init(this.context)
    let meritCount: number = DataManager.shared.getSync('meritCount', 0) as number
    console.debug(`[卡片]onFormEvent,meritCount=${meritCount}`)

    class FormDataClass {
      meritCount: number = meritCount;
    }

    let formData = new FormDataClass();
    return formBindingData.createFormBindingData(formData);
  }
}

卡片ElectronicWoodenFishCard类,通过LocalStorage获取传入的meritCount参数。

import { ElectronicWoodenFishComponent } from '../../components/ElectronicWoodenFishComponent';

let storage = new LocalStorage();
@Entry(storage)
@Component
struct ElectronicWoodenFishCard {

  @LocalStorageProp('meritCount') meritCount: number = 0;

  build() {
    Row() {
      Column() {
        ElectronicWoodenFishComponent({
          meritCount: this.meritCount,
          onClickWoodenFish: () => {
            console.log(`playSound实现`)
            postCardAction(this, {
              action: 'message',
              abilityName: this.ABILITY_NAME,
              params: {
                func: 'clickWoodenFish'
              }
            });
          }
        })
      }
      .width(this.FULL_WIDTH_PERCENT)
    }
    .height(this.FULL_HEIGHT_PERCENT)
    .onClick(() => {
      postCardAction(this, {
        action: this.ACTION_TYPE,
        abilityName: this.ABILITY_NAME,
        params: {
          message: this.MESSAGE
        }
      });
    })
  }
}

onClickWoodenFish方法中调用message类型action,触发EntryFormAbilityonFormEvent方法。

export default class EntryFormAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string) {
    if (JSON.parse(message).func === 'clickWoodenFish') {
      DataManager.shared.init(this.context)
      let meritCount: number = DataManager.shared.getSync('meritCount', 0) as number
      console.debug(`[卡片]onFormEvent,meritCount=${meritCount}`)
      DataManager.shared.putSync('meritCount', meritCount + 1)

      class FormDataClass {
        meritCount: number = meritCount;
      }

      let formData = new FormDataClass();
      let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData);
      formProvider.updateForm(formId, formInfo).then(() => {
        hilog.info(0x0000, '卡片', 'FormAbility updateForm success.');
      }).catch((error: BusinessError) => {
        hilog.info(0x0000, '卡片', `Operation updateForm failed. Cause: ${JSON.stringify(error)}`);
      })
    }
  }
}

onFormEvent中对数据进行操作,然后通过formProvider.updateForm更新卡片。

卡片音效

卡片代码不能直接播放音频🌚🌚🌚。

尝试在EntryFormAbilityonFormEvent方法中播放。

export default class EntryFormAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string) {
    if (JSON.parse(message).func === 'clickWoodenFish') {
      AudioManager.shared.playSound()
      // ...
    }
  }
}

目前测试效果是也不可以🌚🌚🌚。

看官方文档,通过call事件貌似可以实现该功能,但可惜元服务不支持call事件……后面再考虑用应用+卡片的形式进行验证。

运行调试

卡片代码以及EntryFormAbility都不能断点调试,因此调试功能只能通过打印日志。

在DevEco的日志Tab页中,第三个下拉选择框是选择指定进程的。

和卡片相关的有几个:

com.ohos.formrenderservice:这里的是卡片代码输出的日志。(ElectronicWoodenFishCard类以及内嵌组件)
com.atomicservice.xxxxx:form:卡片Aibility相关日志。(EntryFormAbility类以及调用类)
com.atomicservice.xxxxx:form(Dead):Dead表示卡片Aibility被挂起。

卡片调用messageaction会重新激活Dead状态的进程,所以日志界面频繁刷新,还是比较烦的,最好是在第二个下拉选择框里选择No filters,然后通过关键字筛选。

总结

开发元服务卡片的过程想当痛苦,各种不支持,不能调试,给我造成的暴击伤害比之前开发Hvigor插件还大!!!

而且卡片的功能实现依赖宿主能力,也就是说通过应用添加的卡片和通过元服务添加的卡片拥有的能力是不一样的。

鸿蒙前期的支持精力主要在应用开发上面,所以元服务的问题比较多。

本文完整源码查看funny-widget

Logo

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

更多推荐