二、鸿蒙应用程序包基础知识

鸿蒙开发基础知识

一 应用程序包概述

1 应用与应用程序包
用户应用程序泛指运行在设备的操作系统之上,为用户提供特定服务的程序,简称“应用”。
一个应用所对应的软件包文件,称为“应用程序包”。
这个做客户端的同学们都应该知道吧。

当前系统提供了应用程序包开发、安装、查询、更新、卸载的管理机制,便于开发者开发和管理应用。同时,系统还屏蔽了不同的芯片平台的差异(包括x86/ARM,32位/64位等),应用程序包在不同的芯片平台都能够安装运行,这使得开发者可以聚焦于应用的功能实现。


2 应用的多Module设计机制
一个应用通常会包含多种功能,将不同的功能特性按模块来划分和管理是一种良好的设计方式。在开发过程中,我们可以将每个功能模块作为一个独立的Module进行开发,Module中可以包含源代码、资源文件、第三方库、配置文件等,每一个Module可以独立编译,实现特定的功能。这种模块化、松耦合的应用管理方式有助于应用的开发、维护与扩展。

一个应用往往需要适配多种设备类型,在采用多Module设计的应用中,每个Module都会标注所支持的设备类型。有些Module支持全部类型的设备,有些Module只支持某一种或几种型的设备(比如平板),那么在应用市场分发应用包时,也能够根据设备类型做精准的筛选和匹配,从而将不同的包合理的组合和部署到对应的设备上。


3 Module类型
3.1 Ability类型:功能和特性。编译后生成.hap(Harmony Ability Package),HAP包可独立运行和安装,一个应用可有一个或多个HAP包,是基本单位。
Ability类型又分两种类型:

  • entry类型:主Module,包含应用的入口界面、入口图标和主功能特性,编译后生成entry类型的HAP。每一个应用分发到同一类型的设备上的应用程序包,只能包含唯一一个entry类型的HAP。
  • feature类型:应用的动态特性模块,编译后生成feature类型的HAP。一个应用中可以包含一个或多个feature类型的HAP,也可以不包含。

3.2 Library类型:代码和资源的共享。Library类型的Module分为Static和Shared两种类型,编译后会生成共享包。

  • Static Library:静态共享库。编译后会生成一个以.har为后缀的文件,即静态共享包HAR(Harmony Archive)。
  • Shared Library:动态共享库。编译后会生成一个以.hsp为后缀的文件,即动态共享包HSP(Harmony Shared Package)。
说明
实际上,Shared Library编译后除了会生成一个.hsp文件,还会生成一个.har文件。这个.har文件中包含了HSP对外导出的接口,
应用中的其他模块需要通过.har文件来引用HSP的功能。为了表述方便,我们通常认为Shared Library编译后生成HSP。

  HAR与HSP两种共享包的主要区别体现在:

      

      图1 HAR和HSP在APP包中的形态示意图

     

      

二 应用程序包结构 

1 Stage模型应用程序包结构
1.1 开发态包结构

   

工程结构主要包含的文件类型及用途如下:
说明
AppScope目录由DevEco Studio自动生成,不可更改。
Module目录名称可以由DevEco Studio自动生成(比如entry、library等),也可以自定义。为了便于说明,下表中统一采用Module_name表示。

 

1.2 编译态包结构
  不同类型的Module编译后会生成对应的HAP、HAR、HSP等文件,开发态视图与编译态视图的对照关系如下:

  图2 开发态与编译态的工程结构视图

 

  从开发态到编译态,Module中的文件会发生如下变更:

  • ets目录:ArkTS源码编译生成.abc文件。
  • resources目录:AppScope目录下的资源文件会合入到Module下面资源目录中,如果两个目录下的存在重名文件,编译打包后只会保留AppScope目录下的资源文件。
  • module配置文件:AppScope目录下的app.json5文件字段会合入到Module下面的module.json5文件之中,编译后生成HAP或HSP最终的module.json文件。

  说明
  在编译HAP和HSP时,会把他们所依赖的HAR直接编译到HAP和HSP中。

