#星光不负 码向未来# 一键归鸿——鸿蒙2种优雅的保存图片方式 原创 精华

温州393
发布于 2025-10-23 19:14
浏览
1收藏

在 HarmonyOS 应用开发中,将图片保存到系统相册是常见需求。本文将介绍两种高效实现方案:一是通过网络请求获取图片数据后保存,二是对已加载的 UI 组件进行截图保存。两种方案各有适用场景,开发者可根据实际需求选择。

先展示一下实现效果:
#星光不负 码向未来# 一键归鸿——鸿蒙2种优雅的保存图片方式-鸿蒙开发者社区

一、权限处理方案

保存图片到相册需涉及文件写入权限,HarmonyOS提供两种权限获取方式:

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中进行配置。

#星光不负 码向未来# 一键归鸿——鸿蒙2种优雅的保存图片方式-鸿蒙开发者社区

      {
        "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"
      }

2. 安全控件快捷实现

使用系统提供的 SaveButton 安全控件,可直接获取临时权限,无须申请权限,简化开发流程:

          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. 网络图片下载保存

通过 HTTP 请求获取图片二进制数据,再写入系统相册:

  /**
   * 获取网络图片的原始二进制数据
   * @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种优雅的保存图片方式-鸿蒙开发者社区

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,"图片保存成功")
  }

效果如下图:
#星光不负 码向未来# 一键归鸿——鸿蒙2种优雅的保存图片方式-鸿蒙开发者社区

三、完整使用示例

以安全控件为例,展示两种保存方式的调用:

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) {
        // 用户授权,可以继续访问目标操作
      }
    }
  }
}
  

总结

两种方案各有优势:网络下载适用于需要原始高清图的场景,组件截图则适合快速保存当前显示内容。实际开发中推荐优先使用组件截图方案,其无需重复网络请求,性能更优。权限处理方面,安全控件可减少用户交互步骤,提升体验。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
1
收藏 1
回复
举报
回复
    相关推荐