元服务——介绍及环境搭建

2024-11-28 17:15:52
22次阅读
0个评论
最后修改时间:2024-11-29 23:52:57

一、什么是元服务

元服务(Meta Service)是一种依托HarmonyOS系统实现的轻量化、无需下载安装即可使用的服务形式,比传统的应用程序更加简便,主要特点包括:

  • 轻量化:用户不需要下载完整的应用程序,而是可以直接通过系统访问所需的服务,使用完毕后即可关闭,不会占用设备内存
  • 快速访问:元服务通常以卡片的形式呈现,可以通过桌面、搜索结果、消息通知等多种入口快速访问。
  • 个性化定制:用户可以根据个人需求自由组合不同的元服务卡片,实现个性化的服务组合。
  • 跨平台支持:基于HarmonyOS API开发的元服务支持在多种设备上运行,包括但不限于手机、平板、智能手表等,实现了服务的一次开发、多端运行。
  • 高效开发:元服务的开发周期较短,成本效益高,有利于促进开发者生态的繁荣发展。

二、DevEco Studio环境与示例

1、安装DevEco Studio

https://developer.huawei.com/consumer/cn/download/

2、创建元服务项目

2e3d55bf856d4c2ebd87f93bb09b74abb1695.png 需使用华为账号登录 成为开发者: https://id1.cloud.huawei.com/CAS/portal/loginAuth.html

创建一个“Register App ID” a60ed23ce54b4d058f1e97fef9dfe19ab1695.png

5576e7ee12f6413caa971503adf33cbeb1695.png

  • AppScope > app.json5:元服务的全局配置信息。

  • entry :HarmonyOS工程模块,编译构建生成一个HAP。

  • src > main > ets:用于存放ArkTS源码。

  • rc > main > ets > entryability:元服务的入口。 -

  • src > main > ets > pages:元服务包含的页面。 -

  • src > main > resources:用于存放元服务所用到的资源文件,如图形、多媒体、字符串、布局文件等。关于资源文件, -

  • src > main > module.json5:模块配置文件。主要包含HAP的配置信息、元服务在具体设备上的配置信息以及元服务的全局配置信息。

  • build-profile.json5:当前的模块信息 、编译信息配置项,包括buildOption、targets配置等。

  • hvigorfile.ts:模块级编译构建任务脚本,开发者可以自定义相关任务和代码实现。

  • oh_modules:用于存放三方库依赖信息。

  • build-profile.json5:元服务级配置信息,包括签名signingConfigs、产品配置products等。

  • hvigorfile.ts:元服务级编译构建任务脚本

3、生成元服务图标

e78c3747981a4d208fec5d3d9385e06eb1695.png

  • 图标格式:.png、.jpeg、.jpg格式的静态图片资源
  • 图标尺寸:1024 x 1024 px (正方形)
  • 图标背景:不透明
  • 质量要求:图标内容需清晰可辨,避免存在模糊、锯齿、拉伸等问题。
  • Color:推荐使用的图标颜色。选择不同颜色,右边图标预览区域可查看相应的效果。
  • Name:生成的图标名称。
  • Res Directory:生成的512px*512px尺寸图标在工程中的保存位置。
  • Save to:生成的216px*216px尺寸图标需要指定本地文件夹的保存位置。后续在AppGallery Connect上架元服务时,需使用该图标。

注:上传图片后在相应模块目录src > main > resources > base > media路径下生成元服务图标。 可在模块级module.json5中的icon字段中配置元服务图标。

4、构建元服务的第一个页面

默认为相对布局的代码,布局的方式有很多种。 线性布局 (Row/Column),层叠布局 (Stack),弹性布局 (Flex),相对布局 (RelativeContainer),栅格布局 (GridRow/GridCol),媒体查询 (@ohos.mediaquery),创建列表 (List),创建网格 (Grid/GridItem), 创建轮播 (Swiper),选项卡 (Tabs)。

这里我们先来看相对布局: RelativeContainer为采用相对布局的容器,支持容器内部的子元素设置相对位置关系,适用于界面复杂场景的情况,对多个子组件进行对齐和排列。子元素支持指定兄弟元素作为锚点,也支持指定父容器作为锚点,基于锚点做相对位置布局。

