“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期

“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期 本期问题如下:

  1. 如何实现在Web中监控到跳转指定url时拦截跳转并新建一个Web?

  2. 调用http的destroy()方法后仍然收到回调?

  3. Navigation组件怎么设置顶部动态模糊效果?

  4. resourceManager.getRawFileContentSync后面的路径怎么写?

  5. 蓝牙access.enableBluetooth()拉起询问界面时如何感知用户操作对话框的行为?

向所有参与社区互助的开发者致以最诚挚的感谢!

特别感谢本期优质答复贡献者: @仙银@谢道韫@半夏微凉

社区的蓬勃发展,离不开每一位积极参与者的贡献。本期“答开发者问”栏目,精选自广大热心开发者针对提问帖所贡献的众多优质答复之中。它们不仅是智慧与经验的璀璨结晶,更是“众人拾柴火焰高”这一真理的生动体现。在此,我们由衷地感谢每一位热心参与、乐于分享的开发者,是你们的热情与智慧,让这个社区充满了生机与活力,每一次的解答都是对技术探索精神的最好诠释。同时,我们也诚挚邀请更多的开发者加入到这场智慧碰撞的盛宴中来。无论是抛出难题寻求解答,还是慷慨解囊分享经验,您的每一份参与都将为鸿蒙开发者社区注入新的活力,推动我们共同前行,在技术的海洋中扬帆远航。

答开发者问系列汇总:

“答开发者问”系列汇总(持续更新中…)

往期问题回顾:

“答开发者问”之HarmonyOS技术问题解析 第1期

“答开发者问”之HarmonyOS技术问题解析 第2期

“答开发者问”之HarmonyOS技术问题解析 第3期

“答开发者问”之HarmonyOS技术问题解析 第4期

“答开发者问”之HarmonyOS技术问题解析 第5期

“答开发者问”之HarmonyOS技术问题解析 第6期

“答开发者问”之HarmonyOS技术问题解析 第7期

“答开发者问”之HarmonyOS技术问题解析 第8期

“答开发者问”之HarmonyOS技术问题解析 第9期

“答开发者问”之HarmonyOS技术问题解析 第10期

“答开发者问”之HarmonyOS技术问题解析 第11期

“答开发者问”之HarmonyOS技术问题解析 第12期

“答开发者问”之HarmonyOS技术问题解析 第13期

“答开发者问”之HarmonyOS技术问题解析 第14期

“答开发者问”之HarmonyOS技术问题解析 第15期

“答开发者问”之HarmonyOS技术问题解析 第16期

注意:

开发者小伙伴们,规范提问,高效沟通!更快得到问题答案的秘诀来啦,点击链接直达


更多关于“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期的实战教程也可以访问 https://www.itying.com/category-93-b0.html

11 回复

答开发者问系列汇总的帖子,链接打开显示不存在

更多关于“答开发者问”之HarmonyOS鸿蒙Next技术问题解析 第17期的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


我点击是OK的呢。,

现在可以了,不知道为啥我刚刚点不行,应该是开放了

大概率是提交更新后在审核中,

问题五:蓝牙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、BlurStyleBackgroundBlurStyleOptions,然后再赋值给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,
      }
    })
  }
}

原链接:

Navigation组件怎么设置顶部动态模糊效果-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题一:如何实现在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的协同工作机制。

  1. Web跳转拦截与新建Web实现:通过WebControlleronPageStart事件监听URL变化,使用shouldOverrideUrlLoading方法判断目标URL,若需拦截则返回true并调用window.openrouter.pushUrl创建新Web组件。

  2. http.destroy()后回调处理destroy()方法会终止请求但可能因异步操作延迟回调,建议在回调中增加状态检查(如使用isDestroyed标志),或通过AbortController显式取消Promise链。

  3. Navigation顶部动态模糊效果:使用Navigation组件的titleBar属性配置BlurStyle,例如:

    titleBar: {
      style: BlurStyle.Thick, // 可选Thin、Thick等
      opacity: 0.8
    }
    

    需确保外层容器允许模糊效果(如背景非纯色)。

  4. resourceManager.getRawFileContentSync路径写法:路径基于resources/rawfile/目录的相对路径,例如文件位于resources/rawfile/config.json,则直接传入"config.json"。子目录需包含路径如"data/config.json"

  5. 蓝牙权限对话框用户操作感知:通过access.setBluetoothEnableCallback注册回调函数,监听BluetoothStateChange事件,根据state参数(如STATE_ONSTATE_OFF)判断用户允许或拒绝。

(注:具体实现需参考HarmonyOS SDK版本及API细节,代码示例为通用思路。)

回到顶部