• 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一起安装/运行。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> 循环依赖:例如有三个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### 导出ts类和方法
80通过`export`导出ts类和方法,例如:
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 '@ohos.base';
188import router from '@ohos.router';
189
190const TAG = 'Index';
191
192@Entry
193@Component
194struct Index {
195  @State message: string = '';
196
197  build() {
198    Column() {
199      List() {
200        ListItem() {
201          MyTitleBar()
202        }
203        .margin({ left: '35px', top: '32px' })
204
205        ListItem() {
206          Text(this.message)
207            .fontFamily('HarmonyHeiTi')
208            .fontSize(18)
209            .textAlign(TextAlign.Start)
210            .width('100%')
211            .fontWeight(FontWeight.Bold)
212        }
213        .width('685px')
214        .margin({ top: 30, bottom: 10 })
215
216        ListItem() {
217          // ResManager返回的Resource对象,可以传给组件直接使用,也可以从中取出资源来使用
218          Image(ResManager.getPic())
219            .id('image')
220            .borderRadius('48px')
221        }
222        .width('685px')
223        .margin({ top: 10, bottom: 10 })
224        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
225
226        ListItem() {
227          Text($r('app.string.add'))
228            .fontSize(18)
229            .textAlign(TextAlign.Start)
230            .width('100%')
231            .fontWeight(500)
232            .height('100%')
233        }
234        .id('add')
235        .borderRadius(24)
236        .width('685px')
237        .height('84px')
238        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
239        .margin({ top: 10, bottom: 10 })
240        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
241        .onClick(() => {
242          Log.info('add button click!');
243          this.message = 'result: ' + add(1, 2);
244        })
245
246        ListItem() {
247          Text($r('app.string.get_string_value'))
248            .fontSize(18)
249            .textAlign(TextAlign.Start)
250            .width('100%')
251            .fontWeight(500)
252            .height('100%')
253        }
254        .id('getStringValue')
255        .borderRadius(24)
256        .width('685px')
257        .height('84px')
258        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
259        .margin({ top: 10, bottom: 10 })
260        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
261        .onClick(() => {
262          // 先通过当前上下文获取hsp模块的上下文,再获取hsp模块的resourceManager,然后再调用resourceManager的接口获取资源
263          getContext()
264            .createModuleContext('library')
265            .resourceManager
266            .getStringValue(ResManager.getDesc())
267            .then(value => {
268              console.log('getStringValue is ' + value);
269              this.message = 'getStringValue is ' + value;
270            })
271            .catch((err: BusinessError) => {
272              console.error('getStringValue promise error is ' + err);
273            });
274        })
275
276        ListItem() {
277          Text($r('app.string.native_multi'))
278            .fontSize(18)
279            .textAlign(TextAlign.Start)
280            .width('100%')
281            .fontWeight(500)
282            .height('100%')
283        }
284        .id('nativeMulti')
285        .borderRadius(24)
286        .width('685px')
287        .height('84px')
288        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
289        .margin({ top: 10, bottom: 10 })
290        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
291        .onClick(() => {
292          Log.info('nativeMulti button click!');
293          this.message = 'result: ' + nativeMulti(3, 4);
294        })
295      }
296      .alignListItem(ListItemAlign.Center)
297    }
298    .width('100%')
299    .backgroundColor($r('app.color.page_background'))
300    .height('100%')
301  }
302}
303```
304
305### 页面路由跳转
306
307若开发者想在entry模块中,添加一个按钮跳转至library模块中的menu页面(路径为:`library/src/main/ets/pages/menu.ets`),那么可以在使用方的代码(entry模块下的Index.ets,路径为:`entry/src/main/ets/pages/Index.ets`)里这样使用:
308```ts
309import { Log, add, MyTitleBar, ResManager, nativeMulti } from 'library';
310import { BusinessError } from '@ohos.base';
311import router from '@ohos.router';
312
313const TAG = 'Index';
314
315@Entry
316@Component
317struct Index {
318  @State message: string = '';
319
320  build() {
321    Column() {
322      List() {
323        ListItem() {
324          Text($r('app.string.click_to_menu'))
325            .fontSize(18)
326            .textAlign(TextAlign.Start)
327            .width('100%')
328            .fontWeight(500)
329            .height('100%')
330        }
331        .id('clickToMenu')
332        .borderRadius(24)
333        .width('685px')
334        .height('84px')
335        .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary'))
336        .margin({ top: 10, bottom: 10 })
337        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
338        .onClick(() => {
339          router.pushUrl({
340            url: '@bundle:com.samples.hspsample/library/ets/pages/Menu'
341          }).then(() => {
342            console.log('push page success');
343          }).catch((err: BusinessError) => {
344            console.error('pushUrl failed, code is' + err.code + ', message is' + err.message);
345          })
346        })
347      }
348      .alignListItem(ListItemAlign.Center)
349    }
350    .width('100%')
351    .backgroundColor($r('app.color.page_background'))
352    .height('100%')
353  }
354}
355```
356其中`router.pushUrl`方法的入参中`url`的内容为:
357```ets
358'@bundle:com.samples.hspsample/library/ets/pages/Menu'
359```
360`url`内容的模板为:
361```ets
362'@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)'
363```
364### 页面路由返回
365如果当前处于HSP中的页面,需要返回之前的页面时,可以使用`router.back`方法,但是返回的页面必须是当前页面跳转路径上的页面。
366```ts
367import router from '@ohos.router';
368
369@Entry
370@Component
371struct Index3 { // 路径为:`library/src/main/ets/pages/Back.ets
372  @State message: string = 'HSP back page';
373
374  build() {
375    Row() {
376      Column() {
377        Text(this.message)
378          .fontFamily('HarmonyHeiTi')
379          .fontWeight(FontWeight.Bold)
380          .fontSize(32)
381          .fontColor($r('app.color.text_color'))
382          .margin({ top: '32px' })
383          .width('624px')
384
385        Button($r('app.string.back_to_HAP'))
386          .id('backToHAP')
387          .fontFamily('HarmonyHeiTi')
388          .height(48)
389          .width('624px')
390          .margin({ top: 550 })
391          .type(ButtonType.Capsule)
392          .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
393          .backgroundColor($r('app.color.button_background'))
394          .fontColor($r('sys.color.ohos_id_color_foreground_contrary'))
395          .fontSize($r('sys.float.ohos_id_text_size_button1'))
396            // 绑定点击事件
397          .onClick(() => {
398            router.back({ //  返回HAP的页面
399              url: 'pages/Index' // 路径为:`entry/src/main/ets/pages/Index.ets`
400            })
401          })
402
403        Button($r('app.string.back_to_HSP'))
404          .id('backToHSP')
405          .fontFamily('HarmonyHeiTi')
406          .height(48)
407          .width('624px')
408          .margin({ top: '4%' , bottom: '6%' })
409          .type(ButtonType.Capsule)
410          .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
411          .backgroundColor($r('app.color.button_background'))
412          .fontColor($r('sys.color.ohos_id_color_foreground_contrary'))
413          .fontSize($r('sys.float.ohos_id_text_size_button1'))
414            // 绑定点击事件
415          .onClick(() => {
416            router.back({ //  返回HSP的页面
417              url: '@bundle:com.samples.hspsample/library/ets/pages/Menu' //路径为:`library/src/main/ets/pages/Menu.ets
418            })
419          })
420      }
421      .width('100%')
422    }
423    .backgroundColor($r('app.color.page_background'))
424    .height('100%')
425  }
426}
427```
428
429页面返回`router.back`方法的入参中`url`说明:
430
431* 如果从HSP页面返回HAP页面,url的内容为:
432
433    ```ets
434    'pages/Index'
435    ```
436    `url`内容的模板为:
437    ```ets
438    '页面所在的文件名(不加.ets后缀)'
439    ```
440
441* 如果从HSP1的页面跳到HSP2的页面后,需要返回到HSP1的页面,url的内容为:
442
443    ```ets
444    '@bundle:com.samples.hspsample/library/ets/pages/Menu'
445    ```
446    `url`内容的模板为:
447    ```ets
448    '@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)'
449    ```
450
451