基本概念 锚点:通过锚点设置当前元素基于哪个元素确定位置。 对齐方式:通过对齐方式,设置当前元素是基于锚点的上中下对齐,还是基于锚点的左中右对齐。

锚点设置是指设置子元素相对于父元素或兄弟元素的位置依赖关系。在水平方向上,可以设置left、middle、right的锚点。在竖直方向上,可以设置top、center、bottom的锚点。

为了明确定义锚点,必须为RelativeContainer及其子元素设置ID,用于指定锚点信息。ID默认为“container”,其余子元素的ID通过id属性设置。不设置id的组件能显示,但是不能被其他子组件作为锚点,相对布局容器会为其拼接id,此id的规律无法被应用感知。互相依赖,环形依赖时容器内子组件全部不绘制。同方向上两个以上位置设置锚点,但锚点位置逆序时此子组件大小为0,即不绘制。

示例代码(ets/pages/index.ts)

import { authentication } from '@kit.AccountKit';
 import { BusinessError } from '@kit.BasicServicesKit';
 import { hilog } from '@kit.PerformanceAnalysisKit';
 /**
 * @author 坚果派·红目香薰
* @date 2024年11月5日13:58:20
 */
 @Entry
 @Component
 struct Index {
 @State message: string = '时辰时刻';
 @State base_time: Date = new Date(Date.now());
 @State str_time: string =
 this.formatTime(this.base_time.getHours().toString() + ":" + 
this.base_time.getMinutes() + ":" + this.base_time.getSeconds());
 @State show_change_time: string = 
this.getShiChen(this.base_time.getHours(),this.base_time.getMinutes());
 @State isf : boolean = true;
 build() {
 RelativeContainer() {
 Row() {
 Text(this.message)
 .id('shiChenShiKe')
 .fontSize(50)
 .fontWeight(FontWeight.Bold)
 .alignRules({
 center: { anchor: '__container__', align: VerticalAlign.Center },
 middle: { anchor: '__container__', align: HorizontalAlign.Center }
 })
 }.id('row1').margin({ top: 50 
}).justifyContent(FlexAlign.Center).width('100%')
 Row() {
 Text(this.str_time).fontSize(35).fontColor(Color.Black).fontWeight(FontWeight.Bo
 lder)
 }
      .backgroundColor('#66CCDD')
      .id('row2')
      .margin({ top: 150 })
      .justifyContent(FlexAlign.Center)
      .width('100%')
      Row() {
        
Text(this.show_change_time).fontSize(40).fontColor(Color.Black).fontWeight(FontW
 eight.Bolder)
      }
      .backgroundColor('#66CCDD')
      .id('row3')
      .margin({ top: 250 })
      .justifyContent(FlexAlign.Center)
      .width('100%')
      Row() {
        Button('开启时时模式').size({ width: 250, height: 80 
}).fontSize(30).onClick(() => {
          if(this.isf){
            this.isf = false;
            setInterval(()=>{this.show_change_time = 
this.Time_Change_Time();},1000);
          }
        }).id("btn1").width('100%')
      }.id('row4').margin({ top: 350 }).width('100%')
    }
    .height('100%')
    .width('100%')
  }
  Time_Change_Time():string{
    let timeInMs = Date.now();
    let dateObj = new Date(timeInMs);
    this.str_time = this.formatTime(dateObj.getHours() + ":" + 
dateObj.getMinutes() + ":" + dateObj.getSeconds());
    return this.getShiChen(dateObj.getHours(), dateObj.getMinutes());
  }
  formatTime(timeStr: string): string {
    let timeParts = timeStr.split(':');
    let formattedParts = timeParts.map((part) => {
      let num = parseInt(part);
      return num < 10? `0${num}` : `${num}`;
    });
    return formattedParts.join(':');
  }
  aboutToAppear() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    this.loginWithHuaweiID();
  }
  convertToChineseTimes(hour: number): string {
    if (23 <= hour || hour < 1) {
      return "子时";
    } else if (1 <= hour && hour < 3) {
      return "丑时";
    } else if (3 <= hour && hour < 5) {
      return "寅时";
    } else if (5 <= hour && hour < 7) {
      return "卯时";
    } else if (7 <= hour && hour < 9) {
      return "辰时";
    } else if (9 <= hour && hour < 11) {
      return "巳时";
    } else if (11 <= hour && hour < 13) {
      return "午时";
    } else if (13 <= hour && hour < 15) {
      return "未时";
    } else if (15 <= hour && hour < 17) {
      return "申时";
    } else if (17 <= hour && hour < 19) {
      return "酉时";
    } else if (19 <= hour && hour < 21) {
      return "戌时";
    } else {
      return "亥时";
    }
  }
  convertToChineseTime(hour: number, minute: number): string {
    let chineseHour = this.convertToChineseTimes(hour);
    let minuteMark = hour % 2 !== 0 ? "上" : "下";
    if (minute < 15) {
      minuteMark += "一刻";
    } else if (minute < 30) {
      minuteMark += "二刻";
    } else if (minute < 45) {
      minuteMark += "三刻";
    } else {
      minuteMark += "四刻";
    }
    return `${chineseHour}${minuteMark}`;
  }
  getShiChen(hour: number, minute: number): string {
    return this.convertToChineseTime(hour, minute);
  }
  /**
   * Sample code for using HUAWEI ID to log in to atomic service.
   * According to the Atomic Service Review Guide, when a atomic service has an 
account system,
   * the option to log in with a HUAWEI ID must be provided.
   * The following presets the atomic service to use the HUAWEI ID silent login 
function.
   * To enable the atomic service to log in successfully using the HUAWEI ID, 
please refer
   * to the HarmonyOS HUAWEI ID Access Guide to configure the client ID and 
fingerprint certificate.
   */
