webview_flutter插件如何支持文件上传?

Posted 大大大猫头鹰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了webview_flutter插件如何支持文件上传?相关的知识,希望对你有一定的参考价值。

在WEB应用开发中,我们不可避免的会遇到文件上传的场景,由于国泰基金的APP采用的是Flutter + H5的模式进行构建,也就是传统的Hybrid App的开发模式,内部的H5会存在诸如上传身份证图片这样的场景,而内部H5的包裹采用的是flutter_webview这个官方维护的插件,至于为什么会采用flutter_webview这个插件而不是flutter_webview_plugin也会渲染H5,在未来的文章我会逐步提到~

flutter_webview地址:https://pub.flutter-io.cn/packages/webview_flutter

flutter_webview_plugin地址:https://pub.flutter-io.cn/packages/flutter_webview_plugin

我们可以看到,截止到目前为止,flutter_webview这个插件已经升级到1.0.3这个版本,在1.0.0这个生产环境版本之前,大多数DEMO使用的是0.3这个开发预览版,也就在今年10月左右的时候推出了1.0.0这个相对来说比较稳定的生产版本,也算是众望所归:

不过遗憾的是,目前为止的版本,android端是没有办法支持H5文件上传的,也就是对下方这段H5代码的支持,不过毕竟这是个非常常见的场景,我相信在未来的官方升级版本中会将这个issue考虑进去,不过,目前的话,咱们如果使用还是得自己去修改这个插件的JAVA源码以适应这样的场景,害,只能说一句「淦」!

<input type="file">

那么,我们应该如何做呢?

1.1

我们不能再直接通过从pub端下载的方式去集成我们的webview插件,我们需要将这个插件的源码移植到我们自己的工程中去包裹,笔者使用的是1.0.1这个版本,不过截止到文章的更新时间,已经更新到1.0.3了,不过这并不影响我们集成上传文件的能力:

flutter_webview插件源码:https://github.com/flutter/plugins/tree/master/packages/webview_flutter

基于这样的方式,我一般都会在工程根目录创建一个plugins这样的一个文件夹去包裹类似这样的插件源码:

那么在pubspec.yaml中集成的方式也变成如下:

1.2

也就是需要在源码的android工程中,找到具体的代码段将文件上传的回调补充进去,这其实对于Android的原生开发者来说是非常easy的,这也就是像我之前提到的,尽管Flutter能够解决大多数跨端开发的场景,但是你依旧需要比较熟悉Android或者ios其中一个或者所有的原生开发细节,这在你遇到插件所不能解决的场景中,受益匪浅,所以,并不是只会写H5或者Dart就够了!

那么接下来,我就来说一下需要改哪些代码段!

具体Android段的代码内容在如下路径:

1.3

修改WebviewFactory.java,这是webview工厂的创建者

这里其实创建了一个flutterwebview句柄,也是为了在其他对象中能够获得当前创建的webview对象,当然我们看到这是一个私有变量,所以我们同时需要创建一个获取器,也就是getFlutterWebView方法!

1.4

修改WebViewFlutterPlugin.java,这是flutter插件的适配器,这里规定了一个flutter插件是如何和Android的Activity生命周期以及其上下文相关联的:

public class WebViewFlutterPlugin implements FlutterPlugin, PluginRegistry.ActivityResultListener, ActivityAware 
  private static final String TAG = "WebViewFlutterPlugin";

  private FlutterCookieManager flutterCookieManager;
  public static Activity activity;
  private WebViewFactory factory;

  /**
   * Add an instance of this to @link io.flutter.embedding.engine.plugins.PluginRegistry to
   * register it.
   *
   * <p>THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE
   * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least
   * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with @link
   * #registerWith(Registrar) to use this plugin with older Flutter versions.
   *
   * <p>Registration should eventually be handled automatically by v2 of the
   * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694
   */
  public WebViewFlutterPlugin() 
    Log.v(TAG,"WebViewFlutterPlugin");
  

  /**
   * Registers a plugin implementation that uses the stable @code io.flutter.plugin.common
   * package.
   *
   * <p>Calling this automatically initializes the plugin. However plugins initialized this way
   * won't react to changes in activity or context, unlike @link CameraPlugin.
   */
  @SuppressWarnings("deprecation")
  public static void registerWith(Registrar registrar) 
    Log.v(TAG,"registerWith");
    registrar
        .platformViewRegistry()
        .registerViewFactory(
            "plugins.flutter.io/webview",
            new WebViewFactory(registrar.messenger(), registrar.view()));
    new FlutterCookieManager(registrar.messenger());
  

  @Override
  public void onAttachedToEngine(FlutterPluginBinding binding) 
    Log.v(TAG,"onAttachedToEngine");
    BinaryMessenger messenger = binding.getBinaryMessenger();
    factory = new WebViewFactory(messenger, null);
    binding
            .getFlutterEngine()
            .getPlatformViewsController()
            .getRegistry()
            .registerViewFactory(
                    "plugins.flutter.io/webview", factory);
    flutterCookieManager = new FlutterCookieManager(messenger);

    Context appContext = binding.getApplicationContext();
    if (appContext instanceof FlutterApplication) 
      Activity currentActivity = ((FlutterApplication) appContext).getCurrentActivity();
      if (currentActivity != null) 
        activity = currentActivity;
      
    
  

  @Override
  public void onDetachedFromEngine(FlutterPluginBinding binding) 
    Log.v(TAG,"onDetachedFromEngine");
    if (flutterCookieManager == null) 
      return;
    
    activity = null;
    flutterCookieManager.dispose();
    flutterCookieManager = null;
  

  @Override
  public boolean onActivityResult(int requestCode, int resultCode, Intent data) 
    Log.v(TAG,"onActivityResult");
    if (factory != null && factory.getFlutterWebView() != null)
      return factory.getFlutterWebView().activityResult(requestCode, resultCode, data);
    

    return false;
  

  @Override
  public void onAttachedToActivity(ActivityPluginBinding binding) 
    Log.v(TAG,"onAttachedToActivity");
    activity = binding.getActivity();
    binding.addActivityResultListener(this);
  

  @Override
  public void onDetachedFromActivityForConfigChanges() 
    Log.v(TAG,"onDetachedFromActivityForConfigChanges");
  

  @Override
  public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) 
    Log.v(TAG,"onReattachedToActivityForConfigChanges");
  

  @Override
  public void onDetachedFromActivity() 
    Log.v(TAG,"onDetachedFromActivity");
  

