flutter 的 in_app_web_view實現下載功能

2023-06-07 18:00:39

flutter與前端互動,利用in_app_web_view實現下載功能:

首先下載庫,終端輸入

flutter pub add flutter_inappwebview

之後匯出

import 'package:flutter_inappwebview/flutter_inappwebview.dart';

即可使用。

建立in_app_web_view:

InAppWebView(
     initialOptions:
          InAppWebViewGroupOptions(
              crossPlatform:InAppWebViewOptions(
                useOnDownloadStart:true,
              ),
              android: AndroidInAppWebViewOptions()
          ),
      //老版本:initialUrl    新版本:initialUrlRequest
      initialUrlRequest: URLRequest(
        url: Uri.parse(widget.url),
      )
)

因為要下載檔案,所以請務必手動設定 useOnDownloadStart 為 true(否則出發檔案下載的監聽)。

initialUrlRequest中可填寫自己想首先開啟的url地址。

可參考例子:flutter_inappwebview_examples/main.dart at main · pichillilorenzo/flutter_inappwebview_examples · GitHub

https://github.com/pichillilorenzo/flutter_inappwebview_examples/blob/main/file_download/lib/main.dart

填寫自己需要的回撥(例子中的一點錯誤,沒有開啟 useOnDownloadStart, 因此不會下載成功,在使用時請設定為true)

正常情況下,配合downloader和android_path_provider,普通https連結即可下載檔案。

 

而遇到blob連結時,還需要進行更多操作來確保檔案的下載:

可參考javascript - Flutter WebView blob pdf download - Stack Overflow

https://stackoverflow.com/questions/64865972/flutter-webview-blob-pdf-download/64902313

因為Android不支援blob連結下載,因此我們巢狀javascript處理下載連結,在in_app_web_view的build中重寫onWebViewCreated方法,新增javascriptHandler:

onWebViewCreated: (InAppWebViewController controller) {
        if (mounted) {
          setState(() {
            _inAppWebCtrl = controller;
            _inAppWebCtrl!.addJavaScriptHandler(
              handlerName: 'blobToBase64Handler',
              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 extension =
                  _mapMimeTypeToExtension(receivedMimeType);
                  String tmpFileName = 'tmpfile';
                  _createFileFromBase64(
                      receivedFileInBase64, tmpFileName, extension);
                }
              },
            );
          });
        }
      },

首先在assets中新增js資料夾,然後建立 base64.js 檔案

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();

 

注意js中的callhander的名字引數,對應建立webview時addJavascriptHandler中的name。

另外是檔案型別對映函數和檔案下載函數:

  String _mapMimeTypeToExtension(String mimeType) {
    String extension = '';
    switch(mimeType) {
      case 'image/png': extension = 'png'; break;
      case 'application/msword': extension = 'doc'; break;
      case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
        extension = 'docx';
        break;
      case 'image/jpeg': extension = 'jpg'; break;
      case 'image/gif': extension = 'gif'; break;
      case 'image/svg+xml': extension = 'svg'; break;
      case 'image/tiff': extension = 'tif'; break;
      case 'text/plain': extension = 'txt'; break;
      case 'application/vnd.ms-powerpoint': extension = 'ppt'; break;
      case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
        extension = 'pptx';
        break;
      case 'application/vnd.ms-excel': extension = 'xls'; break;
      case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
        extension = 'xlsx';
        break;
      case 'application/zip': extension = 'zip'; break;
      case 'application/x-7z-compressed': extension = '7z'; break;
      case 'application/pdf': extension = 'pdf'; break;
    }
    return extension;
  }

  _createFileFromBase64(String base64content, String fileName, String yourExtension) async {
    var bytes = base64Decode(base64content.replaceAll('\n', ''));
    final file = File("$_localPath/$fileName.$yourExtension");
    await file.writeAsBytes(bytes.buffer.asUint8List());
    
  }

最後重寫inappwebview中的下載請求方法:

      onDownloadStartRequest: (controller, downloadStartRequest) async {
          var jsContent = await rootBundle.loadString("assets/js/base64.js");
// 執行javascript程式碼解析blob
          await controller.evaluateJavascript(
              source: jsContent.replaceAll("blobUrlPlaceholder",
                  downloadStartRequest.url.toString()));
      },

總結:因為android本身不能解析blob,我們因此使用javascript作為翻譯:執行順序:

onDownloadStartRequest -> javascript檔案 -> webviewController中的handler的callback,最後以流的方式寫入檔案。