private loginWithHuaweiID() {
 // Create a login request and set parameters
 let loginRequest = new 
authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
 // Whether to forcibly launch the HUAWEI ID login page when the user is not 
logged in with the HUAWEI ID
 loginRequest.forceLogin = false;
 // Execute login request
 let controller = new authentication.AuthenticationController();
 controller.executeRequest(loginRequest).then((data) => {
 let loginWithHuaweiIDResponse = data as 
authentication.LoginWithHuaweiIDResponse;
 let authCode = loginWithHuaweiIDResponse.data?.authorizationCode;
 // Send authCode to the backend in exchange for unionID, session
 }).catch((error: BusinessError) => {
 hilog.error(0x0000, 'testTag', 'error: %{public}s', 
JSON.stringify(error));
 if (error.code == 
authentication.AuthenticationErrorCode.ACCOUNT_NOT_LOGGED_IN) {
 // HUAWEI ID is not logged in, it is recommended to jump to the login 
guide page
 }
 });
 }
 }

示例效果: 点击右侧previewer预览

5、创建虚拟机

选择右上角的“No Devices”下拉按钮,点击“Device Manager”

1dd0a47193ed4f019ec69d0dc9e68677b1695.png 创建模拟器“New Emulator” 66f359d1e65941708bc5be3c3e04c292b1695.png 下载目标虚拟机的包 f0e32610cf5149c2a169646255b9e773b1695.png 3aff1968ae98487dbf7f6acddaaa780ab1695.png

启动虚拟机

115e76ad0fb440d08593e4989c4aa844b1695.png 17b826616551492bb69153f62d0a9433b1695.png

最终效果

dcf5ba6a2d54446fa992c963b80d192cb1695.png

6、新建元服务卡片

操作顺序:File>New > Service Widget > Dynamic Widget

b636526cf04d47458d06a38dec424975b1695.png

19436774603e48678cb2f7eda9d24cc4b1695.png

  • Service widget name:卡片的名称,在同一个应用/服务中,卡片名称不能重复,且只能包含大小写字母、数字和下划线。
  • Display name:卡片预览面板上显示的卡片名称。仅API 11 及以上Stage工程支持配置该字段。
  • Description:卡片的描述信息。
  • Language:界面开发语言,可选择创建ArkTS/JS卡片,不支持JS卡片。
  • Support dimension:选择卡片的规格。部分卡片支持同时设置多种规格。首次创建服务卡片时,将默认生成一个EntryCard目录,用于存放卡片快照。
  • Default dimension:在下拉框中可选择默认的卡片。
  • Ability name:选择一个挂靠服务卡片的Form Ability,或者创建一个新的Form Ability。
  • Module name:卡片所属的模块。