1.5

修改FlutterWebView.java,在这里我们通过WebChromeClient监听h5选择文件的操作并拦截,然后打开文件管理选择要上传的文件,最后将文件返回给H5,这里其实是一个老生常谈的问题,也就是原生的Android Webview是如何拦截文件上传操作的:

private class FlutterWebChromeClient extends WebChromeClient 
    @Override
    public boolean onCreateWindow(
        final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) 
      final WebViewClient webViewClient =
          new WebViewClient() 
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public boolean shouldOverrideUrlLoading(
                @NonNull WebView view, @NonNull WebResourceRequest request) 
              final String url = request.getUrl().toString();
              if (!flutterWebViewClient.shouldOverrideUrlLoading(
                  FlutterWebView.this.webView, request)) 
                webView.loadUrl(url);
              
              return true;
            

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) 
              if (!flutterWebViewClient.shouldOverrideUrlLoading(
                  FlutterWebView.this.webView, url)) 
                webView.loadUrl(url);
              
              return true;
            
          ;

      final WebView newWebView = new WebView(view.getContext());
      newWebView.setWebViewClient(webViewClient);

      final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
      transport.setWebView(newWebView);
      resultMsg.sendToTarget();

      return true;
    
    // For Android < 3.0
    public void openFileChooser(ValueCallback<Uri> valueCallback) 
      Log.v(TAG, "openFileChooser Android < 3.0");
      uploadMessage = valueCallback;
      openImageChooserActivity();
    

    // For Android  >= 3.0
    public void openFileChooser(ValueCallback valueCallback, String acceptType) 
      Log.v(TAG, "openFileChooser Android  >= 3.0");
      uploadMessage = valueCallback;
      openImageChooserActivity();
    

    //For Android  >= 4.1
    public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) 
      Log.v(TAG, "openFileChooser Android  >= 4.1");
      uploadMessage = valueCallback;
      openImageChooserActivity();
    

    // For Android >= 5.0
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) 
      Log.v(TAG, "openFileChooser Android >= 5.0");
      uploadMessageAboveL = filePathCallback;
      openImageChooserActivity();
      return true;
    
  

我们可以看下其中的openImageChooserActivity的具体实现:

private void openImageChooserActivity() 
    Log.v(TAG, "openImageChooserActivity");
    Intent intent1 = new Intent(Intent.ACTION_PICK, null);
    intent1.setDataAndType(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
    Intent chooser = new Intent(Intent.ACTION_CHOOSER);
    chooser.putExtra(Intent.EXTRA_TITLE, "选择图片");
    chooser.putExtra(Intent.EXTRA_INTENT,intent1);

    if (WebViewFlutterPlugin.activity != null)
      WebViewFlutterPlugin.activity.startActivityForResult(chooser, FILE_CHOOSER_RESULT_CODE);
     else 
      Log.v(TAG, "activity is null");
    
  

那么当获取到图片后进行的回调是如何处理的呢,也就是startActivityForResult对应的处理方式,当然在不同的Android版本中这是不一致的:

  public boolean activityResult(int requestCode, int resultCode, Intent data) 
    if (requestCode == FILE_CHOOSER_RESULT_CODE) 
      if (null == uploadMessage && null == uploadMessageAboveL) 
        return false;
      
      Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
      if (uploadMessageAboveL != null) 
        onActivityResultAboveL(requestCode, resultCode, data);
       else if (uploadMessage != null && result != null) 
        uploadMessage.onReceiveValue(result);
        uploadMessage = null;
      
    
    return false;
  

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) 
    if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
    
      return;
    
    Uri[] results = null;
    if (resultCode == Activity.RESULT_OK) 
      if (intent != null) 
        String dataString = intent.getDataString();
        ClipData clipData = intent.getClipData();
        if (clipData != null) 
          results = new Uri[clipData.getItemCount()];
          for (int i = 0; i < clipData.getItemCount(); i++) 
            ClipData.Item item = clipData.getItemAt(i);
            results[i] = item.getUri();
          
        
        if (dataString != null)
        
          results = new Uri[]Uri.parse(dataString);
        
      
    
    uploadMessageAboveL.onReceiveValue(results);
    uploadMessageAboveL = null;
  

最后大功告成,一个属于你自己可上传文件可维护的flutter_webview插件就可以正常使用了:

最后放上源码的地址,大家可以直接下载使用:https://github.com/xueenze/flutter_webview_with_file_upload.git

希望大家对上述内容有好的建议能够与我及时沟通,小编的邮箱还是那个:kameleon@126.com,欢迎大家多多交流!

以上是关于webview_flutter插件如何支持文件上传?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法让颤动的 webview 使用 android 相机进行文件上传?如何在 webview_flutter 中打开文件选择器?

如何让 Pinch Zoom 在 webview_flutter 插件上工作

如何使用 webview_flutter 包运行自定义 Javascript?

Flutter - 如何使用 webview_flutter 包播放 Google Drive 视频预览

Flutter:webview_flutter插件使用

PHP 大文件上传,支持断点续传,求具体方案、源码或者文件上传插件