Flutter WebView Blob PDF 下载

10

我正在尝试使用Flutter从网站下载文件。我首先使用evaluateJavaScript更改一些值,然后单击生成按钮,这个按钮应该会将相应的pdf文件下载到手机。

以下是我的代码:

InAppWebView(
        initialUrl: '...',
        initialOptions: InAppWebViewGroupOptions(
          crossPlatform: InAppWebViewOptions(
              debuggingEnabled: true,
              useOnDownloadStart: true,
              javaScriptEnabled: true,
          ),
        ),
        //javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (InAppWebViewController webViewController) {
          _controller = webViewController;
          },
          onLoadStop: (_controller ,url) async {
          await _loadData();

          _controller.evaluateJavascript(source:
"console.log(document.getElementById('field-lastname').value = '${data[Field.name]}' );"
+"console.log(document.getElementById('generate-btn').click());"

           );
          },

          onDownloadStart:(_controller,url) async{
            print("onDownloadStart $url");
            final taskId = await FlutterDownloader.enqueue(
                url: url,
                savedDir: (await getExternalStorageDirectory()).path,
            showNotification: true, // show download progress in status bar (for Android)
            openFileFromNotification: true,);
          },

      ),

打印出来的URL就像这个样子

onDownloadStart blob:https://example.com/a2a5316c-9290-4da7-8a2a-9f899861046a

这里是控制台:


这里是控制台

有人能帮我吗?

3个回答

16

使用blob URL下载文件在Flutter WebView的当前状态下是棘手的,并且不被默认支持。有三个流行的插件:

在社区存储库的README中有一条注释

我们正在与Flutter团队紧密合作,将所有社区插件功能集成到官方WebView插件中。我们将尽力解决PR和Bugfixes,但我们现在的优先级是合并我们的两个代码库。完成合并后,我们将弃用社区插件,转而使用官方插件。

要构建完全可工作且无错误的webview仍需大量工作。目前对于像此处提到的更具挑战性的任务,最好的方法是使用非常流行且已被许多人成功使用的flutter_inappwebview与blob文件相关的问题。正如我们在您的片段中看到的,您已经使用了此插件。要下载blob文件,可以尝试将blob:url转换为base64,就像在此案例中一样Download Blob file from Website inside Android WebViewClient

可能的解决方法

向您的webview(_controller)添加JavaScriptHandler。我认为onWebViewCreated可能没问题。

        controller.addJavaScriptHandler(
          handlerName: _webViewHandlerName,
          callback: (data) async {
            if (data.isNotEmpty) {
              final String receivedFileInBase64 = data[0];
              final String receivedMimeType = data[1];

              // NOTE: create a method that will handle your extensions
              final String yourExtension =
                  _mapMimeTypeToYourExtension(receivedMimeType); // 'pdf'

              _createFileFromBase64(
                  receivedFileInBase64, 'YourFileName', yourExtension);
            }
          },
        );

JavaScript处理程序将接收存储在数组中的两个值。第一个参数是以base64编码的文件,第二个参数是mimeType,例如application/pdf。有关mimeType的信息使我们能够获取有关应用于保存文件的扩展名的信息。 它们可以很容易地映射为application/pdf => 'pdf'等。

  _createFileFromBase64(String base64content, String fileName, String yourExtension) async {
    var bytes = base64Decode(base64content.replaceAll('\n', ''));
    final output = await getExternalStorageDirectory();
    final file = File("${output.path}/$fileName.$yourExtension");
    await file.writeAsBytes(bytes.buffer.asUint8List());
    print("${output.path}/${fileName}.$yourExtension");
    await OpenFile.open("${output.path}/$fileName.$yourExtension");
    setState(() {});
  }

最后,在处理blob URL的地方,会调用JavaScript。

       onDownloadStart: (controller, url) async {
        print("onDownloadStart $url");
        var jsContent = await rootBundle.loadString("assets/js/base64.js");
        await controller.evaluateJavascript(
            source: jsContent.replaceAll("blobUrlPlaceholder", url));
      },