1.3 发布态包结构
  每个应用中至少包含一个.hap文件,可能包含若干个.hsp文件、也可能不含,一个应用中的所有.hap与.hsp文件合在一起称为Bundle,其对应的bundleName是应用的唯一标识(详见app.json5配置文件中的bundleName标签)。
  当应用发布上架到应用市场时,需要将Bundle打包为一个.app后缀的文件用于上架,这个.app文件称为App Pack(Application Package),与此同时,DevEco Studio工具自动会生成一个pack.info文件。pack.info文件描述了App Pack中每个HAP和HSP的属性,包含APP中的bundleName和versionCode信息、以及Module中的name、type和abilities等信息。
  说明
  App Pack是发布上架到应用市场的基本单元,但是不能在设备上直接安装和运行。
  在应用签名、云端分发、端侧安装时,都是以HAP/HSP为单位进行签名、分发和安装的。

 

  图3 编译发布与上架部署流程图

 

选择合适的包类型

HAP、HAR、HSP三者的功能和使用场景总结对比如下:

 

HAP、HSP、HAR支持的规格对比如下,其中“√”表示是,“×”表示否。

开发者可以根据实际场景所需的能力,选择相应类型的包进行开发。在后续的章节中还会针对如何使用HAPHARHSP分别展开详细介绍。

说明
HAR虽然不支持在配置文件中声明pages页面,但是可以包含pages页面,并通过命名路由的方式进行跳转。
由于HSP仅支持应用内共享,如果HAR依赖了HSP,则该HAR文件仅支持应用内共享,不支持发布到二方仓或三方仓供其他应用使用,否则会导致编译失败。
HAR和HSP均不支持循环依赖,也不支持依赖传递。

 

2 FA模型应用程序包结构(略)

 

三 应用程序包开发与使用

 

1 HAP    Harmony Ability Package

HAP(Harmony Ability Package)是应用安装和运行的基本单元。HAP包是由代码、资源、第三方库、配置文件等打包生成的模块包,其主要分为两种类型:entry和feature。
  •     entry:应用的主模块,作为应用的入口,提供了应用的基础功能。
  •     feature:应用的动态特性模块,作为应用能力的扩展,可以根据用户的需求和设备类型进行选择性安装。
应用程序包:一个entry包,可选多个功能性的feature包。
 
 

1.1 使用场景
  •     单HAP场景:如果只包含UIAbility组件,无需使用ExtensionAbility组件,优先采用单HAP(即一个entry包)来实现应用开发。虽然一个HAP中可以包含一个或多个UIAbility组件,为了避免不必要的资源加载,推荐采用“一个UIAbility+多个页面”的方式。
  •     多HAP场景:如果应用的功能比较复杂,需要使用ExtensionAbility组件,可以采用多HAP(即一个entry包+多个feature包)来实现应用开发,每个HAP中包含一个UIAbility组件或者一个ExtensionAbility组件。在这种场景下,可能会存在多个HAP引用相同的库文件,导致重复打包的问题。
 
 

1.2 约束限制
  •     不支持导出接口和ArkUI组件,给其他模块使用。
  •     多HAP场景下,App Pack包中同一设备类型的所有HAP中必须有且只有一个Entry类型的HAP,Feature类型的HAP可以有一个或者多个,也可以没有。
  •     多HAP场景下,同一应用中的所有HAP的配置文件中的bundleName、versionCode、versionName、minCompatibleVersionCode、debug、minAPIVersion、targetAPIVersion、apiReleaseType相同,同一设备类型的所有HAP对应的moduleName标签必须唯一。HAP打包生成App Pack包时,会对上述参数配置进行校验。
  •     多HAP场景下,同一应用的所有HAP、HSP的签名证书要保持一致。上架应用市场是以App Pack形式上架,应用市场分发时会将所有HAP从App Pack中拆分出来,同时对其中的所有HAP进行重签名,这样保证了所有HAP签名证书的一致性。在调试阶段,开发者通过命令行或DevEco Studio将HAP安装到设备上时,要保证所有HAP签名证书一致,否则会出现安装失败的问题。
 
 
 

1.3 创建

下面简要介绍如何通过DevEco Studio新建一个HAP模块。
  1. 创建工程,详见构建第一个ArkTS应用
  2. 在工程目录上单击右键,选择New > Module。
  3. 在弹出的对话框中选择Empty Ability模板,单击Next。
  4. 在Module配置界面,配置Module name,选择Module Type和Device Type,然后单击Next。
  5. 在Ability配置界面,配置Ability name,然后单击Finish完成创建。
 
 

1.4 开发

 

1.5 调试

