更新记录

0.1.4(2026-08-13)

  • 修复视频保存到相册后不排在最新位置:0.1.3 的 EXIF 改写只对 JPEG 生效,MP4 没有 EXIF,媒体扫描器改从 moov/mvhd 里的 creation_time 取拍摄时间,于是仍按原始拍摄时间排序。
  • 新增 MP4/MOV 的 mvhd 时间改写:写入相册后、清除 IS_PENDING 之前,按 ISO-BMFF 盒结构定位 moovmvhd,把 creation_time / modification_time 就地改写成「现在」(version 0 写 32 位、version 1 写 64 位,起点为 1904-01-01 UTC)。只改这 8/16 个字节,文件长度和其余内容完全不动,也不产生大文件拷贝。
  • 覆盖 video/mp4video/quicktimevideo/x-m4v;其它容器不改写,退回原有行为。改写失败只记日志,不影响保存结果。
  • Android 9- 的公共目录路径同样改写,并同步文件修改时间。

平台兼容性

uni-app(4.25)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - 5.0 ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(4.25)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - - -

zoo-media-saver

把 App 端本地文件真正写进系统相册 / 系统下载目录的 UTS 插件。

为什么需要它:uni-app 的 uni.saveImageToPhotosAlbumuni.saveVideoToPhotosAlbumplus.gallery.save 在部分机型上存在「接口返回成功、但相册里找不到文件」的假成功问题(媒体库没有正确收录,或收录后按文件自带的拍摄时间排序,混在旧照片里找不到)。本插件直接调用原生 API 写入:

  • Android 走 MediaStore(API 29+)或公共目录 + 媒体扫描(API 21-28);
  • iOS 走 PhotoKit PHAssetCreationRequest,并把 creationDate 显式写成「现在」,保证保存的内容排在相册最新位置。

特性

  • 图片 / 视频写入系统相册,支持自定义文件名
  • 重名自动追加 (1)(2) 序号,不覆盖已有文件;
  • 保存后排在相册最新位置(不沿用文件自带的拍摄时间);
  • 任意类型文件写入系统下载目录(Android Download/);
  • iOS 无公共下载目录,文件落进应用 Documents/Download,配合宿主开启 UIFileSharingEnabled 后可在系统「文件」App 的「我的 iPhone」中查看、分享;
  • 只申请最小权限:Android 10+ 零权限,iOS 只申请「添加照片」级别;
  • 统一错误码,成功/失败都有明确回调,不存在假成功。

快速开始

import { saveToAlbum, saveToDownloads, getMediaSaverVersion } from '@/uni_modules/zoo-media-saver'

console.log('插件版本:', getMediaSaverVersion())

// 保存图片/视频到相册
saveToAlbum({
  filePath: '/storage/emulated/0/Android/data/<包名>/doc/download/a.jpg', // 沙盒绝对路径
  fileName: '巡检报告-20260811.jpg', // 可选,传空沿用源文件名
  mediaType: 'image', // 可选,'image' | 'video',传空按扩展名推断
  success: (res) => {
    console.log('已保存到相册:', res.fileName, res.target)
  },
  fail: (err) => {
    console.error('保存失败:', err.errCode, err.errMsg)
  }
})

// 保存任意文件到下载目录
saveToDownloads({
  filePath: '/var/mobile/Containers/Data/Application/<uuid>/Documents/a.pdf',
  fileName: '巡检报告.pdf',
  success: (res) => {
    console.log('已保存到下载目录:', res.fileName, res.filePath)
  },
  fail: (err) => {
    console.error('保存失败:', err.errCode, err.errMsg)
  }
})

注意:filePath 必须是本地沙盒文件uni.downloadFile 的 tempFilePath 可用 plus.io.convertLocalFileSystemURL 转成绝对路径),不支持直接传网络 URL。

兼容版本与基座边界

  • Android:minSdkVersion 21(Android 5.0)及以上;
  • iOS:deploymentTarget 12.0 及以上;
  • 仅支持 uni-app App 端(vue2 / vue3 / nvue 均可),不支持小程序与 H5;
  • 插件含原生代码,必须制作自定义基座或云打包后才能生效,标准基座运行会报「当前运行环境不支持保存文件」类错误。

参数

saveToAlbum(options)

