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