7、元服务卡片编码

页面地址:src/main/ets/widget/pages/WidgetCard.ets

@Entry
 @Component
 struct WidgetCard {
 readonly message: string = '时辰时刻';
 formatTime(timeStr: string): string {
 let timeParts = timeStr.split(':');
 let formattedParts = timeParts.map((part) => {
 let num = parseInt(part);
 return num < 10 ? `0${num}` : `${num}`;
 });
 return formattedParts.join(':');
 }
 @State base_time: Date = new Date(Date.now());
  @State str_time: string =
    this.formatTime(this.base_time.getHours().toString() + ":" + 
this.base_time.getMinutes() + ":" +
    this.base_time.getSeconds());
  @State show_change_time: string = this.getShiChen(this.base_time.getHours(), 
this.base_time.getMinutes());
  @State isf: boolean = true;
  build() {
    RelativeContainer() {
      Row() {
        Text(this.message)
          .id('shiChenShiKe')
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .alignRules({
            center: { anchor: '__container__', align: VerticalAlign.Center },
            middle: { anchor: '__container__', align: HorizontalAlign.Center }
          })
      }.id('row1').margin({ top: 50 
}).justifyContent(FlexAlign.Center).width('100%')
      Row() {
        
Text(this.str_time).fontSize(35).fontColor(Color.Black).fontWeight(FontWeight.Bo
 lder)
      }
      .backgroundColor('#66CCDD')
      .id('row2')
      .margin({ top: 115 })
      .justifyContent(FlexAlign.Center)
      .width('100%')
      Row() {
        
Text(this.show_change_time).fontSize(40).fontColor(Color.Black).fontWeight(FontW
 eight.Bolder)
      }
      .backgroundColor('#66CCDD')
      .id('row3')
      .margin({ top: 150 })
      .justifyContent(FlexAlign.Center)
      .width('100%')
      Row() {
        Button('更新时间')
          .size({ width: 250, height: 80 })
          .fontSize(30)
          .onClick(() => {
            this.show_change_time = this.Time_Change_Time();
          })
          .id("btn1")
          .width('100%')
      }.id('row4').margin({ top: 220 }).width('100%')
    }
    .height('100%')
    .width('100%')
  }
  Time_Change_Time(): string {
    let timeInMs = Date.now();
    let dateObj = new Date(timeInMs);
    this.str_time = this.formatTime(dateObj.getHours() + ":" + 
dateObj.getMinutes() + ":" + dateObj.getSeconds());
    return this.getShiChen(dateObj.getHours(), dateObj.getMinutes());
  }
  convertToChineseTimes(hour: number): string {
    if (23 <= hour || hour < 1) {
      return "子时";
    } else if (1 <= hour && hour < 3) {
      return "丑时";
    } else if (3 <= hour && hour < 5) {
      return "寅时";
    } else if (5 <= hour && hour < 7) {
      return "卯时";
    } else if (7 <= hour && hour < 9) {
      return "辰时";
    } else if (9 <= hour && hour < 11) {
      return "巳时";
    } else if (11 <= hour && hour < 13) {
      return "午时";
    } else if (13 <= hour && hour < 15) {
      return "未时";
    } else if (15 <= hour && hour < 17) {
      return "申时";
    } else if (17 <= hour && hour < 19) {
      return "酉时";
    } else if (19 <= hour && hour < 21) {
      return "戌时";
    } else {
      return "亥时";
    }
  }
  convertToChineseTime(hour: number, minute: number): string {
    let chineseHour = this.convertToChineseTimes(hour);
    let minuteMark = hour % 2 !== 0 ? "上" : "下";
    if (minute < 15) {
      minuteMark += "一刻";
    } else if (minute < 30) {
      minuteMark += "二刻";
    } else if (minute < 45) {
      minuteMark += "三刻";
    } else {
      minuteMark += "四刻";
    }
    return `${chineseHour}${minuteMark}`;
  }
getShiChen(hour: number, minute: number): string {
 return this.convertToChineseTime(hour, minute);
 }
 } 

效果:

3ebae5fdf5d34644ab2f564c2fcf6cfeb1695.png

收藏00

登录 后评论。没有帐号? 注册 一个。