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

Flutter 3.13.2下Android/iOS下载并打开PDF等文件的方案求助

Flutter跨平台下载并打开PDF文件的可行方案

针对你遇到的flutter_downloader不支持API33、background_downloader不稳定、不想冗余引入Dio的问题,以下是几个稳定可控的解决方案:

方案一:基于官方HTTP包+路径管理+文件打开工具(无冗余依赖)

如果你的项目已经使用Flutter官方http包做网络请求,这个方案完全没有额外冗余,逻辑透明可控,适合前台下载场景。

实现步骤:

  1. 依赖准备:添加path_provider(获取设备存储路径)和open_filex(跨平台打开本地文件)到pubspec.yaml
  2. 核心代码:
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:open_filex/open_filex.dart';

Future<void> downloadAndOpenPdf(String pdfUrl) async {
  try {
    // 1. 获取PDF字节流
    final response = await http.get(Uri.parse(pdfUrl));
    if (response.statusCode != 200) {
      throw Exception('下载失败,状态码:${response.statusCode}');
    }

    // 2. 确定存储路径(适配Android/iOS)
    Directory? targetDir;
    if (Platform.isAndroid) {
      // 存到公共Downloads目录,让系统文件应用可见
      targetDir = await getExternalStoragePublicDirectory(StorageDirectory.downloads);
    } else if (Platform.isIOS) {
      // 存到应用文档目录,需配置Info.plist让文件应用可访问
      targetDir = await getApplicationDocumentsDirectory();
    }

    if (targetDir == null) throw Exception('无法获取有效存储目录');

    // 3. 写入文件
    final fileName = pdfUrl.split('/').last;
    final saveFile = File('${targetDir.path}/$fileName');
    await saveFile.writeAsBytes(response.bodyBytes);

    // 4. 打开文件
    await OpenFilex.open(saveFile.path);
  } catch (e) {
    print('操作失败:$e');
    // 这里可以添加用户提示逻辑
  }
}

平台适配配置:

  • Android:
    在AndroidManifest.xml中添加权限(API33+适配):
    <!-- 访问公共媒体文件 -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <!-- 若需写入公共Downloads目录,API30+需申请此权限 -->
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        android:minSdkVersion="30"
        tools:ignore="ScopedStorage" />
    
  • iOS:
    在Info.plist中添加配置,让文件应用可访问应用目录:
    <key>UIFileSharingEnabled</key>
    <true/>
    <key>LSSupportsOpeningDocumentsInPlace</key>
    <true/>
    <key>NSDocumentsFolderUsageDescription</key>
    <string>需要访问文档目录保存下载的PDF文件</string>
    

优缺点:

  • ✅ 无冗余依赖(若已用官方http)、稳定可控、代码逻辑清晰
  • ❌ 需要自行处理下载进度、错误重试、后台下载等进阶逻辑

方案二:前台下载+本地通知(适配后台需求)

如果需要后台下载能力和进度通知,可以结合flutter_local_notifications包,用Isolate处理下载任务避免阻塞UI。

核心逻辑:

  1. 初始化通知通道
  2. 启动Isolate执行下载任务,实时发送进度到主Isolate
  3. 更新通知显示进度,下载完成后触发文件打开

关键代码片段:

import 'dart:isolate';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();

// 初始化通知
Future<void> _initNotifications() async {
  const AndroidInitializationSettings androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
  const InitializationSettings initSettings = InitializationSettings(android: androidInit);
  await _notificationsPlugin.initialize(initSettings);
}

// Isolate内执行下载任务
Future<void> _downloadTask(List<dynamic> args) async {
  final String pdfUrl = args[0];
  final SendPort sendPort = args[1];
  
  final response = await http.get(Uri.parse(pdfUrl),
      onReceiveProgress: (count, total) {
    if (total != -1) {
      sendPort.send((count / total * 100).toInt());
    }
  });
  
  // 写入文件逻辑同方案一
  // ...
  
  sendPort.send('completed');
}

// 发起带通知的下载
Future<void> downloadWithNotification(String pdfUrl) async {
  await _initNotifications();
  
  const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
      'download_channel', '下载通知',
      channelDescription: 'PDF文件下载进度',
      importance: Importance.max,
      priority: Priority.high,
      showProgress: true,
      maxProgress: 100);
  const NotificationDetails notificationDetails = NotificationDetails(android: androidDetails);

  // 启动Isolate执行下载
  final ReceivePort receivePort = ReceivePort();
  await Isolate.spawn(_downloadTask, [pdfUrl, receivePort.sendPort]);

  // 监听进度更新
  receivePort.listen((message) {
    if (message is int) {
      _notificationsPlugin.show(
          0, '正在下载PDF', '进度:$message%', notificationDetails);
    } else if (message == 'completed') {
      _notificationsPlugin.show(
          0, '下载完成', 'PDF已保存到设备', notificationDetails);
      // 打开文件逻辑
      // ...
    }
  });
}

优缺点:

  • ✅ 支持后台下载、进度通知,依赖包轻量成熟
  • ❌ 需要处理通知权限、Isolate通信等额外逻辑

方案三:原生通道实现(零第三方依赖)

如果对第三方依赖极度敏感,且具备原生开发能力,可以直接调用Android/iOS的原生下载API,完全可控且符合平台规范。

实现思路:

  • Android:调用系统DownloadManager,配置下载请求到公共Downloads目录,通过广播监听下载完成状态,再通知Flutter打开文件
  • iOS:使用NSURLSession创建下载任务,保存到应用文档目录,通过回调通知Flutter下载完成

优缺点:

  • ✅ 零第三方依赖、完全遵循平台规范、稳定可靠
  • ❌ 需要编写原生代码,维护成本较高

内容的提问来源于stack exchange,提问作者Praveen Bhusal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 16:34:58