通过DevEco Studio编译打包,生成单个或者多个HAP,即可基于HAP进行调试。如需根据不同的部署环境、目标人群、运行环境等,将同一个HAP定制编译为不同版本,请参见定制编译指导
开发者可以采用DevEco Studio或者hdc工具进行调试:
  • 方法一: 使用DevEco Studio进行调试,详见应用程序包调试方法
  • 方法二: 使用hdc工具(可通过HarmonyOS SDK获取,在SDK的toolchains目录下)进行调试。
    在调试前,需要先安装或更新HAP,此处有两种方式:
    • 直接使用hdc安装、更新HAP。
      HAP的路径为开发平台上的文件路径,以Windows开发平台为例,命令参考如下:
// 安装、更新,多HAP可以指定多个文件路径
hdc install entry.hap feature.hap
// 执行结果
install bundle successfully.
// 卸载
hdc uninstall com.example.myapplication
// 执行结果
uninstall bundle successfully.
 
            先执行hdc shell,再使用bm工具安装、更新HAP。
            HAP的文件路径为真机上的文件路径,命令参考如下:
// 先执行hdc shell才能使用bm工具
hdc shell
// 安装、更新,多HAP可以指定多个文件路径
bm install -p /data/app/entry.hap /data/app/feature.hap
// 执行结果
install bundle successfully.
// 卸载
bm uninstall -n com.example.myapplication
// 执行结果
uninstall bundle successfully.
完成HAP安装或更新后,即可参考相关调试命令进行调试
 
 
 
 

2 HAR      Harmony Archive

 
HAR(Harmony Archive)是静态共享包,可以包含代码、C++库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。
 

2.1 使用场景

  • 作为二方库,发布到OHPM私仓,供公司内部其他应用使用。
  • 作为三方库,发布到OHPM中心仓,供其他应用使用。
 
 
 

2.2 约束限制

  • HAR不支持在设备上单独安装/运行,只能作为应用模块的依赖项被引用。
  • HAR不支持在配置文件中声明UIAbility组件与ExtensionAbility组件。
  • HAR不支持在配置文件中声明pages页面,但是可以包含pages页面,并通过命名路由的方式进行跳转。
  • HAR不支持引用AppScope目录中的资源。在编译构建时,AppScope中的内容不会打包到HAR中,因此会导致HAR资源引用失败。
  • HAR可以依赖其他HAR,但不支持循环依赖,也不支持依赖传递。
 
 

2.3 创建

通过DevEco Studio创建一个HAR模块,详见创建库模块
 
 

2.4 开发

介绍如何导出HAR的ArkUI组件、接口、资源,供其他应用或当前应用的其他模块引用。
Index.ets文件是HAR导出声明文件的入口,HAR需要导出的接口,统一在Index.ets文件中导出。Index.ets文件是DevEco Studio默认自动生成的,用户也可以自定义,在模块的oh-package.json5文件中的main字段配置入口声明文件,配置如下所示:
{
  "main": "Index.ets"
}
 
 
 

2.4.1 导出ArkUI组件

ArkUI组件的导出方式与ts的导出方式一致,通过export导出ArkUI组件,示例如下:
// library/src/main/ets/components/mainpage/MainPage.ets
@Component
export struct MainPage {
  @State message: string = 'HAR MainPage';
 
 
  build() {
    Column() {
      Row() {
        Text(this.message)
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
      }
      .margin({ top: '32px' })
      .height(56)
      .width('624px')
 
 
      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center, alignContent: FlexAlign.Center }) {
        Column() {
          Image($r('app.media.pic_empty')).width('33%')
          Text($r('app.string.empty'))
            .fontSize(14)
            .fontColor($r('app.color.text_color'))
        }
      }.width('100%')
      .height('90%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.page_background'))
  }
}
 
HAR对外暴露的接口,在Index.ets导出文件中声明如下所示:
// library/Index.ets
export { MainPage } from './src/main/ets/components/mainpage/MainPage';
 

 

2.4.2 导出ts类和方法

通过export导出ts类和方法,支持导出多个ts类和方法,示例如下所示:
// library/src/main/ts/test.ets
export class Log {
    static info(msg: string) {
        console.info(msg);
    }
}
 
 
export function func() {
  return 'har func';
}
 
 
export function func2() {
  return 'har func2';
}
 
HAR对外暴露的接口,在Index.ets导出文件中声明如下所示:
// library/Index.ets
export { Log } from './src/main/ts/test';
export { func } from './src/main/ts/test';
export { func2 } from './src/main/ts/test';
 
 

2.4.3 导出native方法

在HAR中也可以包含C++编写的so。对于so中的native方法,HAR通过以下方式导出,以导出libnative.so的加法接口add为例:
// library/src/main/ets/utils/nativeTest.ts
import native from 'liblibrary.so';
 
 
export function nativeAdd(a: number, b: number): number {
  let result: number = native.add(a, b);
  return result;
}
 
