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