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

如何在Flutter的WebView中实现文件下载?

在Flutter WebView中实现文件下载的标准方案

webview_flutter本身没有内置文件下载处理逻辑,需要通过拦截下载请求+手动处理文件保存来实现,以下是针对Android设备的完整实现步骤:

1. 添加依赖

除现有依赖外,补充下载和文件操作相关依赖:

dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^4.4.0
  permission_handler: ^11.0.1
  dio: ^5.3.3
  path_provider: ^2.1.1
  android_intent_plus: ^4.0.0

2. 核心实现逻辑

步骤1:拦截下载请求

在NavigationDelegate的onNavigationRequest中识别下载链接,拦截后手动处理,避免WebView默认跳转。可通过URL后缀(如.pdf/.zip)或请求头的Content-Type判断是否为可下载资源。

步骤2:适配Android权限

  • Android 10+:写入公共Download目录可通过MediaStore无需申请存储权限;若需自定义目录,Android 11+需申请MANAGE_EXTERNAL_STORAGE权限
  • Android 9及以下:需申请STORAGE权限

步骤3:下载并确保文件可见

使用dio完成文件下载,下载后发送系统媒体扫描广播,确保文件能在设备文件管理器中被找到。

3. 修改后的完整代码

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:android_intent_plus/android_intent.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter WebView下载示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(title: 'WebView下载演示'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late WebViewController controller;
  final Dio _dio = Dio();

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(NavigationDelegate(
        onProgress: (int progress) {},
        onPageStarted: (String url) {},
        onPageFinished: (String url) {},
        onWebResourceError: (WebResourceError error) {},
        // 拦截下载请求
        onNavigationRequest: (NavigationRequest request) async {
          // 扩展规则可覆盖更多文件类型
          if (request.url.endsWith('.pdf') || 
              request.url.endsWith('.zip') || 
              request.url.contains('download')) {
            await _handleDownload(request.url);
            return NavigationDecision.prevent;
          }
          return NavigationDecision.navigate;
        },
      ))
      ..loadRequest(Uri.parse('https://file-examples.com/index.php/sample-documents-download/sample-pdf-download/'));
  }

  // 处理下载流程
  Future<void> _handleDownload(String url) async {
    // 申请存储权限
    if (await Permission.manageExternalStorage.isDenied) {
      await Permission.manageExternalStorage.request();
    }

    if (await Permission.manageExternalStorage.isGranted) {
      try {
        // 获取设备公共Download目录
        final directory = await getExternalStorageDirectory();
        final fileName = url.split('/').last;
        final savePath = '${directory?.path}/Download/$fileName';

        // 执行下载
        await _dio.download(
          url,
          savePath,
          onReceiveProgress: (received, total) {
            if (total != -1) {
              print('下载进度: ${(received / total * 100).toStringAsFixed(0)}%');
            }
          },
        );

        // 通知系统扫描文件,确保在文件管理器可见
        final intent = AndroidIntent(
          action: 'android.intent.action.MEDIA_SCANNER_SCAN_FILE',
          data: Uri.fromFile(Uri.parse(savePath).toFilePath()).toString(),
        );
        await intent.launch();

        if (!mounted) return;
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('文件已下载至: $savePath')),
        );
      } catch (e) {
        if (!mounted) return;
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('下载失败: ${e.toString()}')),
        );
      }
    } else {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('需要存储权限才能下载文件')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter WebView下载示例')),
      body: WebViewWidget(controller: controller),
    );
  }
}

4. 关键注意事项

  • 可根据业务需求扩展下载链接的判断规则,覆盖更多文件类型
  • Android 10+推荐使用MediaStore写入公共目录,避免申请高权限
  • 下载完成后的媒体扫描步骤不可省略,否则文件可能无法在文件管理器中及时显示
  • 需添加错误捕获逻辑,处理网络异常、权限拒绝等场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 00:20:31