HAR对外暴露的接口,在Index.ets导出文件中声明如下所示:
// library/Index.ets
export { nativeAdd } from './src/main/ets/utils/nativeTest';
 
 
 

2.4.4 资源

在编译构建HAP时,DevEco Studio会从HAP模块及依赖的模块中收集资源文件,如果不同模块下的资源文件出现重名冲突时,DevEco Studio会按照以下优先级进行覆盖(优先级由高到低):
  • AppScope(仅API9的Stage模型支持)。
  • HAP包自身模块。
  • 依赖的HAR模块,如果依赖的多个HAR之间有资源冲突,会按照工程oh-package.json5中dependencies下的依赖顺序进行覆盖,依赖顺序在前的优先级较高。例如下方示例中dayjs和lottie中包含同名文件时,会优先使用dayjs中的资源。
 
说明
如果在AppScope/HAP模块/HAR模块的国际化目录中配置了资源,在相同的国际化限定词下,合并的优先级也遵循上述规则。同时,国际化限定词中配置的优先级高于在base中的配置。如:在AppScope的base中配置了资源字段,在HAR模块的en_US中配置了同样的资源字段,则在en_US的使用场景中,会更优先使用HAR模块中配置的资源字段。
// oh-package.json5
{
"dependencies": {
  "dayjs": "^1.10.4",
  "lottie": "^2.0.0"
}
}
 
 
 

2.5 使用

介绍如何配置HAR依赖,并引用HAR的ArkUI组件、接口、资源。
引用HAR前,需要先配置对HAR的依赖,详见引用HAR文件和资源

2.5.1 引用HAR的ArkUI组件

HAR的依赖配置成功后,可以引用HAR的ArkUI组件。ArkUI组件的导入方式与ts的导入方式一致,通过import引入HAR导出的ArkUI组件,示例如下所示:
// entry/src/main/ets/pages/IndexSec.ets
import { MainPage } from 'library';
 
@Entry
@Component
struct IndexSec {
  build() {
    Row() {
      // 引用HAR的ArkUI组件
      MainPage()
    }
    .height('100%')
  }
}
 
 

2.5.2 引用HAR的ts类和方法

通过import引用HAR导出的ts类和方法,示例如下所示:
// entry/src/main/ets/pages/Index.ets
import { Log } from 'library';
import { func } from 'library';
 
 
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
 
 
  build() {
    Column() {
      Text(this.message)
        .fontFamily('HarmonyHeiTi')
        .fontWeight(FontWeight.Bold)
        .fontSize(32)
        .fontWeight(700)
        .fontColor($r('app.color.text_color'))
        .textAlign(TextAlign.Start)
        .margin({ top: '32px' })
        .width('624px')
 
 
      //引用HAR的ts类和方法
      Button($r('app.string.button'))
        .id('button')
        .height(48)
        .width('624px')
        .margin({ top: '4%' })
        .type(ButtonType.Capsule)
        .fontFamily('HarmonyHeiTi')
        .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
        .backgroundColor($r('app.color.button_background'))
        .fontColor($r('sys.color.ohos_id_color_foreground_contrary'))
        .fontSize($r('sys.float.ohos_id_text_size_button1'))
        .onClick(() => {
          // 引用HAR的类和方法
          Log.info('har msg');
          this.message = 'func return: ' + func();
        })
    }
    .width('100%')
    .backgroundColor($r('app.color.page_background'))
    .height('100%')
  }
}
 
 

2.5.3 引用HAR的native方法

通过import引用HAR导出的native方法,示例如下所示:
// entry/src/main/ets/pages/Index.ets
import { nativeAdd } from 'library';
 
 
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
 
 
  build() {
    Column() {
      Text(this.message)
        .fontFamily('HarmonyHeiTi')
        .fontWeight(FontWeight.Bold)
        .fontSize(32)
        .fontWeight(700)
        .fontColor($r('app.color.text_color'))
        .textAlign(TextAlign.Start)
        .margin({ top: '32px' })
        .width('624px')
 
 
      //引用HAR的native方法
      Button($r('app.string.native_add'))
        .id('nativeAdd')
        .height(48)
        .width('624px')
        .margin({ top: '4%', bottom: '6%' })
        .type(ButtonType.Capsule)
        .fontFamily('HarmonyHeiTi')
        .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
        .backgroundColor($r('app.color.button_background'))
        .fontColor($r('sys.color.ohos_id_color_foreground_contrary'))
        .fontSize($r('sys.float.ohos_id_text_size_button1'))
        .onClick(() => {
          this.message = 'result: ' + nativeAdd(1, 2);
        })
    }
    .width('100%')
    .backgroundColor($r('app.color.page_background'))
    .height('100%')
  }
}
 
 

