FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
  • 内功心法

    • 《深入浅出设计模式Java版》笔记
前端
后端
  • 产品
  • 代理
  • 提效
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
  • 内功心法

    • 《深入浅出设计模式Java版》笔记
前端
后端
  • 产品
  • 代理
  • 提效
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

    • Dart - 抽象类的实例化
    • Flutter - 打印好用的Debug日志
    • Flutter - 混合开发
    • Flutter - 解决混合开发iOS脚本打包遇到的问题
    • Flutter - 低版本在iOS14上遇到的问题与解决方案
    • Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题
    • Flutter - 实现列表上下拉切换header
    • Flutter - 获取ListView当前正在显示的Widget信息
    • Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
    • Flutter - 快速实现聊天会话列表的效果,完美💯
    • Flutter - 聊天输入框更新文本时的必备优化点🔖
    • Flutter - 我给官方提PR,解决run命令卡住问题 😃
    • Flutter - 探索run命令到底做了什么 🤔
    • Flutter - 引擎调试(iOS篇)🛠
    • Flutter - 引擎调试bug到提交PR实战 🐞
    • Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
    • Flutter - 瀑布流交替播放视频 🎞
    • Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
    • Flutter - 滚动视图中的表单防遮挡 🗒
    • Flutter - 秒杀1/2曝光统计 📊
    • 一天内加入 Flutter 和 FlutterCandies 两大组织是什么体验 🧐
    • Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
    • Flutter - 混编项目集成Shorebird热更新🐦(安卓篇)
    • Flutter - 混编项目集成Shorebird热更新🐦(iOS篇)
    • Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
    • Flutter - 升级3.19之后页面多次rebuild?🤨
    • Flutter - 热更新 Shorebird 1.0 正式版来了 🐦
    • Flutter - 使用Pigeon实现视频缓存插件 🐌
    • Flutter - 轻松搞定屏幕旋转功能 😎
    • Flutter - 解决Connection closed before full header was received
    • Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
    • Flutter - 支持观察NestedScrollView,兼容性更强 😈
    • Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
    • Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
    • Flutter - 轻松实现PageView卡片偏移效果
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
    • Flutter - GetX Helper 助你规范应用 tag
    • Flutter - GetX Helper 如何应用于旧页面
    • Flutter - 聊天面板库动画生硬?这次让你丝滑个够
    • Flutter - 使用本地 DevTools 验证 SVG 加载优化
    • Flutter - 详情页 TabBar 与模块联动?秒了!
    • Flutter - 详情页初始锚点与优化
    • Flutter - Melos Pub workspaces 实践
    • Flutter webview 崩溃率上升怎么办?我的分析与解决方案
    • Flutter 多仓库本地 Monorepo 方案与体验优化
    • Flutter 拖拉对比组件,换装图片前后对比必备
    • Flutter 3.44 插件内置 Kotlin (KGP) 双向兼容适配指南
    • Flutter 无障碍开发实战:玩转 Semantics 解决视障用户使用痛点
    • 玩转 WebSocket:从原理到 Flutter 实战
    • 别在 Flutter 的 main() 里乱锁屏幕方向,小心 iPad 分屏功能被你搞没了
    • Flutter 3.44 性能飞跃:深度解析 Android Platform View 的 HCPP 新特性
    • 给 Flutter 插件加上 Swift Package Manager 支持
    • 彻底搞懂 Dart Stream:从底层原理到 Flutter 实战与面试题集
    • 别被“Flutter 传感器延迟 150ms”带偏了:这可能只是你的实现方式错了
    • StatefulWidget 里的隐形炸弹:为什么不要在 State 类中使用 context.mounted
    • 深入理解 Flutter 架构:从 Widget 到 GPU 像素的全链路解析
    • 深度拆解 Dart 事件循环:从面试题看清 Microtask 与 Event Queue 的执行顺序
    • Flutter FocusNode 实战指南:玩转键盘焦点与用户输入体验
    • iOS 27 强制要求 UISceneDelegate:UIKit 和 Flutter 开发者该如何应对?
    • Flutter 实战:为你的 App 增加桌面快捷方式 (Quick Actions)
    • 玩转 Flutter 项目依赖:从 pubspec.yaml 到实战包管理
    • Flutter 居然能直接跑在 Apple Watch 上了?玩转 flutter-watchos
    • 玩转 Dart typedef:不仅是函数别名那么简单
    • Impeller 时代:Shader Jank 消失了,但渲染性能的战场也变了
    • 玩转 Flutter 中的 Stack 与 Positioned:解决 UI 重叠问题的实战指南
    • Flutter 3.47 与 Dart 3.13:你必须了解的迁移指南
    • Flutter 实战:图片上传前的压缩优化,解决带宽与存储的“大户”问题
    • Flutter + Unity 混合开发:用 unity_kit 实现高效的双向通信
    • Flutter 实战:使用 local_auth 实现生物识别(指纹 & Face ID)
    • Flutter 实战:如何轻松实现图片与视频保存到手机相册
      • 一、为什么这件事没那么简单
      • 二、上手实战
        • 1、基础配置
        • 2、保存图片到相册
        • 3、保存视频到相册
        • 4、进阶:自定义相册与批量保存
      • 三、避坑指南:权限与细节
        • 1、权限处理
        • 2、如何避免重复下载
        • 3、如何获取保存后的位置
      • 四、总结
    • 从代码复用走向生态:Flutter Dart Package 实战全指南
  • 移动端
  • Flutter开发
GitLqr
2026-08-26
目录

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

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

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

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

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

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

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

# 二、上手实战

# 1、基础配置

首先,在 pubspec.yaml 中引入 saver_gallery:

dependencies:
  saver_gallery: ^5.1.0

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

# 2、保存图片到相册

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

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),然后再进行移动或保存。

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

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

// 批量保存图片
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 在尝试写入相册时会直接闪退:

<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。如果你不想在 Android 的 Scoped Storage 和 iOS 的 Info.plist 规则里“摸黑瞎改”,这个库确实是个不错的选择。

#相册#图片#视频#Gallery#saver_gallery
上次更新: 2026/10/13, 16:17:03
Flutter 实战:使用 local_auth 实现生物识别(指纹 & Face ID)
从代码复用走向生态:Flutter Dart Package 实战全指南

← Flutter 实战:使用 local_auth 实现生物识别(指纹 & Face ID) 从代码复用走向生态:Flutter Dart Package 实战全指南→

最近更新
01
Draw Things 换装 LoRA 实战:用 Silo 秒装,同一提示词对比出图
10-13
02
从代码复用走向生态:Flutter Dart Package 实战全指南
08-28
03
Flutter 实战:使用 local_auth 实现生物识别(指纹 & Face ID)
08-25
更多文章>
Theme by Vdoing | Copyright © 2020-2026 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×