回复
#星光不负 码向未来# 一键归鸿——鸿蒙2种优雅的保存图片方式 原创 精华
温州393
发布于 2025-10-23 19:14
浏览
1收藏
在 HarmonyOS 应用开发中,将图片保存到系统相册是常见需求。本文将介绍两种高效实现方案:一是通过网络请求获取图片数据后保存,二是对已加载的 UI 组件进行截图保存。两种方案各有适用场景,开发者可根据实际需求选择。
先展示一下实现效果:

一、权限处理方案
保存图片到相册需涉及文件写入权限,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中进行配置。

{
"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. 组件截图保存(推荐)
对已加载的图片组件进行截图,直接获取图片数据并保存,效率更高:
/**
* 获取已加载的组件的截图(传入组件的组件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) {
// 用户授权,可以继续访问目标操作
}
}
}
}
总结
两种方案各有优势:网络下载适用于需要原始高清图的场景,组件截图则适合快速保存当前显示内容。实际开发中推荐优先使用组件截图方案,其无需重复网络请求,性能更优。权限处理方面,安全控件可减少用户交互步骤,提升体验。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
赞
1
收藏 1
回复
相关推荐



















