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 ```