【HarmonyOS NEXT】 鸿蒙图片或视频保存相册
在 HarmonyOS NEXT 中,保存图片或视频到相册主要有三种方式,它们的权限模型和适用场景各不相同。下面我将为你详细拆解,并配合代码示例,帮助你理解并选择最合适的方案。
核心方法对比
| 特性 | SaveButton(保存控件) | PhotoViewPicker | photoAccessHelper + 权限 |
|---|---|---|---|
| 权限模型 | 零权限,用户点击控件时临时授权,授权窗口仅弹一次。 | 零权限,拉起系统“保存到”界面,由用户选择保存位置。 | 需申请权限,需在module.json5中配置ohos.permission.WRITE_IMAGEVIDEO等权限,并动态请求。 |
| 交互方式 | 应用内控件,点击即存,无需用户选择保存路径。 | 拉起系统FilePicker界面,用户需二次确认保存路径。 | 静默保存,无额外交互(权限授予后)。 |
| 授权时效 | 授权有时效(如10秒),需在回调中完成核心操作。 | 无时效限制,用户确认后获得目标URI的读写权限。 | 权限长期有效(除非用户手动在设置中关闭)。 |
| 适用场景 | 首选。UI中可放置按钮,用户点击保存图片/小文件。体验最佳,是官方推荐做法。 | 需要用户明确知道文件保存位置,或进行“另存为”操作时。 | 需要后台自动保存、或处理大文件(如>2GB视频)时,用此方案配合分段写入更稳定。 |
方案一:使用 SaveButton 安全控件(推荐)
这是HarmonyOS NEXT主推的方式,能提供最流畅的用户体验,因为它无需应用提前申请任何存储权限,完全由用户点击控件的动作触发临时授权。
1. 核心逻辑与代码
首先,在UI中放置一个SaveButton。当用户点击它且授权成功后,在onClick回调里获取临时授权,并执行保存操作。
typescript
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
// 保存图片的函数,在SaveButton的onClick中调用
async function savePhotoToGallery(context: common.UIAbilityContext, imageBuffer: ArrayBuffer) {
let helper = photoAccessHelper.getPhotoAccessHelper(context);
try {
// 1. 在相册中创建一个图片文件,指定类型和格式
// 注意:此操作需在用户点击SaveButton后的10秒内完成[citation:7]
let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
// 2. 打开文件并写入数据
let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
await fileIo.write(file.fd, imageBuffer);
await fileIo.close(file.fd);
promptAction.showToast({ message: '已保存至相册!' });
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`);
}
}
@Entry
@Component
struct SaveButtonDemo {
build() {
Row() {
Column() {
// 假设这是你要保存的图片
Image($r('app.media.demoImage')).width(300).height(300)
// 保存控件:用户点击即触发保存逻辑,无需任何权限申请
SaveButton()
.padding({ top: 12, bottom: 12, left: 24, right: 24 })
.onClick(async (event, result) => {
if (result === SaveButtonOnClickResult.SUCCESS) {
const context = getContext(this) as common.UIAbilityContext;
// 这里需要将你的图片转换为ArrayBuffer
// 例如从网络下载、从沙箱读取或从PixelMap编码
// const imageBuffer: ArrayBuffer = await getImageBuffer();
// await savePhotoToGallery(context, imageBuffer);
} else {
promptAction.showToast({ message: '授权失败' });
}
})
}
}
}
}
方案二:使用 PhotoViewPicker
这种方案不涉及权限申请,但会拉起系统选择器,让用户自己决定保存位置,适合“另存为”的场景。
1. 核心逻辑与代码
typescript
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
async function saveWithPicker() {
const photoSaveOptions = new picker.PhotoSaveOptions();
// 可指定一个建议的文件名
photoSaveOptions.newFileNames = ["MyImage.jpg"];
const photoViewPicker = new picker.PhotoViewPicker();
try {
// 拉起系统保存界面,用户选择文件夹后返回保存的URI
const photoSaveResult = await photoViewPicker.save(photoSaveOptions);
const uri = photoSaveResult[0];
console.info('Save URI is: ' + uri);
// 拿到URI后,可以像操作普通文件一样写入数据
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
// 这里写入你的图片数据
// let writeLen = fs.writeSync(file.fd, 'your image data');
fs.closeSync(file);
} catch (err) {
console.error(`Save failed, code is ${err.code}, message is ${err.message}`);
}
}
方案三:申请权限 + photoAccessHelper
当需要应用自动、静默地保存文件时(例如自动保存聊天图片),就需要申请ohos.permission.WRITE_IMAGEVIDEO权限。这是一个系统级权限,需要用户在弹窗中授权。
1. 配置权限
在module.json5中添加权限声明:
json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:reason_for_saving_photos",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
2. 动态申请权限并保存
typescript
import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import fs from '@ohos.file.fs';
async function requestPermissionAndSave(context: common.UIAbilityContext, imageBuffer: ArrayBuffer) {
const atManager = abilityAccessCtrl.createAtManager();
const permissions: Array<string> = ['ohos.permission.WRITE_IMAGEVIDEO'];
try {
// 1. 动态申请权限
const result = await atManager.requestPermissionsFromUser(context, permissions);
if (result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 2. 权限已授权,执行保存
let helper = photoAccessHelper.getPhotoAccessHelper(context);
let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(file.fd, imageBuffer);
fs.closeSync(file);
promptAction.showToast({ message: '保存成功' });
}
} catch (err) {
console.error(`Request permission failed, error: ${err.code}`);
}
}
视频保存与“大文件”注意事项
-
方法通用:上述三种方法均适用于视频保存。只需将
PhotoType.IMAGE改为PhotoType.VIDEO,文件后缀改为.mp4即可。 -
大文件处理:对于体积较大的视频(如超过2GB),使用
SaveButton可能会因授权窗口期(10秒)不够用而导致保存失败。-
解决方案:对于此类场景,推荐使用方案三(申请权限模式)。在获得长期权限后,可以通过
fs.openSync等API进行分段写入,稳定地将大文件保存到相册。
-
总结与建议
-
首选
SaveButton:对于绝大多数保存图片和小视频的场景,这是最佳实践,提供了最友好的用户体验。 -
需要用户确认位置时选
PhotoViewPicker:当你需要用户明确知道文件存到了哪里,或者提供“另存为”功能时,使用Picker。 -
后台自动保存或处理超大文件选
photoAccessHelper + 权限:这种方式虽然需要申请权限,但提供了最灵活和持久的访问能力,适合复杂或后台任务。
更多推荐

所有评论(0)