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?