“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期
“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期 本期问题如下:
向所有参与社区互助的开发者致以最诚挚的感谢!
社区的蓬勃发展,离不开每一位积极参与者的贡献。本期“答开发者问”栏目,精选自广大热心开发者针对提问帖所贡献的众多优质答复之中。它们不仅是智慧与经验的璀璨结晶,更是“众人拾柴火焰高”这一真理的生动体现。在此,我们由衷地感谢每一位热心参与、乐于分享的开发者,是你们的热情与智慧,让这个社区充满了生机与活力,每一次的解答都是对技术探索精神的最好诠释。同时,我们也诚挚邀请更多的开发者加入到这场智慧碰撞的盛宴中来。无论是抛出难题寻求解答,还是慷慨解囊分享经验,您的每一份参与都将为鸿蒙开发者社区注入新的活力,推动我们共同前行,在技术的海洋中扬帆远航。
答开发者问系列汇总:
往期问题回顾:
注意:
更多关于“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期的实战教程也可以访问 https://www.itying.com/category-93-b0.html
答开发者问系列汇总的帖子,链接打开显示不存在
更多关于“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
现在可以了,不知道为啥我刚刚点不行,应该是开放了
大概率是提交更新后在审核中,
666
问题五:蓝牙access.enableBluetooth()拉起询问界面时如何感知用户操作对话框的行为?
蓝牙access.enableBluetooth()拉起询问界面时,如何感知用户操作对话框的行为,代码如下:
import { access } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
@Component
@Entry
struct Question7 {
aboutToAppear(): void {
let permissions: Array<Permissions> = ['ohos.permission.ACCESS_BLUETOOTH'];
// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗。
atManager.requestPermissionsFromUser(getContext(), permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// 用户授权,可以继续访问目标操作。
} else {
// 当用户拒绝授权时,系统应提示用户必须授予相应权限才能使用当前页面的功能,并指导用户前往系统设置开启所需权限。
return;
}
}
// 授权成功
}).catch((err: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
})
}
build() {
Column(){
Button().onClick(()=>{
try {
// 主动获取蓝牙当前的开关状态
let state = access.getState();
if (state == access.BluetoothState.STATE_OFF) {
// 若蓝牙是关闭的,则主动开启蓝牙
access.enableBluetooth();
}
} catch (err) {
console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
})
}
}
}
解决方案:
如果应用想要感知用户操作对话框的行为,建议使用access.enableBluetoothAsync,该方法API20开始支持。demo代码如下:
import { access } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';
try {
access.enableBluetoothAsync().then(() => {
// 点击弹窗里的开启按钮进入此处
console.info('enableBluetoothAsync');
}, (error: BusinessError) => {
// 点击弹窗里的禁止按钮进入此处
console.error('enableBluetoothAsync: errCode:' + error.code + ',errMessage' + error.message);
})
} catch (err) {
console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
原链接:
开启蓝牙access.enableBluetooth()拉起询问界面时,怎么监听点击禁止的情况-华为开发者问答 | 华为开发者联盟 (huawei.com)
问题四:resourceManager.getRawFileContentSync后面的路径怎么写?
错误提示是Invalid relative path和SourceMap is not initialized yet ,是不是因为路径写错了,还是什么问题?
解决方案:
这两个错误分别对应文件路径配置错误和项目构建 / 调试配置问题,需针对性排查。resourceManager.getRawFileContentSync对应的是你当前entry下src/main/resources/rawfile目录,如果是其他模块,需要使用createModuleContext()来实现,具体参考如下demo:
// 在EntryAbility的onCreate方法中
let moduleContext: common.Context;
try {
application.createModuleContext(this.context, 'library').then((data: Context) => {
moduleContext = data;
AppStorage.setOrCreate("moduleContext", moduleContext);
console.info('createBundleContext success!');
}).catch((error: BusinessError) => {
let code: number = (error as BusinessError).code;
let message: string = (error as BusinessError).message;
console.error(`createModuleContext failed, error.code: ${code}, error.message: ${message}`);
});
} catch (error) {
let code: number = (error as BusinessError).code;
let message: string = (error as BusinessError).message;
console.error(`createModuleContext failed, error.code: ${code}, error.message: ${message}`);
}
import { BusinessError } from '@kit.BasicServicesKit';
import { application } from '@kit.AbilityKit';
@Component
@Entry
struct Test5 {
build() {
Column() {
Button('获取当前entry的rawfile下的文件').onClick((event: ClickEvent) => {
try {
// 例如你在 resources/rawfile/file 目录下放了test.json
this.getUIContext().getHostContext()?.resourceManager.getRawFileContent("file/test.json",
(error: BusinessError, value: Uint8Array) => {
if (error != null) {
console.error("error is " + error);
} else {
let rawFile = value;
}
});
} catch (error) {
let code = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`callback getRawFileContent failed, error code: ${code}, message: ${message}.`);
}
})
Button('获取其他模块的rawfile下的文件').onClick((event: ClickEvent) => {
// 需要在EntryAbility的onCreate方法中先设置moduleContext,这边去读取就行
let moduleContext: Context = AppStorage.get("moduleContext") as Context;
// 例如你在 library模块 resources/rawfile/ 目录下放了test.json
moduleContext.resourceManager.getRawFileContent("test.json",
(error: BusinessError, value: Uint8Array) => {
if (error != null) {
console.error("error is " + error);
} else {
let rawFile = value;
}
});
})
}
}
}
原链接:
this.context.resourceManager.getRawFileContentSync后面的路径怎么写-华为开发者问答 | 华为开发者联盟 (huawei.com)
问题三:Navigation组件怎么设置顶部动态模糊效果?
Navigation组件怎么设置顶部动态模糊效果?希望底部元素经过头部的时候,会有背景模糊的效果。
解决方案:
可以参考背景设置和NavigationTitleOptions,先定义backgroundColor、BlurStyle和BackgroundBlurStyleOptions,然后再赋值给NavigationTitleOptions对象里的对应属性字段,样例demo如下:
const COLOR1: string = "#80004AAF";
const COLOR2: string = "#802787D9";
const BLUR_STYLE_1: BlurStyle = BlurStyle.BACKGROUND_THIN;
const BLUR_STYLE_2: BlurStyle = BlurStyle.BACKGROUND_THICK;
const BLUR_STYLE_OPTION_1: BackgroundBlurStyleOptions = {
colorMode: ThemeColorMode.DARK,
adaptiveColor: AdaptiveColor.DEFAULT,
blurOptions: { grayscale: [20, 20] },
scale: 1
};
const BLUR_STYLE_OPTION_2: BackgroundBlurStyleOptions = {
colorMode: ThemeColorMode.LIGHT,
adaptiveColor: AdaptiveColor.AVERAGE,
blurOptions: { grayscale: [20, 20] },
scale: 1
};
const EFFECT_OPTION_1: BackgroundEffectOptions = {
radius: 20,
saturation: 10,
brightness: 0,
color: '#66FFFFFF',
adaptiveColor: AdaptiveColor.DEFAULT,
blurOptions: { grayscale: [0, 0] },
};
const EFFECT_OPTION_2: BackgroundEffectOptions = {
radius: 60,
saturation: 40,
brightness: 1,
color: '#661A1A1A',
adaptiveColor: AdaptiveColor.AVERAGE,
blurOptions: { grayscale: [20, 20] },
};
@Component
struct BackComponent {
build() {
Row() {
Column() {
}
.height('100%')
.backgroundColor("#3D9DB4")
.layoutWeight(9)
Column() {
}
.height('100%')
.backgroundColor("#17A98D")
.layoutWeight(9)
Column() {
}
.height('100%')
.backgroundColor("#FFC000")
.layoutWeight(9)
}
.height('100%')
.width('100%')
}
}
@Component
struct ColorAndBlur {
@State useColor1: boolean = true;
@State useBlur1: boolean = true;
@State useEffect1: boolean = true;
build() {
NavDestination() {
Stack({ alignContent: Alignment.Center }) {
BackComponent()
.width('100%')
.height('100%')
Column() {
Stack({ alignContent: Alignment.Center }) {
Button("switch color")
.onClick(() => {
this.useColor1 = !this.useColor1;
})
}
.width('100%')
.layoutWeight(1)
Stack({ alignContent: Alignment.Center }) {
Button("switch blur")
.onClick(() => {
this.useBlur1 = !this.useBlur1;
})
}
.width('100%')
.layoutWeight(1)
Stack({ alignContent: Alignment.Center }) {
Button("switch effect")
.onClick(() => {
this.useEffect1 = !this.useEffect1;
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
// 开发者可以设置标题栏的背景颜色和背景模糊效果
.title("Destination Title", {
backgroundColor: this.useColor1 ? COLOR1 : COLOR2,
backgroundBlurStyle: this.useBlur1 ? BLUR_STYLE_1 : BLUR_STYLE_2,
barStyle: BarStyle.STACK,
backgroundEffect: this.useEffect1 ? EFFECT_OPTION_1 : EFFECT_OPTION_2,
})
// 开发者可以设置菜单的背景颜色和背景模糊效果
.menus([
{ value: "A" },
{ value: "B" },
{ value: "C" },
{ value: "D" },
], {
moreButtonOptions: {
backgroundEffect: this.useEffect1 ? EFFECT_OPTION_1 : EFFECT_OPTION_2,
}
})
// 开发者可以设置工具栏的背景颜色和背景模糊效果
.toolbarConfiguration([
{ value: "A" },
{ value: "B" },
{ value: "C" },
{ value: "D" },
{ value: "E" },
{ value: "F" }
], {
backgroundEffect: this.useEffect1 ? EFFECT_OPTION_1 : EFFECT_OPTION_2,
// 开发者可以设置工具栏的菜单的背景颜色和背景模糊效果
moreButtonOptions: {
backgroundEffect: this.useEffect1 ? EFFECT_OPTION_1 : EFFECT_OPTION_2,
}
})
}
}
@Entry
@Component
struct Index {
@Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();
@State useColor1: boolean = true;
@State useBlur1: boolean = true;
@State useBlurOption1: boolean = true;
@Builder
PageBuilder(name: string, param?: Object) {
if (name === 'NavigationMenu') {
ColorAndBlur();
}
}
build() {
Navigation(this.navPathStack) {
Stack({ alignContent: Alignment.Center }) {
BackComponent()
.width('100%')
.height('100%')
Column() {
Stack({ alignContent: Alignment.Center }) {
Button("switch color")
.onClick(() => {
this.useColor1 = !this.useColor1;
})
}
.width('100%')
.layoutWeight(1)
Stack({ alignContent: Alignment.Center }) {
Button("switch blur")
.onClick(() => {
this.useBlur1 = !this.useBlur1;
})
}
.width('100%')
.layoutWeight(1)
Stack({ alignContent: Alignment.Center }) {
Button("switch blurOption")
.onClick(() => {
this.useBlurOption1 = !this.useBlurOption1;
})
}
.width('100%')
.layoutWeight(1)
Stack({ alignContent: Alignment.Center }) {
Button("push page")
.onClick(() => {
this.navPathStack.pushPathByName('NavigationMenu', null);
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('80%')
}.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.navDestination(this.PageBuilder)
// 开发者可以设置标题栏的背景颜色和背景模糊效果
.title("NavTitle", {
backgroundColor: this.useColor1 ? COLOR1 : COLOR2,
backgroundBlurStyle: this.useBlur1 ? BLUR_STYLE_1 : BLUR_STYLE_2,
barStyle: BarStyle.STACK,
backgroundBlurStyleOptions: this.useBlurOption1 ? BLUR_STYLE_OPTION_1 : BLUR_STYLE_OPTION_2,
})
// 开发者可以设置菜单的背景颜色和背景模糊效果
.menus([
{ value: "A" },
{ value: "B" },
{ value: "C" },
{ value: "D" },
], {
moreButtonOptions: {
backgroundBlurStyle: this.useBlur1 ? BLUR_STYLE_1 : BLUR_STYLE_2,
backgroundBlurStyleOptions: this.useBlurOption1 ? BLUR_STYLE_OPTION_1 : BLUR_STYLE_OPTION_2,
}
})
// 开发者可以设置工具栏的背景颜色和背景模糊效果
.toolbarConfiguration([
{ value: "A" },
{ value: "B" },
{ value: "C" },
{ value: "D" },
{ value: "E" },
{ value: "F" }
], {
backgroundColor: this.useColor1 ? COLOR1 : COLOR2,
backgroundBlurStyle: this.useBlur1 ? BLUR_STYLE_1 : BLUR_STYLE_2,
// 开发者可以设置工具栏的菜单的背景颜色和背景模糊效果
moreButtonOptions: {
backgroundBlurStyle: this.useBlur1 ? BLUR_STYLE_1 : BLUR_STYLE_2,
backgroundBlurStyleOptions: this.useBlurOption1 ? BLUR_STYLE_OPTION_1 : BLUR_STYLE_OPTION_2,
}
})
}
}
原链接:
问题一:如何实现在Web中监控到跳转指定url时拦截跳转并新建一个Web?
想要实现下面的逻辑,但是这种写法报错:UI component syntax cannot be written here,请问要怎么写才正确?
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct Question {
webviewController: webview.WebviewController = new webview.WebviewController();
webviewController_hahaha: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'www.example.com', controller: this.webviewController })
.onLoadIntercept((event) => {
if (event) {
let url = event.data.getRequestUrl();
if (url.endsWith('example')) {
Web({ src: this.url, controller: this.webviewController_hahaha })
return true;
}
}
return false;
})
}
.height('100%')
.width('100%')
}
}
解决方案:
报错是因为回调函数中不能使用UI组件语法,回调函数中可以使用loadUrl来加载一个新的页面:
this.webviewController.loadUrl('www.xxx/hahah?t=1');
一个ArkTS页面内可以包含多个Web组件,只需保证每个Web组件都拥有自己独立的WebviewController
实例就行了。对于本问题场景,可以参考如下demo:
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct Question1 {
context: Context = this.getUIContext()?.getHostContext() as Context;
webviewController: webview.WebviewController = new webview.WebviewController();
webviewController_hahaha: webview.WebviewController = new webview.WebviewController();
@State isVisible: boolean = true; // 自定义标志位isVisible,来控制是否需要显示组件
@State url: string = ''
build() {
Column() {
Web({ src: 'www.example.com', controller: this.webviewController })
.onLoadIntercept((event) => {
if (event) {
let url = event.data.getRequestUrl();
if (url.endsWith('example')) {
this.isVisible = false;
this.url = 'www.example.com/example';
return true;
}
}
return false;
})
.visibility(this.isVisible ? Visibility.Visible :
Visibility.None)
if (!this.isVisible) {
Web({ src: this.url, controller: this.webviewController_hahaha })
}
}
.height('100%')
.width('100%')
}
}
原链接:
UI component syntax cannot be written here. 那这个逻辑是实现不了的么-华为开发者问答 | 华为开发者联盟 (huawei.com)
该期内容主要聚焦HarmonyOS NEXT的分布式能力与ArkTS语言特性,涉及跨设备组件调用、Stage模型适配及声明式UI开发。同时解答了DevEco Studio工具链的调试功能、应用包管理机制及系统安全架构相关问题。部分问题涉及Native API与ArkCompiler的协同工作机制。
-
Web跳转拦截与新建Web实现:通过
WebController
的onPageStart
事件监听URL变化,使用shouldOverrideUrlLoading
方法判断目标URL,若需拦截则返回true并调用window.open
或router.pushUrl
创建新Web组件。 -
http.destroy()后回调处理:
destroy()
方法会终止请求但可能因异步操作延迟回调,建议在回调中增加状态检查(如使用isDestroyed标志),或通过AbortController
显式取消Promise链。 -
Navigation顶部动态模糊效果:使用
Navigation
组件的titleBar
属性配置BlurStyle
,例如:titleBar: { style: BlurStyle.Thick, // 可选Thin、Thick等 opacity: 0.8 }
需确保外层容器允许模糊效果(如背景非纯色)。
-
resourceManager.getRawFileContentSync路径写法:路径基于
resources/rawfile/
目录的相对路径,例如文件位于resources/rawfile/config.json
,则直接传入"config.json"
。子目录需包含路径如"data/config.json"
。 -
蓝牙权限对话框用户操作感知:通过
access.setBluetoothEnableCallback
注册回调函数,监听BluetoothStateChange
事件,根据state
参数(如STATE_ON
或STATE_OFF
)判断用户允许或拒绝。
(注:具体实现需参考HarmonyOS SDK版本及API细节,代码示例为通用思路。)