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