Flutter 实战:如何轻松实现图片与视频保存到手机相册

欢迎关注微信公众号:FSA全栈行动 👋

一、为什么这件事没那么简单

在做 Flutter 开发时,下载一个图片或者视频其实并不难。最让人头疼的往往是:下载完之后,怎么让这些文件乖乖出现在用户的手机相册(Gallery)里。

乍一看这事儿挺简单:下载文件,存到本地,完事。

但实际操作起来,AndroidiOS 的存储逻辑完全不同。Android 的权限控制随着版本升级变了又变;有时候你还希望把文件分门别类存进特定的相册,或者想避开重复保存。

以前遇到这种需求,我可能得针对 AndroidiOS 分别写原生代码去处理,折腾起来非常麻烦。最近我在找更轻量化的方案时,发现了 saver_gallery 这个库。它能直接从 Flutter 端实现保存图片、视频到相册,还支持自定义相册路径和批量保存,非常省心。

二、上手实战

1、基础配置

首先,在 pubspec.yaml 中引入 saver_gallery

YAML 复制代码
dependencies:
  saver_gallery: ^5.1.0

然后在终端跑一下 flutter pub get 即可。

2、保存图片到相册

最常见的场景就是从 API 获取图片字节流(bytes),然后直接存入相册。这里我结合 Dio 来演示一下:

Dart 复制代码
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:saver_gallery/saver_gallery.dart';

Future<void> saveImage() async {
  // 1. 下载图片字节流
  final response = await Dio().get(
    'https://example.com/image.jpg',
    options: Options(responseType: ResponseType.bytes),
  );

  // 2. 写入相册
  final result = await SaverGallery.saveImage(
    Uint8List.fromList(response.data),
    quality: 100,
    fileName: 'my_photo.jpg',
    albumPath: 'MyApp/Images', // 自定义相册名
    skipIfExists: false,       // 是否跳过已存在的文件
  );

  print('保存结果: $result');
}

这里 albumPath 特别有用,它可以帮你把图片归类到指定的文件夹里,而不是全都堆在相机默认目录下。

3、保存视频到相册

视频的逻辑跟图片略有不同。因为视频文件通常比较大,我们通常先把它下载到设备的临时目录(Temporary Directory),然后再进行移动或保存。

Dart 复制代码
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:saver_gallery/saver_gallery.dart';

Future<void> saveVideo() async {
  // 1. 获取临时目录
  final tempDir = await getTemporaryDirectory();
  final videoPath = '${tempDir.path}/sample_video.mp4';

  // 2. 先下载到临时文件
  await Dio().download('https://example.com/video.mp4', videoPath);

  // 3. 移动到相册
  final result = await SaverGallery.saveFile(
    filePath: videoPath,
    fileName: 'my_video.mp4',
    albumPath: 'MyApp/Videos',
    skipIfExists: false,
  );

  print('视频保存结果: $result');
}

4、进阶:自定义相册与批量保存

如果你需要一次性下载一批图片(比如从一组壁纸里选),用 saveImages 这种批量接口效率更高。

需求场景 使用方法 核心逻辑
单张图片 saveImage 直接传入 Uint8List
单张视频 saveFile 需要先下载到本地路径再调用
多张图片 saveImages 构造 List
多段视频 saveFiles 构造 List

批量保存的示例代码如下:

Dart 复制代码
// 批量保存图片
final images = [
  SaveImageData(bytes: bytes1, fileName: '1.jpg', albumPath: 'MyAlbum'),
  SaveImageData(bytes: bytes2, fileName: '2.jpg', albumPath: 'MyAlbum'),
];
await SaverGallery.saveImages(images);

三、避坑指南:权限与细节

搞定功能后,最容易在"权限"上栽跟头。

1、权限处理

存储权限在不同版本的 Android 上表现非常复杂。在旧版本上,你可能需要申请 WRITE_EXTERNAL_STORAGE;而在 Android 10 及更高版本,由于"分区存储(Scoped Storage)"的引入,逻辑变了很多。

针对这种多版本差异,我建议配合 permission_handler 库来统一管理权限。

对于 iOS 端,你需要在 Info.plist 中添加以下配置,否则 App 在尝试写入相册时会直接闪退:

XML 复制代码
<key>NSPhotoLibraryAddUsageDescription</key>
<string>我们需要访问您的相册以便保存图片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>我们需要访问您的相册以便保存图片</string>

2、如何避免重复下载

这里有一个很实用的参数:skipIfExists: true

如果你的 App 支持重复点击下载,或者用户可能多次触发同一张图的保存,开启这个开关可以有效防止手机相册里出现一堆重复的、一模一样的图片。

3、如何获取保存后的位置

保存成功后,SaverGallery 会返回一个 result 对象。

注意,这个返回的 savedUri 在不同平台上表现不同:

  • 新版 Android 可能返回 content:// 类型的 URI。

  • 旧版可能返回 file:// 类型的路径。

  • iOS 则返回其专属的媒体资源 ID。

所以,在处理返回结果时,把它当成一个"平台媒体标识符"即可,不要强行把它当成普通文件系统路径去处理。

四、总结

保存媒体文件看似是个小功能,但涉及到权限、分区存储、自定义相册以及多平台兼容性时,细节确实不少。

saver_gallery 给我的感觉是:它把复杂的原生调用封装成了一套非常直观的 API。如果你不想在 AndroidScoped StorageiOSInfo.plist 规则里"摸黑瞎改",这个库确实是个不错的选择。

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~

相关推荐
程序员老刘6 小时前
Android Studio Quail 4发布,看日志我以为谷歌放弃Flutter了
flutter·android studio·ai编程
Crazy_MT7 小时前
Flutter 本地大模型实战:做一个自然语言记账工具
flutter·llm·ai编程
SL_staff8 小时前
战略落地的最后一公里:从目标到个人日历的自动化链路实现
java·github·全栈
Ticnix9 小时前
RAG 的坑不在检索,在"对齐":pgvector 实战手记
python·agent·全栈
嵩风抚13 小时前
一款HK银行APP的业务+技术
android·flutter·react native·html5
不羁的木木15 小时前
给鸿蒙 App 增加广播收发能力 —— flutter_broadcasts 的鸿蒙使用指南
flutter·harmonyos
不羁的木木15 小时前
给鸿蒙 App 增加打开外部网页能力 —— flutter_web_browser 的鸿蒙使用指南
前端·flutter·harmonyos
EatFan16 小时前
多租户系统到底怎么设计?从 tenant_id 到 JWT 租户隔离的真实实践
前端·后端·全栈·多租户
EatFan16 小时前
我为什么把患者与病例从 1:1 改成 1:N?一次真实数据库模型重构记录
数据库·后端·重构·健康医疗·全栈
安好说AI16 小时前
Flutter 三方库 sound_mode 的鸿蒙化适配指南:免权限读取与受限写入的契约对齐
flutter·harmonyos·鸿蒙