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等文章, 可能有你想要了解的技能知识点哦~

相关推荐
大龄秃头程序员2 小时前
【小记】第一次全栈踩坑 WebSocket 404
flutter
HjhIron5 小时前
全栈项目容器化实战:从 React + NestJS 到 Docker 部署与跨域处理
全栈
大龄秃头程序员6 小时前
Dart/Flutter StatefulWidget 生命周期 + ChangeNotifier Listener 泄漏的经典坑。
flutter
大龄秃头程序员8 小时前
Flutter踩坑记-WebSocket 通知挤占事件循环
flutter
万敏8 小时前
Vue3 全栈实战第七周:错误处理与请求层封装实战记录
vue.js·node.js·全栈
恋猫de小郭11 小时前
Gradle 9.7.0 将提速 Android 构建,Sync 提升接近一倍
android·前端·flutter
iFlyCai11 小时前
Flutter三棵树核心详解之Widget树完全解析(二)
flutter·statelesswidget
GitLqr1 天前
Flutter 实战:使用 local_auth 实现生物识别(指纹/Face ID)
安全·flutter·全栈
大龄秃头程序员1 天前
【Flutter 性能踩坑小记】相册选个图卡了
flutter