参数 类型 必填 说明
filePath string 待保存的本地文件路径,沙盒绝对路径(可带 file:// 前缀,插件内会自行归一化)
fileName string 期望的落地文件名(含扩展名);传空沿用源文件名;系统内重名时自动追加序号
mediaType string 'image''video';传空按扩展名推断,推断失败返回 9011006
success function 保存成功回调,参数为 ZooSaveResult
fail function 保存失败回调,参数为 ZooSaveFail
complete function 完成回调(成功失败都会触发)

saveToDownloads(options)

参数 类型 必填 说明
filePath string 待保存的本地文件路径,沙盒绝对路径
fileName string 期望的落地文件名(含扩展名);传空沿用源文件名;重名自动追加序号
success / fail / complete function 同上

返回

ZooSaveResult

字段 类型 说明
fileName string 实际落地的文件名(重名追加序号后以这里返回的为准)
filePath string Android 为媒体库 content:// URI;iOS 相册保存为 asset localIdentifier,下载目录保存为沙盒绝对路径
target string 落地位置:album-image / album-video / downloads

错误码

errCode 含义
9011001 当前运行环境不支持保存文件(如未打自定义基座)
9011002 源文件不存在或读取失败
9011003 写入系统相册失败
9011004 相册或存储权限被拒绝,请在系统设置中允许后重试
9011005 保存参数错误(如 filePath 为空)
9011006 无法识别的媒体类型,请显式传入 mediaType
9011007 写入系统下载目录失败

宿主权限声明

Android

  • Android 10(API 29)及以上:无需任何权限(MediaStore 分区存储);
  • Android 9 及以下:需在 manifest 中声明
"permissions": [
  "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>"
]

iOS

  • 保存相册需配置隐私描述(缺失会导致保存失败):
"NSPhotoLibraryAddUsageDescription": "需要保存图片和视频到您的相册"
  • 若使用 saveToDownloads 并希望在系统「文件」App 中查看,需开启文件共享:
"UIFileSharingEnabled": true,
"LSSupportsOpeningDocumentsInPlace": true

各平台行为

Android

系统版本 相册保存 下载目录保存
API 29+ MediaStore 写入 DCIM/(图片)、Movies/(视频) MediaStore 写入 Download/
API 21-28 复制到公共 DCIM/Movies/ 后触发媒体扫描 复制到公共 Download/ 目录

保存时会显式写入 DATE_ADDEDDATE_MODIFIEDdatetaken 为当前时间,相册按最新排序。

iOS

  • 相册:PhotoKit 写入,creationDate = Date(),排在最新;只申请「添加照片」权限(iOS 14+ 为 .addOnly),不需要读取整个相册;
  • 下载目录:iOS 没有公共下载目录,文件复制到应用 Documents/Download/;宿主开启 UIFileSharingEnabled 后,在系统「文件」App →「我的 iPhone」→ 对应应用文件夹中可见,也可在「文件」App 里分享出去。

FAQ

Q:调用后 success 了,但相册里看不到? 本插件的写路径就是为解决该问题设计的:Android 走 MediaStore 显式插入并写好时间字段,iOS 走 PhotoKit 并显式设置 creationDate。如果仍看不到,先确认 getMediaSaverVersion() 能取到版本号(取不到说明自定义基座里没有打进本插件)。

Q:标准基座可以直接运行吗? 不可以。插件含原生代码,必须「运行 → 运行到手机或模拟器 → 制作自定义调试基座」后再运行,或云打包。

Q:fileName 传中文、特殊字符可以吗? 可以。Android MediaStore 与 iOS 下载目录都支持中文文件名;iOS 相册不暴露文件名概念(相册按时间排序),传入的 fileName 会原样回传便于业务记录。

Q:权限被用户拒绝过怎么办? 返回 9011004。业务侧可引导用户去系统设置开启(Android 9- 的存储权限、iOS 的照片「添加」权限)。

授权说明

插件不采集、不上传任何数据,只把宿主指定的本地文件写入系统相册或下载目录。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android 10 及以上无需权限;Android 9 及以下需宿主声明 WRITE_EXTERNAL_STORAGE;iOS 保存相册需宿主声明 NSPhotoLibraryAddUsageDescription

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集、不上传任何数据,只把宿主指定的本地文件写入系统相册或下载目录

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。