开发中使用——鸿蒙特有的图片保存到相册的方式 原创 精华

前端森之鸟
发布于 2025-8-20 15:09
浏览
0收藏

@TOC

保存图片到相册是App常用的功能,HarmonyOS开发中也不例外,一般情况下都是请求保存网络图片到本地。因为图片显示出来就已经加载过一次了,HarmonyOS开发又提供了一种简便的方式,通过对获取已加载的组件的截图,进行保存,这种效率更高。现在对两种方式进行汇总。

  • 请求保存网络图片数据,进行保存。
  • 对获取已加载的组件的截图,进行保存。

一、前言

从功能上来说,保存图片到相机涉及到的权限是ohos.permission.WRITE_IMAGEVIDEO,仅特殊场景与功能才可申请此权限,例如应用需要克隆、备份或同步图片/视频类文件。还要一种方式是使用安全控件来临时申请权限。

1.自定义下载按钮,通过申请权限

          // 保存至图库按钮
          Button() {
            Row() {
              Image($r('app.media.ic_download'))
                .width(20)
                .height(20)
                .fillColor(Color.White)
                .margin({ right: 5 })
              Text('保存至图库')
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor(Color.White)
            }
          }
          .width('42%')
          .height('6%')
          .borderRadius(20)
          .backgroundColor(`#${this.movie.bgColor}`)
          .onClick(() => {
            // TODO: 实现保存至图库逻辑
            console.log('保存至图库');
          })
          .margin({ left: 10, right: 10 })

需要申请权限,在模块的module.json5中进行配置

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

进行下载操作时,进行申请权限

/**
   * 创建申请权限明细(非安全控件使用时使用)
   * @returns
   */
  async reqPermissionsFromUser(): Promise<number[]> {
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus = await atManager.requestPermissionsFromUser(this.context, ['ohos.permission.WRITE_IMAGEVIDEO']);
    return grantStatus.authResults;
  }

  /**
   * 用户申请权限(非安全控件使用时使用)
   */
  async requestPermission() {
    let grantStatus = await this.reqPermissionsFromUser();
    for (let i = 0; i < grantStatus.length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作
      }
    }
  }

2.使用安全控件实现权限的直接访问

          SaveButton({icon:SaveIconStyle.FULL_FILLED, text: SaveDescription.SAVE_IMAGE, buttonType: ButtonType.ROUNDED_RECTANGLE })
            .fontColor(Color.White)
            .fontWeight(FontWeight.Medium)
            .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {

                // TODO: 实现保存至图库逻辑
                console.log('保存至图库');
            })

二、实现方式1:通过获取网络图片数据,进行保存

  /**
   * 获取网络图片的原始二进制数据
   * @param url
   * @param callback
   */
  async getPicArrayBuffer(url:string,callback:(buf:ArrayBuffer|null)=>void) {
    http.createHttp()
      .request(url,
        (error: Error, data: http.HttpResponse) => {

          let arrayBuf:ArrayBuffer|null = null;
          if (error) {
            // SilToast.showToast(this.uiContext,'图片保存失败'),这里不做处理,自行处理
            arrayBuf = null;
          }else{
            // 判断网络获取到的资源是否为ArrayBuffer类型
            if (data.result instanceof ArrayBuffer) {
              arrayBuf = data.result as ArrayBuffer;
            }
          }
          callback(arrayBuf);
        }
      )
  }
  
    /**
   * 保存ArrayBuffer到图库
   * @param buffer:图片ArrayBuffer
   * @returns
   */
  async saveImageToPhoto(buffer: ArrayBuffer | string): Promise<void> {
    const helper = photoAccessHelper.getPhotoAccessHelper(this.context); // 获取相册管理模块的实例
    const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 指定待创建的文件类型、后缀和创建选项,创建图片或视频资源
    const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let r =  await fs.write(file.fd, buffer);
    await fs.close(file.fd);
    // SilToast.showToast(this.uiContext,"图片保存成功")
  }

三、实现方式2:通过获取已加载的组件的截图,进行保存

  /**
   * 获取已加载的组件的截图(传入组件的组件id标识,找到对应组件进行截图)
   * 推荐使用,保存速度不是一般的快
   * @param componentId
   * @param callback
   */
  getComponentSnapshot(componentId:string,callback:(buf:ArrayBuffer|null)=>void){
    this.uiContext.getComponentSnapshot().get(componentId, async (error: Error, pixelMap: image.PixelMap) => {
      if (error) {
        /// 获取失败
        callback(null);
        console.log("error: " + JSON.stringify(error))
        return;
      }

      let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 }
      const imagePackerApi: image.ImagePacker = image.createImagePacker();
      imagePackerApi.packToData(pixelMap, packOpts)
        .then((data: ArrayBuffer) => {
          callback(data)
          console.info('Succeeded in packing the image.');
        }).catch((error: BusinessError) => {
        callback(null)
        console.error(`Failed to pack the image.code ${error.code},message is ${error.message}`);
      })

    });
  }
  
    /**
   * 保存ArrayBuffer到图库
   * @param buffer:图片ArrayBuffer
   * @returns
   */
  async saveImageToPhoto(buffer: ArrayBuffer | string): Promise<void> {
    const helper = photoAccessHelper.getPhotoAccessHelper(this.context); // 获取相册管理模块的实例
    const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 指定待创建的文件类型、后缀和创建选项,创建图片或视频资源
    const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let r =  await fs.write(file.fd, buffer);
    await fs.close(file.fd);
    // SilToast.showToast(this.uiContext,"图片保存成功")
  }