2.5.4 引用HAR的资源

通过$r引用HAR中的资源,例如在HAR模块的src/main/resources里添加字符串资源(在string.json中定义,name:hello_har)和图片资源(icon_har.png),然后在Entry模块中引用该字符串和图片资源的示例如下所示:
// entry/src/main/ets/pages/Index.ets
@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
 
 
  build() {
    Column() {
      // 引用HAR的字符串资源
      Text($r('app.string.hello_har'))
        .id('stringHar')
        .fontFamily('HarmonyHeiTi')
        .fontColor($r('app.color.text_color'))
        .fontSize(24)
        .fontWeight(500)
        .margin({ top: '40%' })
 
 
      List() {
        ListItem() {
          // 引用HAR的图片资源
          Image($r('app.media.icon_har'))
            .id('iconHar')
            .borderRadius('48px')
        }
        .margin({ top: '5%' })
        .width('312px')
      }
      .alignListItem(ListItemAlign.Center)
    }
    .width('100%')
    .backgroundColor($r('app.color.page_background'))
    .height('100%')
  }
}
 
 
 

2.6 编译

HAR可以作为二方库和三方库提供给其他应用使用,如果需要对代码资产进行保护时,建议开启混淆能力。
混淆能力开启后,DevEco Studio在构建HAR时,会对代码进行编译、混淆及压缩处理,保护代码资产。
说明
仅Stage模型的ArkTS工程支持混淆。
HAR开启混淆后资源ID为-1,ResourceManager等通过ID获取资源的API不再生效。
对于API 10及以上版本,HAR模块默认开启混淆能力,可以在HAR模块的build-profile.json5文件中的ruleOptions字段下的enable进行设置,配置如下所示:
{
  "apiType": "stageMode",
  "buildOption": {
  },
  "buildOptionSet": [
    {
      "name": "release",
      "arkOptions": {
        "obfuscation": {
          "ruleOptions": {
            "enable": true,
            "files": [
              "./obfuscation-rules.txt"
            ]
          },
          "consumerFiles": [
            "./consumer-rules.txt"
          ]
        }
      }
    },
  ],
  "targets": [
    {
      "name": "default"
    }
  ]
}
 
 

编译生成TS文件

说明
在har中使用Sendable class时,开启该配置。
har模块中arkts文件编译后,默认产物为js文件,想要将产物修改为ts文件,可以在har模块src/main目录下的module.json5文件中的"metadata"字段下的"UseTsHar"进行设置,配置如下所示:
{
  "module": {
    "name": "TsClosedHar",
    "type": "har",
    "deviceTypes": [
      "default",
      "tablet",
      "2in1"
    ],
    "metadata": [
      {
        "name": "UseTsHar",
        "value": "true"
      }
    ]
  }
}
 
 

2.7 发布

详见发布HAR
 
 
 
 

3 HSP  Harmony Shared Package

HSP(Harmony Shared Package)是动态共享包,可以包含代码、C++库、资源和配置文件,通过HSP可以实现代码和资源的共享。HSP不支持独立发布,而是跟随其宿主应用的APP包一起发布,与宿主应用同进程,具有相同的包名和生命周期。
 
说明
应用内HSP:在编译过程中与应用包名(bundleName)强耦合,只能给某个特定的应用使用。
集成态HSP:构建、发布过程中,不与特定的应用包名耦合;使用时,工具链支持自动将集成态HSP的包名替换成宿主应用包名。
 
 

3.1 使用场景

多个HAP/HSP共用的代码和资源放在同一个HSP中,可以提高代码、资源的可重用性和可维护性,同时编译打包时也只保留一份HSP代码和资源,能够有效控制应用包大小。
HSP在运行时按需加载,有助于提升应用性能。
同一个组织内部的多个应用之间,可以使用集成态HSP实现代码和资源的共享。

3.2 约束限制

HSP不支持在设备上单独安装/运行,需要与依赖该HSP的HAP一起安装/运行。HSP的版本号必须与HAP版本号一致。
HSP不支持在配置文件中声明UIAbility组件与ExtensionAbility组件。
HSP可以依赖其他HAR或HSP,但不支持循环依赖,也不支持依赖传递。
集成态HSP只支持Stage模型
集成态HSP需要API12及以上版本,使用标准化的OHMUrl格式。

 