Javascript(我更喜欢将其作为资产 base64.js 加载,而不是在 Dart 代码中硬编码)打开 blob url 并将编码的基于 base64 的数据和 mimeType 作为参数传递给我们在 dart 中的处理程序 blobToBase64Handler

var xhr = new XMLHttpRequest();
var blobUrl = "blobUrlPlaceholder";
console.log(blobUrl);
xhr.open('GET', blobUrl, true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
  if (this.status == 200) {
    var blob = this.response;
    var reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.onloadend = function() {
      var base64data = reader.result;
      var base64ContentArray = base64data.split(",")     ;
      var mimeType = base64ContentArray[0].match(/[^:\s*]\w+\/[\w-+\d.]+(?=[;| ])/)[0];
      var decodedFile = base64ContentArray[1];
      console.log(mimeType);
      window.flutter_inappwebview.callHandler('blobToBase64Handler', decodedFile, mimeType);
    };
  };
};
xhr.send();

来源:在Android WebViewClient内从网站下载Blob文件

来源:如何解码Flutter中的base64 PDF字符串?

虽然不太干净且看起来很hacky,但找不到更好更简单的方法。


@Nazar 是的,也可以下载其他类型的文件。Blob 提供了有关大小和 mimeType 的信息。在我的答案中,代码是针对“application/pdf”硬编码的,但我还使用更灵活的解决方案,将 mimeType 传递给下载函数,并根据 blob 中的 mimeType 保存具有依赖于 mimeType 的扩展名的文件。在您的情况下,它可能是这样的“application/vnd.ms-excel”。我会在空闲时间更新我的答案,使其更加通用。 - wpazio
@wpazio 我尝试将内容类型从application/pdf更改为application/vnd.ms-excel,同时将文件类型从.pdf更改为.xls,但是我收到了这个错误[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: FormatException: Invalid character (at character 23) formats-officedocument.spreadsheetml.sheet;base64,UEsDBAoAAAAAAPJukFIAAAAAA... - Nazarudin
@wpazio 好的,我会等待的。非常感谢您的帮助。 - Nazarudin
@wpazio 我在 _webViewHandlerName 和 _mapMimeTypeToYourExtension 处遇到了一个错误,说 方法未定义。也许你忘记添加一些代码了? - Nazarudin
@axumnemonic,你可以尝试我的解决方案,它适用于Android和iOS。 - PageGuo
显示剩余11条评论

3

如何使用它?请提供一个例子。 - fajar ainul
请参考https://github.com/guoguoguilai/flutter-webview-blob-download/blob/main/blob.html#L15 - PageGuo

0
如果有人找到这个问题,我提供的解决方案是下载blob文件。
    controller = WebViewController()
      ..addJavaScriptChannel('Mobile', onMessageReceived: (JavaScriptMessage message) {
          Map<String, dynamic> data = jsonDecode(message.message);
          if (data['action'] == 'fileDownload') {
            saveToFile(data['data']);
          }
        })
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageFinished: (String url) {
            controller.runJavaScript("""
if (!window.mobilelistener) {
  window.mobilelistener = true;
  document.addEventListener('click', async function(event) {
    var element = event.target; 
    if (element.tagName === 'A' && element.href.startsWith("blob")) {
      event.preventDefault();
      var reader = new FileReader();
      reader.onloadend = function() {
        var base64data = reader.result;
        Mobile.postMessage(JSON.stringify(
          {
            "action": "fileDownload",
            "data": base64data
          }
        ));
      }
      fetch(element.href)
        .then(r => r.blob())
        .then(blob => reader.readAsDataURL(blob));
    }
  });
}
            """);
          },
          onNavigationRequest: (NavigationRequest request) {
            if (request.url.startsWith('blob:')) {
              return NavigationDecision.prevent;
            }

保存到文件的操作大致如下:
void saveToFile(String content) async {
    content = content.substring(content.indexOf('base64,') + 7);
    Uint8List bytes = base64Decode(content);

    String? path = await FileSaver.instance.saveFile(
      name: 'file',
      bytes: bytes,
      ext: 'pdf',
      mimeType: MimeType.pdf);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接