鸿蒙中如何保存图片_鸿蒙开发如何复制沙箱路径图片保存到本地路径
注:适用版本(HarmonyOS NEXT/5.0/API13+)
鸿蒙系统实现图片保存至相册全流程详解
一、技术实现流程概述
本业务需完成从组件截图到持久化存储的完整链路,主要分为四大阶段:
-
组件截图生成
-
图片数据转换存储
-
沙箱文件转公有文件
-
媒体库写入与权限管理
二、核心实现步骤
步骤1:获取组件截图对象
步骤2:JPEG格式转换与缓存写入
选择JPEG格式的考量(代码有标注):
-
采用有损压缩平衡画质与体积
-
官方推荐设置98%质量参数(0-100范围)
-
避免PNG的无损压缩导致体积过大
/** * 保存图片到沙箱, 再从沙箱中读取写入相册 */ async saveImage(){ // 1. 根据组件的id生成截图对象 const pixelMap = await componentSnapshot.get(\'share\') // 2. 借助ImagePacker去把图片对象生成二级制数据流 const imagePacker = image.createImagePacker() const arrayBuffer = await imagePacker.packToData(pixelMap,{ format: \"image/jpeg\", quality: 98 }) // 3. 借助fileIo读写文件 // 3.1 获取上下文 const ctx = getContext(this) // 3.2 获取沙箱中存图的路径 const imagePath = ctx.cacheDir + \'/\' + Date.now() + \'jpeg\' // 3.3 以 创建 或 读写 的模式打开文件(没有则创建并打开, 有则打开) const file = fileIo.openSync(imagePath,fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) // 3.4 同步写入二级制数据流到文件中 fileIo.writeSync(file.fd,arrayBuffer) // 3.5 同步去关闭文件 fileIo.closeSync(file.fd) promptAction.showToast({message: \'写入成功\'}) }
步骤3:沙箱文件转存相册
这个时候是没用权限的 需要使用 SaveButton组件 来授权 组件时间一般有30分钟左右 授权一次三十分钟之内不用授权
// 3.2 获取沙箱中存图的路径 const imagePath = ctx.cacheDir + \'/\' + Date.now() + \'jpeg\' // 3.3 以 创建 或 读写 的模式打开文件(没有则创建并打开, 有则打开) const file = fileIo.openSync(imagePath,fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) // 3.4 同步写入二级制数据流到文件中 fileIo.writeSync(file.fd,arrayBuffer) // 3.5 同步去关闭文件 fileIo.closeSync(file.fd) // 4. 把沙箱中的文件写入相册 // 4.1 获取资源文件的uri地址 3.2 获取存在沙箱的 路径 const imgUrl = fileUri.getUriFromPath(imagePath) // 4.2 进行图片资产变更(私有->公有) 要私有变公有 要先获取 再变更,变更看文档 复制 后传参数 上下文和url的地址 const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx,imgUrl) // 4.3 提交媒体变更请求 // 4.3.1 获取相册管理模块的实例 const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx) // 4.3.2 调用变更方法 await phAccessHelper.applyChanges(assetChangeRequest) promptAction.showToast({message: \'图片写入成功\'})
步骤4:权限动态申请与安全组件
SaveButton({ icon: SaveIconStyle.FULL_FILLED,//保存按钮展示填充样式图标。 text: SaveDescription.SAVE_IMAGE,//保存按钮的文字描述为“保存图片”。 buttonType: ButtonType.Normal //普通按钮(默认不带圆角)。 })... //参数可复制文档实例 .onClick((event:ClickEvent, result:SaveButtonOnClickResult)=>{ //恒等于0 if(result == SaveButtonOnClickResult.SUCCESS){ this.saveImage() } })
三、关键注意事项
-
路径有效性:沙箱路径需要使用context获取标准目录
-
内存管理:及时释放PixelMap资源避免内存泄漏
-
异常处理:文件IO操作需包裹try-catch块
-
权限时效:WRITE_IMAGEVIDEO权限默认有效期30分钟
-
格式兼容性:确保设备支持JPEG编码格式
四、性能优化建议
-
大图处理建议分块写入
-
频繁操作建议使用Worker线程
-
可添加进度提示提升用户体验
-
建议对重复保存操作做防抖处理
通过以上标准化实现流程,我们可高效完成鸿蒙系统的图片存储业务开发,同时保证应用的稳定性和用户体验。实际开发中需根据具体业务场景调整质量参数和异常处理策略。
总结:
要完成鸿蒙中保存图片业务可以分为以下步骤进行实现:
首先我们要用componentSnapshot组件的get方法获取id生成截图对象
然后借助ImagePacker把图片转化为JPEG二进制数据流(问为什么是JPEG格式1:平衡画质和体积2:官方文档设置98%质量参数避免过度压缩,保证图片清晰度)
这个时候图片数据已经写入编译器的缓存目录了,我们接着需要获取沙箱中存放图片的路径这时还需要借助fileIo文件管理组件 创建或读写的方式打开文件之后同步写入二进制数据中
将图片存储到沙箱之后 这个时候图片是私有的 我们需要把私有变为共有然后获取相册管理权限 最终就可以实现了
接下来把沙箱中的文件写入相册中(先获取上文中写好的url地址,用fileUri.getUriFromPath()获取),用photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(\'上下文\',\'url地址\')变私为公
公有完毕之后就可以提交媒体变更请求 采用applyChanges方法(需要再model.json5中配置权限:permission: ohos.permission.WRITE_IMAGEVIDEO)
这里配合使用SaveButton安全组件获取短时期权限,唤起系统的相册授权(一般是30分钟内不用重复授权)
最终写入相册就完成了此业务
import { UserStoreKey } from \'../../commons/utils/Auth\'import { QuestionDetail, User } from \'../../models\'import { componentSnapshot, promptAction } from \'@kit.ArkUI\'import image from \'@ohos.multimedia.image\'import { fileIo, fileUri } from \'@kit.CoreFileKit\'import { photoAccessHelper } from \'@kit.MediaLibraryKit\'@CustomDialogexport struct QuestionShareDialog { @Prop item: QuestionDetail @StorageProp(UserStoreKey) user: User = {} as User /** * 保存图片到沙箱, 再从沙箱中读取写入相册 */ async saveImage(){ // 1. 根据组件的id生成截图对象 const pixelMap = await componentSnapshot.get(\'share\') // 2. 借助ImagePacker去把图片对象生成二级制数据流 const imagePacker = image.createImagePacker() const arrayBuffer = await imagePacker.packToData(pixelMap,{ format: \"image/jpeg\", quality: 98 }) // 3. 借助fileIo读写文件 // 3.1 获取上下文 const ctx = getContext(this) // 3.2 获取沙箱中存图的路径 const imagePath = ctx.cacheDir + \'/\' + Date.now() + \'.jpeg\' // 3.3 以 创建 或 读写 的模式打开文件(没有则创建并打开, 有则打开) const file = fileIo.openSync(imagePath,fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE) // 3.4 同步写入二级制数据流到文件中 fileIo.writeSync(file.fd,arrayBuffer) // 3.5 同步去关闭文件 fileIo.closeSync(file.fd) // 4. 把沙箱中的文件写入相册 // 4.1 获取资源文件的uri地址 3.2 获取存在沙箱的 路径 const imgUrl = fileUri.getUriFromPath(imagePath) // 4.2 进行图片资产变更(私有->公有) 要私有变公有 要先获取 再变更,变更看文档 复制 后传参数 上下文和url的地址 const assetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(ctx, imgUrl); // 4.3 提交媒体变更请求 // 4.3.1 获取相册管理模块的实例 const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(ctx) // 4.3.2 调用变更方法 // 需要配置权限: permission: ohos.permission.WRITE_IMAGEVIDEO await phAccessHelper.applyChanges(assetChangeRequest); // 5. 关闭弹窗 this.controller.close() promptAction.showToast({message: \'图片写入相册成功\'}) } controller: CustomDialogController build() { Stack({ alignContent: Alignment.BottomEnd }) { Column({ space: 20 }) { Image($r(\'app.media.ic_interview_logo\')) .width(40) .height(40) Text(\'面试通,搞定企业面试题\') Divider() .strokeWidth(0.5) .color($r(\'app.color.common_gray_border\')) Text(\'大厂面试题:\' + this.item.stem) .fontSize(12) .maxLines(2) .fontWeight(600) .width(\'100%\') .lineHeight(24) .textOverflow({ overflow: TextOverflow.Ellipsis }) QRCode(this.item.id) .width(160) .height(160) .alignSelf(ItemAlign.Center) Text(\'扫码查看答案\') .fontSize(12) .alignSelf(ItemAlign.Center) Blank() Text(\'分享来自:\' + this.user.nickName || this.user.username) .fontSize(12) } .id(\'share\') // 用于后续截图使用 .padding(20) .alignItems(HorizontalAlign.Start) .width(300) .height(500) .backgroundColor($r(\'app.color.white\')) Row() { SaveButton({ icon: SaveIconStyle.FULL_FILLED,//保存按钮展示填充样式图标。 text: SaveDescription.SAVE_IMAGE,//保存按钮的文字描述为“保存图片”。 buttonType: ButtonType.Normal //普通按钮(默认不带圆角)。 }) .fontColor($r(\'app.color.white\')) .fontSize(14) .padding(12) .backgroundColor($r(\'app.color.common_main_color\')) .onClick((event:ClickEvent, result:SaveButtonOnClickResult)=>{ if(result == SaveButtonOnClickResult.SUCCESS){ this.saveImage() } }) } .borderRadius({ topLeft: 8 }) .clip(true) } .borderRadius(8) .clip(true) }}
以上代码只是单个文件夹的流程图,如需总代码可留下关注私信我~