3.3 创建

通过DevEco Studio创建一个HSP模块,详见创建HSP模块,我们以创建一个名为library的HSP模块为例。基本的工程目录结构如下:
MyApplication
├── library
│ ├── src
│ │ └── main
│ │ ├── ets
│ │ │ └── pages
│ │ │ └── index.ets
│ │ ├── resources
│ │ └── module.json5
│ ├── oh-package.json5
│ ├── index.ets
│ └── build-profile.json5 //模块级
└── build-profile.json5 //工程级

 

3.4 开发

介绍如何导出HSP的ArkUI组件、接口、资源,供应用内的其他HAP/HSP引用。

3.4.1 导出ArkUI组件

ArkUI组件可以通过export导出,例如:
// library/src/main/ets/components/MyTitleBar.ets
@Component
export struct MyTitleBar {
  build() {
    Row() {
      Text($r('app.string.library_title'))
        .id('library')
        .fontFamily('HarmonyHeiTi')
        .fontWeight(FontWeight.Bold)
        .fontSize(32)
        .fontColor($r('app.color.text_color'))
    }
    .width('100%')
  }
}
对外暴露的接口,需要在入口文件index.ets中声明:
// library/index.ets
export { MyTitleBar } from './src/main/ets/components/MyTitleBar';

 

3.4.2 导出ts类和方法

通过export导出ts类和方法,例如:
// library/src/main/ets/utils/test.ets
export class Log {
  static info(msg: string): void {
    console.info(msg);
  }
}
export function add(a: number, b: number): number {
  return a + b;
}
export function minus(a: number, b: number): number {
  return a - b;
}
对外暴露的接口,需要在入口文件index.ets中声明:
// library/index.ets
export { Log, add, minus } from './src/main/ets/utils/test';

3.4.3 导出native方法

在HSP中也可以包含C++编写的so。对于so中的native方法,HSP通过间接的方式导出,以导出liblibrary.so的乘法接口multi为例:
// library/src/main/ets/utils/nativeTest.ets
import native from 'liblibrary.so';
export function nativeMulti(a: number, b: number): number {
  let result: number = native.multi(a, b);
  return result;
}
对外暴露的接口,需要在入口文件index.ets中声明:
// library/index.ets
export { nativeMulti } from './src/main/ets/utils/nativeTest';

3.4.4 通过$r访问HSP中的资源

在组件中,经常需要使用字符串、图片等资源。HSP中的组件需要使用资源时,一般将其所用资源放在HSP包内,而非放在HSP的使用方处,以符合高内聚低耦合的原则。
在工程中,常通过$r/$rawfile的形式引用应用资源。可以用$r/$rawfile访问本模块resources目录下的资源,如访问resources目录下定义的图片src/main/resources/base/media/example.png时,可以用$r("app.media.example")。有关$r/$rawfile的详细使用方式,请参阅文档资源分类与访问中“资源访问-应用资源”小节。
不推荐使用相对路径的方式,容易引用错误路径。例如:
当要引用上述同一图片资源时,在HSP模块中使用Image("../../resources/base/media/example.png"),实际上该Image组件访问的是HSP调用方(如entry)下的资源entry/src/main/resources/base/media/example.png。
// library/src/main/ets/pages/Index.ets
// 正确用例
Image($r('app.media.example'))
  .id('example')
  .borderRadius('48px')
// 错误用例
Image("../../resources/base/media/example.png")
  .id('example')
  .borderRadius('48px')

 

3.4.5 导出HSP中的资源

跨包访问HSP内资源时,推荐实现一个资源管理类,以封装对外导出的资源。采用这种方式,具有如下优点:
HSP开发者可以控制自己需要导出的资源,不需要对外暴露的资源可以不用导出。
使用方无须感知HSP内部的资源名称。当HSP内部的资源名称发生变化时,也不需要使用方跟着修改。
其具体实现如下:
将需要对外提供的资源封装为一个资源管理类:
// library/src/main/ets/ResManager.ets
export class ResManager{
  static getPic(): Resource{
    return $r('app.media.pic');
  }
  static getDesc(): Resource{
    return $r('app.string.shared_desc');
  }
}
对外暴露的接口,需要在入口文件index.ets中声明:
// library/index.ets
export { ResManager } from './src/main/ets/ResManager';

 

3.5 使用

介绍如何引用HSP中的接口,以及如何通过页面路由实现HSP的pages页面跳转与返回。

