如何在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
相关产品推荐
相关产品推荐

