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