iOS端flutter_downloader下载文件后无法找到的问题
iOS使用flutter_downloader下载文件后文件应用无法找到的解决方法
问题场景
在iOS平台通过flutter_downloader插件完成文件下载,日志确认文件已保存至应用沙箱的Documents目录(路径/var/mobile/Containers/Data/Application/014DCD70-5E34-41A3-95E0-868FEA48A189/Documents),但设备自带的「文件」应用始终无法找到该文件。
下载任务代码
final taskId = await FlutterDownloader.enqueue( url: ref.read(resumeUrlSD), headers: {}, // optional: header send with url (auth token etc) // savedDir: externalDir!.path, savedDir: _localPath, showNotification: true, openFileFromNotification: true, saveInPublicStorage: _saveInPublicStorage, );
下载任务初始化代码
late String _localPath; late bool _permissionReady; final bool _saveInPublicStorage = true; final ReceivePort _port = ReceivePort(); bool hasResume = false; @override void initState() { super.initState(); _bindBackgroundIsolate(); FlutterDownloader.registerCallback(downloadCallback, step: 1); _prepare(); } @pragma('vm:entry-point') static void downloadCallback( String id, DownloadTaskStatus status, int progress, ) { print( 'Callback on background isolate: ' 'task ($id) is in status ($status) and process ($progress)', ); IsolateNameServer.lookupPortByName('downloader_send_port') ?.send([id, status, progress]); if(status.toString() == "DownloadTaskStatus(3)" && progress == 100){ print('2124:DOWNLOAD COMPLETE'); FlutterDownloader.open(taskId: id); } } @override void dispose() { _unbindBackgroundIsolate(); super.dispose(); } void _bindBackgroundIsolate() { final isSuccess = IsolateNameServer.registerPortWithName( _port.sendPort, 'downloader_send_port', ); if (!isSuccess) { _unbindBackgroundIsolate(); _bindBackgroundIsolate(); return; } _port.listen((dynamic data) { final taskId = (data as List<dynamic>)[0] as String; final status = data[1] as DownloadTaskStatus; final progress = data[2] as int; print( 'Callback on UI isolate: ' 'task ($taskId) is in status ($status) and process ($progress)', ); print('2124: PROGRESS $progress'); if(progress == 100){ print('2124: PROGRESS 100'); OpenFilex.open(_localPath); Fluttertoast.showToast( msg: 'Coming soon', toastLength: Toast.LENGTH_LONG, timeInSecForIosWeb: 3); } // if (_tasks != null && _tasks!.isNotEmpty) { // final task = _tasks!.firstWhere((task) => task.taskId == taskId); // setState(() { // task // ..status = status // ..progress = progress; // }); // } }); } void _unbindBackgroundIsolate() { IsolateNameServer.removePortNameMapping('downloader_send_port'); } Future<void> _prepareSaveDir() async { _localPath = (await _getSavedDir())!; final savedDir = Directory(_localPath); if (!savedDir.existsSync()) { await savedDir.create(); } } Future<String?> _getSavedDir() async { String? externalStorageDirPath; if (Platform.isAndroid) { try { externalStorageDirPath = await AndroidPathProvider.downloadsPath; print('2124: externalStorageDirPath: $externalStorageDirPath'); } catch (err, st) { print('failed to get downloads path: $err, $st'); final directory = await getExternalStorageDirectory(); externalStorageDirPath = directory?.path; } } else if (Platform.isIOS) { externalStorageDirPath = (await getApplicationDocumentsDirectory()).absolute.path; print('2124: externalStorageDirPath IOS: $externalStorageDirPath'); } return externalStorageDirPath; } Future<void> _prepare() async { _permissionReady = await _checkPermission(); if (_permissionReady) { await _prepareSaveDir(); } } Future<bool> _checkPermission() async { if (Platform.isIOS) { return true; } if (Platform.isAndroid) { final status = await Permission.storage.status; if (status == PermissionStatus.granted) { return true; } final result = await Permission.storage.request(); return result == PermissionStatus.granted; } throw StateError('unknown platform'); }
下载日志
flutter: Callback on background isolate: task (com.mekudeshet.download.task.31006.1683227407.679338) is in status (DownloadTaskStatus(1)) and process (0) flutter: Callback on background isolate: task (com.mekudeshet.download.task.31006.1683227407.679338) is in status (DownloadTaskStatus(2)) and process (1) Query was executed successfully. Affected rows = 1 flutter: Callback on background isolate: task (com.mekudeshet.download.task.31006.1683227407.679338) is in status (DownloadTaskStatus(2)) and process (3) Query was executed successfully. Affected rows = 1 flutter: Callback on background isolate: task (com.mekudeshet.download.task.31006.1683227407.679338) is in status (DownloadTaskStatus(2)) and process (100) Query was executed successfully. Affected rows = 1 -[FlutterDownloaderPlugin URLSession:downloadTask:didFinishDownloadingToURL:] HTTP status code: 200 SuggestedFileName: --PDF.pdf Query was executed successfully. Affected rows = 1 savedDir: /var/mobile/Containers/Data/Application/014DCD70-5E34-41A3-95E0-868FEA48A189/Documents filename: --PDF.pdf flutter: Callback on background isolate: task (com.mekudeshet.download.task.31006.1683227407.679338) is in status (DownloadTaskStatus(3)) and process (100) Query was executed successfully. Affected rows = 1 -[FlutterDownloaderPlugin URLSession:task:didCompleteWithError:] HTTP status code: 200
解决步骤
1. 开启iOS文件共享权限
iOS默认不会将应用沙箱的Documents目录暴露给「文件」应用,需在项目的Info.plist中添加以下配置:
<key>UIFileSharingEnabled</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/>
UIFileSharingEnabled:允许「文件」应用访问应用的Documents目录LSSupportsOpeningDocumentsInPlace:支持直接在沙箱中打开文件,无需复制到其他位置
2. 理解saveInPublicStorage参数的作用
你设置了saveInPublicStorage: true,但iOS上该参数仅控制文件保存到NSDocumentDirectory(而非缓存目录),不会自动将文件放到系统公共下载目录。必须配合上述Info.plist配置,才能让文件在「文件」应用中显示。
3. 修正文件名
日志显示文件名为--PDF.pdf,特殊字符开头可能导致部分文件浏览器识别异常。建议在下载时指定合法文件名:
final taskId = await FlutterDownloader.enqueue( // ... 其他参数 fileName: "downloaded_file.pdf", // 自定义文件名 );
4. 验证文件存在性
下载完成后,通过代码确认文件是否真的存在于目标路径:
if(progress == 100){ final filePath = "$_localPath/--PDF.pdf"; final file = File(filePath); if(await file.exists()){ print("文件存在:$filePath"); } else { print("文件不存在,需检查路径或下载逻辑"); } }
5. 刷新「文件」应用
「文件」应用不会实时刷新目录,下载完成后重启应用和「文件」应用,再到「我的iPhone」→ 你的应用名称对应的目录下查找文件。
内容的提问来源于stack exchange,提问作者RJB
相关产品推荐
相关产品推荐

