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