3.5.1 引用HSP中的接口

要使用HSP中的接口,首先需要在使用方的oh-package.json5中配置对它的依赖,详见引用动态共享包
依赖配置成功后,就可以像使用HAR一样调用HSP的对外接口了。例如,上面的library已经导出了下面这些接口:
// library/index.ets
export { Log, add, minus } from './src/main/ets/utils/test';
export { MyTitleBar } from './src/main/ets/components/MyTitleBar';
export { ResManager } from './src/main/ets/ResManager';
export { nativeMulti } from './src/main/ets/utils/nativeTest';
在使用方的代码中,可以这样使用:
// entry/src/main/ets/pages/index.ets
import { Log, add, MyTitleBar, ResManager, nativeMulti } from 'library';
import { BusinessError } from '@ohos.base';
import Logger from '../logger/Logger';
import router from '@ohos.router';
const TAG = 'Index';
@Entry
@Component
struct Index {
  @State message: string = '';
  build() {
    Column() {
      List() {
        ListItem() {
          MyTitleBar()
        }
        .margin({ left: '35px', top: '32px' })
        ListItem() {
          Text(this.message)
            .fontFamily('HarmonyHeiTi')
            .fontSize(18)
            .textAlign(TextAlign.Start)
            .width('100%')
            .fontWeight(FontWeight.Bold)
        }
        .width('685px')
        .margin({ top: 30, bottom: 10 })
        ListItem() {
          // ResManager返回的Resource对象,可以传给组件直接使用,也可以从中取出资源来使用
          Image(ResManager.getPic())
            .id('image')
            .borderRadius('48px')
        }
        .width('685px')
        .margin({ top: 10, bottom: 10 })
        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
        ListItem() {
          Text($r('app.string.add'))
            .fontSize(18)
            .textAlign(TextAlign.Start)
            .width('100%')
            .fontWeight(500)
            .height('100%')
        }
        .id('add')
        .borderRadius(24)
        .width('685px')
        .height('84px')
        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
        .margin({ top: 10, bottom: 10 })
        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
        .onClick(() => {
          Log.info('add button click!');
          this.message = 'result: ' + add(1, 2);
        })
        ListItem() {
          Text($r('app.string.get_string_value'))
            .fontSize(18)
            .textAlign(TextAlign.Start)
            .width('100%')
            .fontWeight(500)
            .height('100%')
        }
        .id('getStringValue')
        .borderRadius(24)
        .width('685px')
        .height('84px')
        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
        .margin({ top: 10, bottom: 10 })
        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
        .onClick(() => {
          // 先通过当前上下文获取hsp模块的上下文,再获取hsp模块的resourceManager,然后再调用resourceManager的接口获取资源
          getContext()
            .createModuleContext('library')
            .resourceManager
            .getStringValue(ResManager.getDesc())
            .then(value => {
              Logger.info(TAG, `getStringValue is ${value}`);
              this.message = 'getStringValue is ' + value;
            })
            .catch((err: BusinessError) => {
              Logger.info(TAG, `getStringValue promise error is ${err}`);
            });
        })
        ListItem() {
          Text($r('app.string.native_multi'))
            .fontSize(18)
            .textAlign(TextAlign.Start)
            .width('100%')
            .fontWeight(500)
            .height('100%')
        }
        .id('nativeMulti')
        .borderRadius(24)
        .width('685px')
        .height('84px')
        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
        .margin({ top: 10, bottom: 10 })
        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
        .onClick(() => {
          Log.info('nativeMulti button click!');
          this.message = 'result: ' + nativeMulti(3, 4);
        })
      }
      .alignListItem(ListItemAlign.Center)
    }
    .width('100%')
    .backgroundColor($r('app.color.page_background'))
    .height('100%')
  }
}

 

3.5.2 页面路由跳转

