You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:42:53