四、使用实例

使用安全控件实现权限的直接访问为例。

import { PicSaveManager } from '../utils/PicSaveManager';
import { SilToast } from '../utils/SilToastUtils';
// 图片下载管理
private  picSaveManager:PicSaveManager = new PicSaveManager(this.getUIContext());

/// 使用安全控件实现权限的直接访问
SaveButton({icon:SaveIconStyle.FULL_FILLED, text: SaveDescription.SAVE_IMAGE, buttonType: ButtonType.ROUNDED_RECTANGLE })
  .fontColor(Color.White)
  .fontWeight(FontWeight.Medium)
  .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {

    if (result == SaveButtonOnClickResult.SUCCESS) {
      // 方法1:从网络请求下载,然后保存到相册
      // this.picSaveManager.getPicArrayBuffer(this.movie.poster_url, async (bufArray)=>{
      //     if(bufArray == null){
      //       SilToast.showToast(this.getUIContext(),'图片保存失败')
      //     }else{
      //       // 保存图片到相册
      //       await this.picSaveManager.saveImageToPhoto(bufArray);
      //       SilToast.showToast(this.getUIContext(),'图片保存成功')
      //     }
      // });

      /// 方法2:获取已加载图片的截图,然后保存到相册(保存速度不是一般的快)
      this.picSaveManager.getComponentSnapshot('moviePic', async (bufArray)=>{
        if(bufArray == null){
          SilToast.showToast(this.getUIContext(),'图片保存失败')
        }else{
          // 保存图片到相册
          await this.picSaveManager.saveImageToPhoto(bufArray);
          SilToast.showToast(this.getUIContext(),'图片保存成功')
        }
      })
    }
  })

五、核心代码补充


import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { http } from '@kit.NetworkKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import {  UIContext } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';


export class PicSaveManager{
  private uiContext:UIContext;
  private context:common.UIAbilityContext;

  /// 初始化时,获取到context
  constructor(uiContext:UIContext) {
    this.uiContext = uiContext;
    this.context = uiContext.getHostContext() as common.UIAbilityContext;
  }

  /**
   * 获取网络图片的原始二进制数据
   * @param url
   * @param callback
   */
  async getPicArrayBuffer(url:string,callback:(buf:ArrayBuffer|null)=>void) {
    http.createHttp()
      .request(url,
        (error: Error, data: http.HttpResponse) => {

          let arrayBuf:ArrayBuffer|null = null;
          if (error) {
            // SilToast.showToast(this.uiContext,'图片保存失败'),这里不做处理,自行处理
            arrayBuf = null;
          }else{
            // 判断网络获取到的资源是否为ArrayBuffer类型
            if (data.result instanceof ArrayBuffer) {
              arrayBuf = data.result as ArrayBuffer;
            }
          }
          callback(arrayBuf);
        }
      )
  }

  /**
   * 获取已加载的组件的截图(传入组件的组件id标识,找到对应组件进行截图)
   * 推荐使用,保存速度不是一般的快
   * @param componentId
   * @param callback
   */
  getComponentSnapshot(componentId:string,callback:(buf:ArrayBuffer|null)=>void){
    this.uiContext.getComponentSnapshot().get(componentId, async (error: Error, pixelMap: image.PixelMap) => {
      if (error) {
        /// 获取失败
        callback(null);
        console.log("error: " + JSON.stringify(error))
        return;
      }

      let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 }
      const imagePackerApi: image.ImagePacker = image.createImagePacker();
      imagePackerApi.packToData(pixelMap, packOpts)
        .then((data: ArrayBuffer) => {
          callback(data)
          console.info('Succeeded in packing the image.');
        }).catch((error: BusinessError) => {
        callback(null)
        console.error(`Failed to pack the image.code ${error.code},message is ${error.message}`);
      })

    });
  }

  /**
   * 保存ArrayBuffer到图库
   * @param buffer:图片ArrayBuffer
   * @returns
   */
  async saveImageToPhoto(buffer: ArrayBuffer | string): Promise<void> {
    const helper = photoAccessHelper.getPhotoAccessHelper(this.context); // 获取相册管理模块的实例
    const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 指定待创建的文件类型、后缀和创建选项,创建图片或视频资源
    const file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    let r =  await fs.write(file.fd, buffer);
    await fs.close(file.fd);
    // SilToast.showToast(this.uiContext,"图片保存成功")
  }

  /**
   * 创建申请权限明细(非安全控件使用时使用)
   * @returns
   */
  async reqPermissionsFromUser(): Promise<number[]> {
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus = await atManager.requestPermissionsFromUser(this.context, ['ohos.permission.WRITE_IMAGEVIDEO']);
    return grantStatus.authResults;
  }

  /**
   * 用户申请权限(非安全控件使用时使用)
   */
  async requestPermission() {
    let grantStatus = await this.reqPermissionsFromUser();
    for (let i = 0; i < grantStatus.length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作
      }
    }
  }
}
  

六、感谢

我主要是在原来大佬们博客和官方文档的基础,进行了汇总,以及新API的适配工作。

感谢大佬们的分享。

参考

【HarmonyOS 5】 鸿蒙图片或视频保存相册

HarmonyOS 将图片保存到相册

HarmonyOS实战:一招搞定保存图片到相册

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
鸿蒙保存图片到相册工具类.zip 2.97K 3次下载
已于2025-8-20 15:09:53修改
1
收藏
回复
举报
回复
    相关推荐