若开发者想在entry模块中,添加一个按钮跳转至library模块中的menu页面(路径为:library/src/main/ets/pages/menu.ets),那么可以在使用方的代码(entry模块下的Index.ets,路径为:entry/src/main/ets/pages/Index.ets)里这样使用:
import { Log, add, MyTitleBar, ResManager, nativeMulti } from 'library';
import { BusinessError } from '@ohos.base';
import Logger from '../logger/Logger';
import router from '@ohos.router';
const TAG = 'Index';
@Entry
@Component
struct Index {
  @State message: string = '';
  build() {
    Column() {
      List() {
        ListItem() {
          Text($r('app.string.click_to_menu'))
            .fontSize(18)
            .textAlign(TextAlign.Start)
            .width('100%')
            .fontWeight(500)
            .height('100%')
        }
        .id('clickToMenu')
        .borderRadius(24)
        .width('685px')
        .height('84px')
        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
        .margin({ top: 10, bottom: 10 })
        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
        .onClick(() => {
          router.pushUrl({
            url: '@bundle:com.samples.hspsample/library/ets/pages/Menu'
          }).then(() => {
            console.log('push page success');
            Logger.info(TAG, 'push page success');
          }).catch((err: BusinessError) => {
            Logger.error(TAG, `pushUrl failed, code is ${err.code}, message is ${err.message}`);
          })
        })
      }
      .alignListItem(ListItemAlign.Center)
    }
    .width('100%')
    .backgroundColor($r('app.color.page_background'))
    .height('100%')
  }
}
其中router.pushUrl方法的入参中url的内容为:
'@bundle:com.samples.hspsample/library/ets/pages/Menu'
url内容的模板为:
'@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)'

3.5.3 页面路由返回

如果当前处于HSP中的页面,需要返回之前的页面时,可以使用router.back方法,但是返回的页面必须是当前页面跳转路径上的页面。
import router from '@ohos.router';
@Entry
@Component
struct Index3 { // 路径为:`library/src/main/ets/pages/Back.ets
  @State message: string = 'HSP back page';
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontFamily('HarmonyHeiTi')
          .fontWeight(FontWeight.Bold)
          .fontSize(32)
          .fontColor($r('app.color.text_color'))
          .margin({ top: '32px' })
          .width('624px')
        Button($r('app.string.back_to_HAP'))
          .id('backToHAP')
          .fontFamily('HarmonyHeiTi')
          .height(48)
          .width('624px')
          .margin({ top: 550 })
          .type(ButtonType.Capsule)
          .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
          .backgroundColor($r('app.color.button_background'))
          .fontColor($r('sys.color.ohos_id_color_foreground_contrary'))
          .fontSize($r('sys.float.ohos_id_text_size_button1'))
            // 绑定点击事件
          .onClick(() => {
            router.back({ // 返回HAP的页面
              url: 'pages/Index' // 路径为:`entry/src/main/ets/pages/Index.ets`
            })
          })
        Button($r('app.string.back_to_HSP'))
          .id('backToHSP')
          .fontFamily('HarmonyHeiTi')
          .height(48)
          .width('624px')
          .margin({ top: '4%' , bottom: '6%' })
          .type(ButtonType.Capsule)
          .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
          .backgroundColor($r('app.color.button_background'))
          .fontColor($r('sys.color.ohos_id_color_foreground_contrary'))
          .fontSize($r('sys.float.ohos_id_text_size_button1'))
            // 绑定点击事件
          .onClick(() => {
            router.back({ // 返回HSP的页面
              url: '@bundle:com.samples.hspsample/library/ets/pages/Menu' //路径为:`library/src/main/ets/pages/Menu.ets
            })
          })
      }
      .width('100%')
    }
    .backgroundColor($r('app.color.page_background'))
    .height('100%')
  }
}
页面返回router.back方法的入参中url说明:
如果从HSP页面返回HAP页面,url的内容为:
'pages/Index'
url内容的模板为:
'页面所在的文件名(不加.ets后缀)'
如果从HSP1的页面跳到HSP2的页面后,需要返回到HSP1的页面,url的内容为:
'@bundle:com.samples.hspsample/library/ets/pages/Menu'
url内容的模板为:
'@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)'

 

3.6 集成态HSP

3.6.1 配置HSP模块为集成态HSP

修改模块级构建配置文件build-profile.json5,设置配置项integratedHsp为true,指定构建的HSP模块为集成态HSP。
{
  "apiType": "stageMode", 
  "buildOption": {
    "arkOptions": {
      "integratedHsp": true
    }
  }
}

3.6.2 配置工程使用标准化的OHMUrl格式

集成态HSP需要使用标准化的OHMUrl格式,修改工程级构建配置文件build-profile.json5,设置配置项useNormalizedOHMUrl为true,指定工程使用标准化的OHMUrl格式。
{
  "app": {
    "products": {
      "name": "default",
      "signingConfig": "default",
      "compatibleSdkVersion": "5.0.0(12)",
      "runtimeOS": "HarmonyOS",
      "buildOption": {
        "strictMode": {
          "useNormalizedOHMUrl": true
        }
      }
    }
  }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

posted @ 2024-07-04 00:59  上九品  阅读(278)  评论(0)    收藏  举报