You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Flutter WebView中PDF文件无法下载的问题求助

解决Flutter WebView中PDF无法下载的问题

嘿,我懂你的困扰——WebView里点PDF链接完全没反应对吧?这是因为默认的WebView并不会自动处理下载请求,咱们得手动拦截这些请求,再用专门的工具来完成下载操作。下面是具体的解决步骤和修改后的代码:

步骤1:添加必要依赖

先在你的pubspec.yaml里加上这几个依赖,用来处理下载、文件路径和权限请求:

dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^4.4.0 # 建议用最新稳定版
  flutter_downloader: ^1.10.4
  path_provider: ^2.1.1
  permission_handler: ^11.0.1

然后运行flutter pub get安装这些依赖。

步骤2:配置平台权限

  • Android端:在android/app/src/main/AndroidManifest.xml里添加权限和下载服务:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<!-- 在application标签内添加 -->
<service android:name="vn.hunghd.flutterdownloader.DownloadService"
         android:permission="android.permission.FOREGROUND_SERVICE"
         android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
    </intent-filter>
</service>
  • iOS端:在ios/Runner/Info.plist里添加权限描述:
<key>NSDownloadsUsageDescription</key>
<string>需要访问下载目录保存PDF文件</string>
<key>NSFileProviderDomainUsageDescription</key>
<string>需要访问文件系统存储下载的PDF</string>

步骤3:修改WebView代码(整合下载逻辑)

下面是完整的修改后代码,关键部分我都标出来了:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'LoadingIndicator.dart';

class Row_Element_2 extends StatefulWidget {
  final String url;
  const Row_Element_2({
    Key? key,
    required this.url,
  }) : super(key: key);

  @override
  State<Row_Element_2> createState() => _Row_Element_2State();
}

class _Row_Element_2State extends State<Row_Element_2> {
  bool isLoading = true;
  final _key = UniqueKey();

  @override
  void initState() {
    super.initState();
    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
    // 初始化下载器并请求权限
    _setupDownloadEnv();
  }

  // 初始化下载环境+请求权限
  Future<void> _setupDownloadEnv() async {
    await FlutterDownloader.initialize(
      debug: true, // 开发阶段可开启调试日志
      ignoreSsl: false, // 生产环境建议关闭,除非链接证书有问题
    );

    // 安卓请求存储权限
    if (Platform.isAndroid) {
      final status = await Permission.storage.request();
      if (status.isDenied) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('需要存储权限才能下载PDF哦')),
        );
      }
    }
  }

  // 处理PDF下载
  Future<void> _startPdfDownload(String pdfUrl) async {
    // 获取设备的下载目录
    final directory = Platform.isAndroid
        ? await getExternalStorageDirectory()
        : await getApplicationDocumentsDirectory();

    // 发起下载请求
    await FlutterDownloader.enqueue(
      url: pdfUrl,
      savedDir: directory!.path,
      showNotification: true, // 显示下载进度通知
      openFileFromNotification: true, // 下载完成后可从通知打开文件
    );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Stack(
        children: [
          WebView(
            key: _key,
            initialUrl: widget.url,
            javascriptMode: JavascriptMode.unrestricted,
            onPageFinished: (finish) {
              setState(() {
                isLoading = false;
              });
            },
            // 核心:拦截导航请求,识别PDF链接
            navigationDelegate: (NavigationRequest request) {
              // 判断是否为PDF链接(可根据实际链接格式调整判断逻辑)
              if (request.url.endsWith('.pdf')) {
                _startPdfDownload(request.url);
                return NavigationDecision.prevent; // 阻止WebView默认跳转
              }
              // 其他请求正常放行
              return NavigationDecision.navigate;
            },
          ),
          isLoading ? Container(child: LoadingIndicator()) : const SizedBox.shrink(),
        ],
      ),
    );
  }
}

关键逻辑说明

  • navigationDelegate:这个回调会拦截WebView的所有导航请求,我们在这里判断如果是.pdf结尾的链接,就触发下载逻辑,同时阻止WebView的默认跳转行为。
  • FlutterDownloader:专门的下载插件,支持后台下载、进度通知、下载完成后打开文件等功能,比自己实现下载逻辑更稳定可靠。
  • 权限处理:安卓需要存储权限才能保存文件,iOS需要访问下载目录的权限,所以初始化阶段要主动请求权限,避免下载失败。

这样修改后,用户在WebView里点击PDF链接时,就会自动触发下载,还能在通知栏看到进度,下载完成后直接从通知打开文件就行。

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

火山引擎 最新活动