Flutter如何从URL自动下载文件到Android/iOS下载目录
Flutter双端直链文件下载到系统Downloads目录实现方案
完全可以实现双端点击即触发、无需用户额外选路径的直链文件下载需求,核心是适配双端存储权限规则、正确获取系统公共下载目录路径即可。
依赖选型
选用轻量稳定的通用包即可实现需求,不需要引入过重的定制封装:
dio:处理文件下载逻辑,支持进度监听、断点续传,适配各类直链资源path_provider:获取双端系统标准目录路径permission_handler:适配安卓低版本存储权限申请逻辑path:处理文件路径拼接、文件名解析device_info_plus:准确获取安卓系统版本,避免权限适配错误
双端前置配置
Android端
在android/app/src/main/AndroidManifest.xml中添加如下配置:
- 仅对安卓9(API 28)及以下版本申请写存储权限,安卓10及以上采用分区存储机制,无需申请存储权限即可写入公共Downloads目录:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <uses-permission android:name="android.permission.INTERNET"/>
- 若下载后需要触发打开文件的操作,额外配置FileProvider即可,纯下载保存场景不需要额外配置。
iOS端
iOS沙盒机制限制下没有全局公共Downloads目录,配置后下载文件会显示在系统「文件」App的应用专属目录中,用户可直接访问,符合App Store审核规则。在ios/Runner/Info.plist中添加:
<key>LSSupportsOpeningDocumentsInPlace</key> <true/> <key>UIFileSharingEnabled</key> <true/>
iOS不需要额外申请存储权限,配置后写入应用Documents目录的文件会自动对系统Files可见。
核心实现代码
- 先在
pubspec.yaml中添加依赖:
dependencies: flutter: sdk: flutter dio: ^5.4.0 path_provider: ^2.1.2 permission_handler: ^11.2.0 path: ^1.9.0 device_info_plus: ^9.1.2
- 编写下载工具方法:
import 'dart:io'; import 'package:dio/dio.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; /// [url] 直链下载地址 /// [onProgress] 可选下载进度回调,返回0-1的进度值 Future<String> downloadToSystemDownloads( String url, { void Function(double progress)? onProgress, }) async { // 安卓低版本权限校验 if (Platform.isAndroid) { final androidInfo = await DeviceInfoPlugin().androidInfo; if (androidInfo.version.sdkInt <= 28) { final permStatus = await Permission.storage.request(); if (!permStatus.isGranted) { throw Exception('存储权限未授予,无法下载文件'); } } } // 获取对应平台的下载目录 late String downloadDir; if (Platform.isAndroid) { final extDir = await getExternalStoragePublicDirectory(StorageDirectory.downloads); downloadDir = extDir?.path ?? '/storage/emulated/0/Download'; } else if (Platform.isIOS) { final appDocDir = await getApplicationDocumentsDirectory(); downloadDir = appDocDir.path; } // 从直链解析文件名 final uri = Uri.parse(url); final fileName = path.basename(uri.path); final saveFullPath = path.join(downloadDir, fileName); // 执行下载 final dio = Dio(); await dio.download( url, saveFullPath, onReceiveProgress: (received, total) { if (total > 0 && onProgress != null) { onProgress(received / total); } }, ); return saveFullPath; }
- 点击触发下载的调用示例:
ElevatedButton( onPressed: () async { try { final savePath = await downloadToSystemDownloads( 'https://******/image.jpg', onProgress: (progress) { // 可自行绑定进度条组件更新UI debugPrint('下载进度: ${(progress * 100).toStringAsFixed(0)}%'); }, ); debugPrint('文件已保存至: $savePath'); } catch (e) { debugPrint('下载失败: $e'); } }, child: const Text('点击下载文件'), )
补充说明
- 如果需要大文件后台下载能力,可替换为
flutter_downloader包,该包封装了双端原生后台下载服务,同样支持配置保存路径到系统Downloads目录,适合长时下载场景 - 安卓9及以下版本下载后如果文件未在系统文件管理器立刻显示,可主动调用MediaScanner扫描新文件路径触发系统媒体库刷新
- iOS端下载的文件用户可在系统「文件」App中直接查看、编辑、移动,不需要额外操作,符合平台交互规范
内容的提问来源于stack exchange,提问作者Ammar Mohamed
相关产品推荐
相关产品推荐

