HarmonyOS鸿蒙Next中在弹窗中保存网络图片Permission denied, js errcode:201

HarmonyOS鸿蒙Next中在弹窗中保存网络图片Permission denied, js errcode:201

static async loadImageWithUrl(context:Context,url: string): Promise<boolean> {
    let pixelMaptemp: PixelMap | undefined = undefined;
    let responseCode = http.ResponseCode;
    let OutData: http.HttpResponse;
    let imagePackerApi = image.createImagePacker();
    let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 100 };
    // 确保网络正常
    http.createHttp().request(url, {
      method: http.RequestMethod.GET,
      connectTimeout: 60000,
      readTimeout: 60000
    },
      async (error: BusinessError, data: http.HttpResponse) => {
        if (error) {
          console.error(`http request failed with. Code: ${error.code}, message: ${error.message}`);
        } else {
          OutData = data;
          let code: http.ResponseCode | number = OutData.responseCode;
          if (responseCode.OK === code) {
            let imageData: ArrayBuffer = OutData.result as ArrayBuffer;
            let imageSource: image.ImageSource = image.createImageSource(imageData);

            class tmp {
              height: number = 100
              width: number = 100
            };

            let options: Record<string, number | boolean | tmp> = {
              'alphaType': 0, // Transparency
              'editable': false, // Is it editable
              'pixelFormat': 3, // Pixel Format
              'scaleMode': 1, // Abbreviation
              'size': { height: 100, width: 100 }
            }; // Create Image Size
            imageSource.createPixelMap(options).then((pixelMap: PixelMap) => {
              pixelMaptemp = pixelMap;
              pixelMaptemp.getImageInfo().then((info: image.ImageInfo) => {
                console.info('info.width = ' + info.size.width);
              }).catch((err: BusinessError) => {
                console.error('Failed ' + err);
              })
              imagePackerApi.packToData(pixelMaptemp, packOpts).then(async (buffer: ArrayBuffer) => {
                try {
                  let helper = photoAccessHelper.getPhotoAccessHelper(context);
                  let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
                  let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
                  // Write to file
                  await fs.write(file.fd, buffer);
                  promptAction.openToast({
                    message: '已保存至相册',
                    duration: 2500
                  });
                  // Close the file
                  await fs.close(file.fd);
                } catch (error) {
                  console.error('error is ' + JSON.stringify(error));
                }
                return false
              }).catch((error: BusinessError) => {
                console.error('Failed to pack the image. And the error is: ' + error);
              }).finally(()=>{
                pixelMap.release();
              })

            })
          }
        }
      }
    )
    return false
  }

以上是保存图片的方法

@CustomDialog
@Component
struct CustomDialogExample {
  @Prop smallPic: string;
  @Prop bigPic: string;
  @Prop context: Context;
  controller?: CustomDialogController;
  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Image(this.smallPic)
        .width('100%')
        .height("100%")
      Button("保存到相册", { type: ButtonType.Normal, stateEffect: true })
        .onClick(async ()=>{
          await FilesUtils.loadImageWithUrl(this.context,this.bigPic).then((result)=>{
            if (result) {
              this.controller?.close()
            }
          });
        })
        .borderRadius(8)
        .backgroundColor(0x317aff)
    }.borderRadius(10).height(300)
  }
}

以上是弹窗代码

dialogController: CustomDialogController | null = new CustomDialogController({
    builder: CustomDialogExample({
      smallPic: this.datepicBean[0].smallPic,
      bigPic: this.datepicBean[0].bigPic,
      context:this.context
    }),
    autoCancel: true,
    onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
      console.info(`reason= ${dismissDialogAction.reason}`);
      console.info('dialog onWillDismiss');
      if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
        dismissDialogAction.dismiss();
      }
      if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
        dismissDialogAction.dismiss();
      }
    },
    alignment: DialogAlignment.Bottom,
    offset: { dx: 0, dy: -20 },
    gridCount: 4,
    customStyle: false,
    cornerRadius: 10,
  })

以上是弹窗Controller,context为:

private context = this.getUIContext().getHostContext()!;

现在是在弹窗中保存图片就提示Permission denied, js errcode:201,如果不在弹窗中调用loadImageWithUrl就能正常保存图片,请问这个context该怎么传?


更多关于HarmonyOS鸿蒙Next中在弹窗中保存网络图片Permission denied, js errcode:201的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

你当前通过 this.getUIContext().getHostContext() 获取的是 HostContext(UI 层上下文),而相册读写、文件操作等权限相关操作必须依赖具备完整权限能力的 AbilityContext(应用能力上下文),这是导致权限拒绝的根本原因。

更多关于HarmonyOS鸿蒙Next中在弹窗中保存网络图片Permission denied, js errcode:201的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


感谢,private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 这样就都可以了,

在HarmonyOS Next中,弹窗内保存网络图片出现Permission denied,错误码201,通常是由于未正确配置存储权限所致。需要在module.json5文件的module字段内,添加ohos.permission.WRITE_IMAGEVIDEO权限请求。同时,确保在保存操作前,通过系统能力接口动态申请该权限。

在HarmonyOS Next中,弹窗(CustomDialog)是一个独立的UI组件,其生命周期和上下文环境与主页面不同。你遇到的Permission denied, js errcode:201错误,通常是因为在弹窗中使用的context没有正确的权限或上下文环境。

从你的代码看,你通过this.getUIContext().getHostContext()获取了context并传递给弹窗。但在弹窗中,这个context可能无法正确访问相册等敏感资源。

解决方案:

  1. 直接使用弹窗自身的context 在弹窗组件内部,可以直接使用ArkUI提供的getContext()方法获取正确的上下文:

    [@CustomDialog](/user/CustomDialog)
    [@Component](/user/Component)
    struct CustomDialogExample {
      // 移除@Prop context: Context;
      controller?: CustomDialogController;
      
      aboutToAppear() {
        // 可以在生命周期中获取context
        let context = getContext();
      }
      
      build() {
        // ...
        .onClick(async ()=>{
          let context = getContext(); // 在这里获取
          await FilesUtils.loadImageWithUrl(context, this.bigPic).then((result)=>{
            // ...
          });
        })
      }
    }
    
  2. 在调用弹窗时传递UI上下文 如果需要在弹窗构建时传递,可以使用正确的上下文获取方式:

    // 在主页面中
    private context = getContext(); // 使用getContext()而不是getUIContext().getHostContext()
    
    // 或者直接在构建弹窗时获取
    dialogController: CustomDialogController | null = new CustomDialogController({
      builder: CustomDialogExample({
        smallPic: this.datepicBean[0].smallPic,
        bigPic: this.datepicBean[0].bigPic,
        context: getContext() // 直接使用getContext()
      }),
      // ... 其他配置
    })
    
  3. 确保权限声明module.json5中确认已声明必要的权限:

    {
      "module": {
        "requestPermissions": [
          {
            "name": "ohos.permission.READ_IMAGEVIDEO",
            "reason": "$string:reason_desc",
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "always"
            }
          },
          {
            "name": "ohos.permission.WRITE_IMAGEVIDEO", 
            "reason": "$string:reason_desc",
            "usedScene": {
              "abilities": ["EntryAbility"],
              "when": "always"
            }
          }
        ]
      }
    }
    

关键点:

  • 弹窗中的上下文环境是独立的,不能直接使用主页面的上下文对象
  • getContext()在ArkUI中会自动返回当前组件可用的正确上下文
  • 权限错误201通常表示上下文环境不正确或权限未正确声明

建议在弹窗内部使用getContext()动态获取上下文,这样可以确保始终使用正确的上下文环境来访问相册等系统资源。

回到顶部