问题1:如何跳转到共享包中的指定页面
在使用方通过router.pushUrl方法传递正确url地址信息进行跳转。其中url地址模板内容为:
'@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)'
问题2:HSP/HAR包中如何引用外部编译的so库文件
- libxxx.so库文件放入HAR或HSP的libs/arm64-v8a目录。设备类型不同时,需添加对应子目录。新版的arm64为libs/arm64-v8a,老版的arm64为libs/armeabi-v7a,x86模拟器为libs/x86_64。
- 在src/main/cpp/CMakeLists.txt文件中链接so库文件
// CMakeLists.txt链接so库文件
target_link_libraries(entry PUBLIC libxxx)
问题3:业务模块HAR如何获取宿主HAP的数据
把需要获取的信息当做参数传入,HAR提供方法给HAP,HAP调用HAR的接口把需要的HAP中的信息传入到HAR里面,这样HAR可以获取到HAP里面的数据。
问题4:如何通过路由跳转到一个只有页面没有UIAbility的模块
问题现象
现在有模块A,B。A模块为Entry类型,其中有个UIAbility,有pages。B模块是Feature类型,没有UIAbility,但是有pages。整个App只想使用一个UIAbility的情况下,A模块怎么跳转到B模块的页面。
解决措施
- Module分为“Ability”和“Library”两种类型:“Ability”类型的Module对应于编译后的HAP;“Library”类型的Module对应于HAR或者HSP
- HAP可分为Entry和Feature两种类型:Entry类型的HAP是应用的主模块,通常用于实现应用的入口界面、入口图标、主特性功能等;Feature类型的HAP是应用的动态特性模块,通常用于实现应用的特性功能,可以配置成按需下载安装。
- HAR静态共享包,和HSP动态共享包,都是为了实现代码和资源的共享,都可以包含代码、C++库、资源和配置文件。其中HAR不支持在配置文件中声明pages页面,HSP支持配置pages页面。
对于没有UIAbility但依然提供可跳转页面的模块,应该考虑使用Library类型的HSP实现相应的功能,从UIAbility跳转HSP中的页面可参考下列方式:
import { router } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
@State message: string = '跳转到HSP页面';
build() {
Row() {
Column() {
Button() {
Text(this.message)
.fontSize(24)
}
.onClick(() => {
router.pushUrl({
url: '@bundle:com.example.gotohsppage/library/ets/pages/Index'
}).then(() => {
console.info("Go to hSP page success.");
}).catch((err: BusinessError) => {
console.error(`Go to hSP page failed, code is ${err.code}, message is ${err.message}.`);
})
})
.width(200)
}
.width('100%')
}
.height('100%')
}
}
其中router.pushUrl方法的入参中url的内容模板为:
'@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)'
问题5:如何查询应用包的名称、供应商、版本号、版本文本、安装时间、更新时间描述信息
首先通过bundleManager.getBundleInfoForSelf()接口获取应用包的名称、供应商、版本号、版本文本、安装时间、更新时间描述信息。具体可参考示例代码:
import { bundleManager } from '@kit.AbilityKit';
// 申请获取bundleInfo和applicationInfo
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
try {
bundleManager.getBundleInfoForSelf(bundleFlags, (err, data) => {
// 获取应用自身的bundleName
const bundleName = data.name;
// 获取应用的版本号(versionCode)
const versionCode = data.versionCode;
// 获取应用的版本名(versionName)
const versionName = data.versionName;
if (err) {
console.error(`getBundleInfoForSelf failed: ${err.message}`);
} else {
console.info(`get bundleName successfully: ${bundleName}`);
console.info(`get versionCode successfully: ${versionCode}`);
console.info(`get versionName successfully: ${versionName}`);
console.info(`getBundleInfoForSelf successfully: ${JSON.stringify(data)}`);
}
});
} catch (err) {
console.error(`getBundleInfoForSelf failed: ${JSON.stringify(err)}`);
}
问题6:通过resourceManager.getStringResource接口获取HSP资源文件报“Resource id invalid”错误
问题现象
通过this.resourceManager.getStringResource($r('app.string.PlayCount').id)获取hsp资源文件报错:
Error message:Resource id invalid
Error code:9001001
SourceCode:returnResource = this.context.resourceManager.getStringSync(id);
可能原因
未创建对应的context,传入的是一个不存在的id值。
解决措施
根据模块名创建上下文Context,然后通过getStringByNameSync获取指定资源名称对应的字符串,具体请参考示例代码:
import { common, application } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
@Entry
@Component
struct Index {
private context = getContext(this) as common.UIAbilityContext;
build() {
Column() {
Button()
.onClick(() => {
// 根据模块名创建上下文Context
let moduleName: string = 'library';
application.createModuleContext(this.context, moduleName)
.then((data: common.Context) => {
console.info(`CreateModuleContext success, data: ${JSON.stringify(data)}`);
if (data !== null) {
promptAction.showToast({
message: ('成功获取Context')
});
}
// 然后通过getStringByNameSync获取指定资源名称对应的字符串
try {
let str = data.resourceManager.getStringByNameSync('shared_desc');
console.info(`getStringByNameSync, data: ${JSON.stringify(str)}`);
} catch (error) {
let code = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`getStringByNameSync failed, error code: ${code}, message: ${message}.`);
}
})
.catch((err: BusinessError) => {
console.error(`CeateMudleContext failed, err code:${err.code}, err msg: ${err.message}`);
});
})
}
}
}
问题7:如何跨模块访问HSP/HAR包中resources目录的element目录、media目录和rawfile目录资源文件
可以通过以下几种方式访问HSP/HAR里面的资源:
- 通过createModuleContext(moduleName)接口创建同应用中不同module的上下文,获取resourceManager对象后,调用不同接口访问不同资源。
例如:getContext.createModuleContext(moduleName).resourceManager.getStringByNameSync('app.string.xxx')。
- 通过"rawfile"引用资源,例如:Text($r('[hsp].string.test_string')),其中“hsp”为HSP包模块/HAR包模块的名称。
说明
在HarmonyOS NEXT Developer Beta1及以上版本支持直接通过"rawfile"引用HSP或者HAR包的资源。
- 通过HSP包下实现一个资源管理类,以封装对外导出的资源。
HAP中访问HAR包中resources目录的rawfile原始文件资源。
例如在HAR包(不妨设名称为library)的“\library\src\main\resources\rawfile”目录中有“iconHar.png”文件。
在HAR包中将rawfile目录下的“iconHar.png”文件封装成一个方法,例如在“\library\src\main\ets\components\mainpage\MainPage.ets”文件中封装一个方法。
export function rawFileIconHarPng() {
return $rawfile('iconHar.png');
}
在“\library\Index.ets”文件中导出rawFileIconHarPng()方法。
export { rawFileIconHarPng } from './src/main/ets/components/mainpage/MainPage';
在HAP中的“entry\src\main\ets\pages\Index.ets”文件中通过导入rawFileIconHarPng()方法后直接使用即可。
import { rawFileIconHarPng } from 'library';
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Image(rawFileIconHarPng())
.width(100)
.height(100)
}
.width('100%')
}
.height('100%')
}
}
问题8:如何获取当前HAP的BundleName
通过bundleManager模块的getBundleInfoForSelf接口获取所有信息:
GET_BUNDLE_INFO_DEFAULT:接口默认的参数,返回结果的name字段对应BundleName。
GET_BUNDLE_INFO_WITH_APPLICATION:除基本字段外,能够额外获取到ApplicationInfo字段,ApplicationInfo的name字段也对应BundleName。
下面代码以GET_BUNDLE_INFO_DEFAULT为例:
import { bundleManager } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT;
try {
bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
hilog.info(0x0000, 'testTag', 'getBundleInfoForSelf successfully. Data: %{public}s', JSON.stringify(data));
}).catch((err: BusinessError) => {
hilog.error(0x0000, 'testTag', 'getBundleInfoForSelf failed. Cause: %{public}s', err.message);
});
} catch (err) {
let message = (err as BusinessError).message;
hilog.error(0x0000, 'testTag', 'getBundleInfoForSelf failed: %{public}s', message);
}
问题9:如何实现在不使用UIAbility的情况下,能够模块化管理代码,并且各个模块之间可以相互路由跳转
采用HSP进行模块管理,可以实现页面之间的跳转,无需导入导包即可跳转,跳转方式如下:
方式一:所有跳转到HSP内的页面需要使用特定的格式跳转:’@bundle:包名(bundleName)/模块名(moduleName)/路径/页面所在的文件名(不加.ets后缀)’。
方式二:正常entry内模块路由跳转:‘pages/页面所在的文件名(不加.ets后缀)’。
- entry跳转到HSP页面:使用方式一。
- HSP跳转到entry页面:使用方式二。
- HSP跳转到HSP页面:使用方式一。
问题10:如何实现跨模块的页面跳转功能
在业务体系庞大或复杂的情况下,通常会将业务拆分成多个子业务模块,单个子业务模块为一个har/hsp。在该场景下,通常存在从主业务入口跳转到不同子页面模块,或从一个子业务模块A页面跳转到另一个子业务模块B页面的需求。如,从应用首页跳转到登录子业务模块页面。 针对该场景,有以下三种解决方案:
- 方案一:使用router的命名路由接口router.pushNamedRoute()跳转。
- 方案二:使用navigation组件跳转。
以从应用入口模块的页面NavigationPage跳转到Login子业务模块页面LoginPage为例。主要包含以下步骤:
在Login模块中开发自定义组件LoginPage(路由跳转目的地),并对外导出。
@Component
export struct LoginPage {
@Consume('pathStack') pathStack: NavPathStack;
@State message: string = 'Login Page';
build() {
NavDestination() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height('100%')
}
.onBackPressed(() => {
this.pathStack.pop();
return true;
})
}
}
在Login模块的入口文件Index.ets中导出自定义组件。
export { LoginPage } from './src/main/ets/pages/loginPage';
在入口模块的oh-package.json5文件中添加对Login模块的依赖。
{
// ...
"dependencies": {
"@ohos/login": "file:../login"
}
}
入口模块LoginPage页面导入Login模块的自定义组件,并添加到Navigation组件的路由表中。
// 导入Login模块自定义组件
import { LoginPage } from '@ohos/login';
@Entry
@Component
struct NavigationPage {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack();
@Builder
pageMap(name: string) {
if (name === 'loginPage') {
LoginPage()
}
}
build() {
Navigation(this.pathStack) {
Button('jump to login page')
.onClick(() => {
// NavPathInfo第二个参数为自定义参数,可用于信息传递
let pathInfo: NavPathInfo = new NavPathInfo('loginPage', new Object());
this.pathStack.pushDestination(pathInfo, true);
})
}
.navDestination(this.pageMap)
}
}
-
方案三:使用基于navigation组件的自定义路由框架跳转。
方案二虽然可以实现跨模块跳转的功能,但当模块间跳转需求增多,各个模块间将存在非常复杂的依赖关系,甚至会导致多个har/hsp间循环依赖。为了解决模块间的强耦合关系,并且提升页面加载性能,推荐使用自定义路由框架。该方案的整体思路如下:
- 自定义一个路由管理模块RouterModule,各个需要使用路由功能的模块均依赖此模块。
- 路由管理模块RouterModule内部定义路由栈NavPathStack,并对NavPathStack进行封装,对外提供路由能力。
- 在使用Navigation组件时,需将Navigation组件对应的NavPathStack注册到路由管理模块中。通过路由管理模块RouterModule的NavPathStack对路由能力进行控制。
- 各个路由页面不再提供组件,转为提供@builder封装的构建函数,再通过WrappedBuilder封装进行传递使用。
- 各个路由页面将模块名称、路由名称、WrappedBuilder封装后的构建函数注册到路由管理模块RouterModule的路由表中。
- 当路由需要跳转到指定路由时,调用路由管理模块RouterModule的push方法。该方法对指定的模块的路由页面动态导入,并完成路由跳转。
问题11:如何处理错误码9568300:moduleName is not unique
问题原因
用户配置了远端依赖,本地依赖未屏蔽,导致最后的包既有远端依赖,也会有本地依赖,两个依赖moduleName一致导致重复。
解决措施
依赖远端仓库时,屏蔽本地模块,不打包本地模块。
问题12:如何解决依赖的版本冲突问题
发生依赖的版本冲突时,可以给项目级别的oh-package.json5文件配置override字段统一版本。
问题13:当前支持的HAP安装到设备的方式有哪些
支持两种方式安装HAP包到设备:
- 通过hdc命令安装:hdc install xxx.hap
- 将.app文件上架到应用市场后,通过应用市场将HAP分发部署到设备中
问题14:如何让两个HSP不相互依赖,使用对方的组件
可以将需要共用的组件抽离出来,然后放到一个共享包中使用。
问题15:为什么同一App下的HSP文件vendor参数不同时会安装失败
因为安装时会有参数一致性的校验,需要保证module.json中app标签下的vendor字段一致。