• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# HSP
2
3HSP(Harmony Shared Package)是动态共享包,可以包含代码、C++库、资源和配置文件,通过HSP可以实现代码和资源的共享。HSP不支持独立发布,而是跟随其宿主应用的APP包一起发布,与宿主应用同进程,具有相同的包名和生命周期。
4> **说明:**
5>
6> 应用内HSP:在编译过程中与应用包名(bundleName)强耦合,只能给某个特定的应用使用。
7>
8> [集成态HSP](integrated-hsp.md):构建、发布过程中,不与特定的应用包名耦合;使用时,工具链支持自动将集成态HSP的包名替换成宿主应用包名,并且会重新签名生成一个新的HSP包,作为宿主应用的安装包,这个新的HSP也属于应用内HSP。
9
10## 使用场景
11- 多个HAP/HSP共用的代码和资源放在同一个HSP中,可以提高代码、资源的可重用性和可维护性,同时编译打包时也只保留一份HSP代码和资源,能够有效控制应用包大小。
12
13- HSP在运行时按需加载,有助于提升应用性能。
14
15- 同一个组织内部的多个应用之间,可以使用集成态HSP实现代码和资源的共享。
16
17## 约束限制
18
19- HSP不支持在设备上单独安装/运行,需要与依赖该HSP的HAP一起安装/运行。HAP的版本号须大于等于HSP版本号。
20- HSP支持在配置文件中声明[ExtensionAbility](../application-models/extensionability-overview.md)组件和[UIAbility](../application-models/uiability-overview.md)组件,但不支持具有入口能力的ExtensionAbility或UIAbility(即skill标签配置了entity.system.homeohos.want.action.home)。
21- HSP可以依赖其他HAR或HSP,但不支持循环依赖,也不支持依赖传递。
22
23> **说明:**
24>
25> 循环依赖:例如有三个HSP,HSP-A、HSP-B和HSP-C,循环依赖指HSP-A依赖HSP-B,HSP-B依赖HSP-C,HSP-C又依赖HSP-A。
26>
27> 依赖传递:例如有三个HSP,HSP-A、HSP-B和HSP-C,依赖关系是HSP-A依赖HSP-B,HSP-B依赖HSP-C。不支持传递依赖指HSP-A可以使用HSP-B的方法和组件,但是HSP-A不能直接使用HSP-C的方法和组件。
28
29
30## 创建
31通过DevEco Studio创建一个HSP模块,详见[创建HSP模块](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-hsp-V13#section7717162312546),我们以创建一个名为`library`的HSP模块为例。基本的工程目录结构如下:
32```
33MyApplication
34├── library
35│   ├── src
36│   │   └── main
37│   │       ├── ets
38│   │       │   └── pages
39│   │       │       └── index.ets     //模块library的页面文件
40│   │       ├── resources             //模块library的资源目录
41│   │       └── module.json5          //模块library的配置文件
42│   ├── oh-package.json5              //模块级
43│   ├── index.ets                     //入口文件index.ets
44│   └── build-profile.json5           //模块级
45└── build-profile.json5               //工程级
46```
47
48## 开发
49
50
51介绍如何导出HSP的ArkUI组件、接口、资源,供应用内的其他HAP/HSP引用。
52
53### 导出ArkUI组件
54ArkUI组件可以通过`export`导出,例如:
55```ts
56// library/src/main/ets/components/MyTitleBar.ets
57@Component
58export struct MyTitleBar {
59  build() {
60    Row() {
61      Text($r('app.string.library_title'))
62        .id('library')
63        .fontFamily('HarmonyHeiTi')
64        .fontWeight(FontWeight.Bold)
65        .fontSize(32)
66        .fontColor($r('app.color.text_color'))
67    }
68    .width('100%')
69  }
70}
71```
72对外暴露的接口,需要在入口文件`index.ets`中声明:
73```ts
74// library/index.ets
75export { MyTitleBar } from './src/main/ets/components/MyTitleBar';
76```
77
78
79### 导出类和方法
80通过`export`导出类和方法,例如:
81```ts
82// library/src/main/ets/utils/test.ets
83export class Log {
84  static info(msg: string): void {
85    console.info(msg);
86  }
87}
88
89export function add(a: number, b: number): number {
90  return a + b;
91}
92
93export function minus(a: number, b: number): number {
94  return a - b;
95}
96```
97对外暴露的接口,需要在入口文件`index.ets`中声明:
98```ts
99// library/index.ets
100export { Log, add, minus } from './src/main/ets/utils/test';
101```
102### 导出native方法
103在HSP中也可以包含C++编写的`so`。对于`so`中的`native`方法,HSP通过间接的方式导出,以导出`liblibrary.so`的乘法接口`multi`为例:
104```ts
105// library/src/main/ets/utils/nativeTest.ets
106import native from 'liblibrary.so';
107
108export function nativeMulti(a: number, b: number): number {
109  let result: number = native.multi(a, b);
110  return result;
111}
112```
113
114对外暴露的接口,需要在入口文件`index.ets`中声明:
115```ts
116// library/index.ets
117export { nativeMulti } from './src/main/ets/utils/nativeTest';
118```
119
120### 通过$r访问HSP中的资源
121在组件中,经常需要使用字符串、图片等资源。HSP中的组件需要使用资源时,一般将其所用资源放在HSP包内,而非放在HSP的使用方处,以符合高内聚低耦合的原则。
122
123在工程中,常通过`$r`/`$rawfile`的形式引用应用资源。可以用`$r`/`$rawfile`访问本模块`resources`目录下的资源,如访问`resources`目录下定义的图片`src/main/resources/base/media/example.png`时,可以用`$r("app.media.example")`。有关`$r`/`$rawfile`的详细使用方式,请参阅文档[资源分类与访问](./resource-categories-and-access.md)中“资源访问-应用资源”小节。
124
125不推荐使用相对路径的方式,容易引用错误路径。例如:
126当要引用上述同一图片资源时,在HSP模块中使用`Image("../../resources/base/media/example.png")`,实际上该`Image`组件访问的是HSP调用方(如`entry`)下的资源`entry/src/main/resources/base/media/example.png`。
127
128```ts
129// library/src/main/ets/pages/Index.ets
130// 正确用例
131Image($r('app.media.example'))
132  .id('example')
133  .borderRadius('48px')
134// 错误用例
135Image("../../resources/base/media/example.png")
136  .id('example')
137  .borderRadius('48px')
138```
139
140### 导出HSP中的资源
141跨包访问HSP内资源时,推荐实现一个资源管理类,以封装对外导出的资源。采用这种方式,具有如下优点:
142- HSP开发者可以控制自己需要导出的资源,不需要对外暴露的资源可以不用导出。
143- 使用方无须感知HSP内部的资源名称。当HSP内部的资源名称发生变化时,也不需要使用方跟着修改。
144
145其具体实现如下:
146
147将需要对外提供的资源封装为一个资源管理类:
148```ts
149// library/src/main/ets/ResManager.ets
150export class ResManager{
151  static getPic(): Resource{
152    return $r('app.media.pic');
153  }
154  static getDesc(): Resource{
155    return $r('app.string.shared_desc');
156  }
157}
158```
159
160对外暴露的接口,需要在入口文件`index.ets`中声明:
161```ts
162// library/index.ets
163export { ResManager } from './src/main/ets/ResManager';
164```
165
166
167
168## 使用
169
170介绍如何引用HSP中的接口,以及如何通过页面路由实现HSP的pages页面跳转与返回。
171
172### 引用HSP中的接口
173要使用HSP中的接口,首先需要在使用方的oh-package.json5中配置对它的依赖,详见[引用动态共享包](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-har-import-V13)174依赖配置成功后,就可以像使用HAR一样调用HSP的对外接口了。例如,上面的library已经导出了下面这些接口:
175
176```ts
177// library/index.ets
178export { Log, add, minus } from './src/main/ets/utils/test';
179export { MyTitleBar } from './src/main/ets/components/MyTitleBar';
180export { ResManager } from './src/main/ets/ResManager';
181export { nativeMulti } from './src/main/ets/utils/nativeTest';
182```
183在使用方的代码中,可以这样使用:
184```ts
185// entry/src/main/ets/pages/index.ets
186import { Log, add, MyTitleBar, ResManager, nativeMulti } from 'library';
187import { BusinessError } from "@kit.BasicServicesKit";
188
189const TAG = 'Index';
190
191@Entry
192@Component
193struct Index {
194  @State message: string = '';
195
196  build() {
197    Column() {
198      List() {
199        ListItem() {
200          MyTitleBar()
201        }
202        .margin({ left: '35px', top: '32px' })
203
204        ListItem() {
205          Text(this.message)
206            .fontFamily('HarmonyHeiTi')
207            .fontSize(18)
208            .textAlign(TextAlign.Start)
209            .width('100%')
210            .fontWeight(FontWeight.Bold)
211        }
212        .width('685px')
213        .margin({ top: 30, bottom: 10 })
214
215        ListItem() {
216          // ResManager返回的Resource对象,可以传给组件直接使用,也可以从中取出资源来使用
217          Image(ResManager.getPic())
218            .id('image')
219            .borderRadius('48px')
220        }
221        .width('685px')
222        .margin({ top: 10, bottom: 10 })
223        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
224
225        ListItem() {
226          Text($r('app.string.add'))
227            .fontSize(18)
228            .textAlign(TextAlign.Start)
229            .width('100%')
230            .fontWeight(500)
231            .height('100%')
232        }
233        .id('add')
234        .borderRadius(24)
235        .width('685px')
236        .height('84px')
237        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
238        .margin({ top: 10, bottom: 10 })
239        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
240        .onClick(() => {
241          Log.info('add button click!');
242          this.message = 'result: ' + add(1, 2);
243        })
244
245        ListItem() {
246          Text($r('app.string.get_string_value'))
247            .fontSize(18)
248            .textAlign(TextAlign.Start)
249            .width('100%')
250            .fontWeight(500)
251            .height('100%')
252        }
253        .id('getStringValue')
254        .borderRadius(24)
255        .width('685px')
256        .height('84px')
257        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
258        .margin({ top: 10, bottom: 10 })
259        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
260        .onClick(() => {
261          // 先通过当前上下文获取hsp模块的上下文,再获取hsp模块的resourceManager,然后再调用resourceManager的接口获取资源
262          this.getUIContext()?.getHostContext()?.resourceManager.getStringValue(ResManager.getDesc())
263            .then(value => {
264              console.log('getStringValue is ' + value);
265              this.message = 'getStringValue is ' + value;
266            })
267            .catch((err: BusinessError) => {
268              console.error('getStringValue promise error is ' + err);
269            });
270        })
271
272        ListItem() {
273          Text($r('app.string.native_multi'))
274            .fontSize(18)
275            .textAlign(TextAlign.Start)
276            .width('100%')
277            .fontWeight(500)
278            .height('100%')
279        }
280        .id('nativeMulti')
281        .borderRadius(24)
282        .width('685px')
283        .height('84px')
284        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
285        .margin({ top: 10, bottom: 10 })
286        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
287        .onClick(() => {
288          Log.info('nativeMulti button click!');
289          this.message = 'result: ' + nativeMulti(3, 4);
290        })
291      }
292      .alignListItem(ListItemAlign.Center)
293    }
294    .width('100%')
295    .backgroundColor($r('app.color.page_background'))
296    .height('100%')
297  }
298}
299```
300
301### 页面跳转和返回
302
303开发者想在entry模块中,添加一个按钮跳转至library模块中的menu页面(路径为:`library/src/main/ets/pages/library_menu.ets`),那么可以在使用方的代码(entry模块下的Index.ets,路径为:`entry/src/main/ets/pages/Index.ets`)里这样使用:
304```ts
305// entry/src/main/ets/pages/Index.ets
306
307@Entry
308@Component
309struct Index {
310  @State message: string = '';
311  pathStack: NavPathStack = new NavPathStack();
312
313  build() {
314    Navigation(this.pathStack) {
315      Column() {
316        List() {
317          ListItem() {
318            Text($r('app.string.click_to_menu'))
319              .fontSize(18)
320              .textAlign(TextAlign.Start)
321              .width('100%')
322              .fontWeight(500)
323              .height('100%')
324          }
325          .id('clickToMenu')
326          .borderRadius(24)
327          .width('685px')
328          .height('84px')
329          .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
330          .margin({ top: 10, bottom: 10 })
331          .padding({
332            left: 12,
333            right: 12,
334            top: 4,
335            bottom: 4
336          })
337          .onClick(() => {
338            this.pathStack.pushPathByName('library_menu', null)
339          })
340        }
341        .alignListItem(ListItemAlign.Center)
342      }
343      .width('100%')
344      .backgroundColor($r('app.color.page_background'))
345      .height('100%')
346    }.title("Navigation_index")
347    .mode(NavigationMode.Stack)
348  }
349}
350```
351
352在library下新增page文件(library/src/main/ets/pages/library_menu.ets),其中'back_to_index'的按钮返回上一页。
353```
354// library/src/main/ets/pages/library_menu.ets
355@Builder
356export function PageOneBuilder() {
357  Library_Menu()
358}
359
360@Entry
361@Component
362export struct Library_Menu {
363  @State message: string = 'Hello World';
364  pathStack: NavPathStack = new NavPathStack();
365
366  build() {
367    NavDestination() {
368      Row() {
369        Column() {
370          Text(this.message)
371            .fontSize($r('app.float.page_text_font_size'))
372            .fontWeight(FontWeight.Bold)
373            .onClick(() => {
374              this.message = 'Welcome';
375            })
376          Button("back_to_index").fontSize(50).onClick(() => {
377            this.pathStack.pop();
378          })
379        }
380        .width('100%')
381      }
382      .height('100%')
383    }.title('Library_Menu')
384    .onReady((context: NavDestinationContext) => {
385      this.pathStack = context.pathStack
386    })
387  }
388}
389```
390
391需要在library模块下新增route_map.json文件(library/src/main/resources/base/profile/route_map.json)。
392```
393{
394  "routerMap": [
395    {
396      "name": "library_menu",
397      "pageSourceFile": "src/main/ets/pages/library_menu.ets",
398      "buildFunction": "PageOneBuilder",
399      "data": {
400        "description": "this is library_menu"
401      }
402    }
403  ]
404}
405```
406
407在library模块下的配置文件(library/src/main/module.json5)中配置json文件。
408```
409{
410  "module": {
411    "name": "library",
412    "type": "shared",
413    "description": "$string:shared_desc",
414    "deviceTypes": [
415      "phone",
416      "tablet",
417      "2in1"
418    ],
419    "deliveryWithInstall": true,
420    "pages": "$profile:main_pages",
421    "routerMap": "$profile:route_map" //新增
422  }
423}
424```
425
426页面跳转和页面返回都使用了Navigation的特性,详情参考[Navigation跳转](../ui/arkts-navigation-navigation.md#路由操作)。