Flutter-让webview支持图片上传(Android, 2020.11.22更新)

详细源码地址:https://gitee.com/yugecse/WebViewUploadApplication/tree/master/

该功能主要是对谷歌的第三方库(webview_flutter)进行源码修改,如果升级版本后需要重新修改。

需要修改或新增的文件:

  1. 修改项目app/build.gradle文件,增加一个图片选择库;修改如下:
    dependencies {
        implementation 'com.zhihu.android:matisse:0.5.3-beta3'
        implementation 'com.github.bumptech.glide:glide:4.11.0'
        annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    }
  1. 修改FlutterWebview.java

   // 文件选择监听声明
   interface OnShowFileChooserListener {

        boolean onShowFileChooser(ValueCallback<Uri[]> filePathCallback, String acceptType);

    }

    private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
    private final InputAwareWebView webView;
    private final MethodChannel methodChannel;
    private final FlutterWebViewClient flutterWebViewClient;
    private final Handler platformThreadHandler;
    // 声明监听器对象
    private OnShowFileChooserListener onShowFileChooserListener;

    // Verifies that a url opened by `Window.open` has a secure url.
    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;
        }

        // 重写文件选择函数
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            String[] acceptTypes = fileChooserParams.getAcceptTypes();
            if (acceptTypes != null && acceptTypes.length > 0 && onShowFileChooserListener != null)
                return onShowFileChooserListener.onShowFileChooser(filePathCallback, acceptTypes[0]);
            return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @SuppressWarnings("unchecked")
    FlutterWebView(
            final Context context,
            BinaryMessenger messenger,
            int id,
            Map<String, Object> params,
            View containerView,
           // 传入文件选择监听器对象
            OnShowFileChooserListener onShowFileChooserListener) {
        // 监听器对象赋值
        this.onShowFileChooserListener = onShowFileChooserListener;
        DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();
        DisplayManager displayManager =
                (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
        displayListenerProxy.onPreWebViewInitialization(displayManager);
        webView = new InputAwareWebView(context, containerView);
        displayListenerProxy.onPostWebViewInitialization(displayManager);

        platformThreadHandler = new Handler(context.getMainLooper());
        // Allow local storage.
        webView.getSettings().setDomStorageEnabled(true);
        webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

        // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679.
        webView.getSettings().setSupportMultipleWindows(true);
        webView.setWebChromeClient(new FlutterWebChromeClient());

        methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
        methodChannel.setMethodCallHandler(this);

        flutterWebViewClient = new FlutterWebViewClient(methodChannel);
        Map<String, Object> settings = (Map<String, Object>) params.get("settings");
        if (settings != null) applySettings(settings);

        if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {
            List<String> names = (List<String>) params.get(JS_CHANNEL_NAMES_FIELD);
            if (names != null) registerJavaScriptChannelNames(names);
        }

        Integer autoMediaPlaybackPolicy = (Integer) params.get("autoMediaPlaybackPolicy");
        if (autoMediaPlaybackPolicy != null) updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy);
        if (params.containsKey("userAgent")) {
            String userAgent = (String) params.get("userAgent");
            updateUserAgent(userAgent);
        }
        if (params.containsKey("initialUrl")) {
            String url = (String) params.get("initialUrl");
            webView.loadUrl(url);
        }
    }
  1. 修改FlutterWebViewFactory.java
public final class WebViewFactory extends PlatformViewFactory {
  private final BinaryMessenger messenger;
  private final View containerView;
  private final FlutterWebView.OnShowFileChooserListener onShowFileChooserListener;

  WebViewFactory(BinaryMessenger messenger, View containerView, FlutterWebView.OnShowFileChooserListener onShowFileChooserListener) {
    super(StandardMessageCodec.INSTANCE);
    this.messenger = messenger;
    this.containerView = containerView;
    this.onShowFileChooserListener = onShowFileChooserListener;
  }

  @SuppressWarnings("unchecked")
  @Override
  public PlatformView create(Context context, int id, Object args) {
    Map<String, Object> params = (Map<String, Object>) args;
    return new FlutterWebView(context, messenger, id, params, containerView, onShowFileChooserListener);
  }
}
  1. 新增加一个WebViewFileUploader.java的类
package io.flutter.plugins.webviewflutter;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.webkit.ValueCallback;
import android.widget.Toast;

import com.zhihu.matisse.Matisse;
import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.engine.impl.GlideEngine;

import java.util.List;

public class WebViewFileUploader {

    private Activity activity;

    private ValueCallback<Uri[]> filePathCallback;

    private String acceptType;

    WebViewFileUploader(Activity activity ){
        this.activity = activity;
    }

    public void start(ValueCallback<Uri[]> filePathCallback, String acceptType){
        this.filePathCallback = filePathCallback;
        this.acceptType = acceptType;
        if(acceptType != null && acceptType.toLowerCase().contains("image/*")){
            pickImage();
        }else {
            this.filePathCallback.onReceiveValue(null);
            this.filePathCallback = null;
            Toast.makeText(activity, "您设置的AcceptType不是图片", Toast.LENGTH_SHORT).show();
        }
    }

    private void pickImage(){
        Matisse.from(activity)
                .choose(MimeType.ofAll())
                .countable(true)
                .maxSelectable(1)
//                .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
//                .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
                .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
                .thumbnailScale(0.85f)
                .imageEngine(new GlideEngine())
                .showPreview(true)
                .forResult(0x001ABC);
    }

    public boolean onActivityResult(int requestCode, int resultCode, Intent data){
        if(requestCode == 0x001ABC){
            if(resultCode == Activity.RESULT_OK){
              List<Uri> uriPaths = Matisse.obtainResult(data);
              if(uriPaths != null && uriPaths.size() > 0){
                  filePathCallback.onReceiveValue(new Uri[]{ uriPaths.get(0) });
                  filePathCallback = null;
                  return true;
              }
            }
            filePathCallback.onReceiveValue(null);
            filePathCallback = null;
            return true;
        }
        return false;
    }

}

  1. 修改FlutterWebViewPlugin.java类

/**
 * Java platform implementation of the webview_flutter plugin.
 *
 * <p>Register this in an add to app scenario to gracefully handle activity and context changes.
 *
 * <p>Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common}
 * package instead.
 */
public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware, PluginRegistry.ActivityResultListener, FlutterWebView.OnShowFileChooserListener {

    private Activity activity;
    private FlutterCookieManager flutterCookieManager;
    private WebViewFileUploader fileUploader;

    /**
     * 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() {
    }

//  /**
//   * 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(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
//    registrar
//        .platformViewRegistry()
//        .registerViewFactory(
//            "plugins.flutter.io/webview",
//            new WebViewFactory(registrar.messenger(), registrar.view()));
//    new FlutterCookieManager(registrar.messenger());
//  }

    @Override
    public void onAttachedToEngine(FlutterPluginBinding binding) {
        BinaryMessenger messenger = binding.getBinaryMessenger();
        binding
                .getPlatformViewRegistry()
                .registerViewFactory(
                        "plugins.flutter.io/webview",
                        new WebViewFactory(messenger, /*containerView=*/ null, this));
        flutterCookieManager = new FlutterCookieManager(messenger);
    }

    @Override
    public void onDetachedFromEngine(FlutterPluginBinding binding) {
        if (flutterCookieManager == null) {
            return;
        }

        flutterCookieManager.dispose();
        flutterCookieManager = null;
    }

    @Override
    public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
        this.activity = binding.getActivity();
        binding.addActivityResultListener(this);
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {

    }

    @Override
    public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
        binding.removeActivityResultListener(this);
    }

    @Override
    public void onDetachedFromActivity() {

    }

    @Override
    public boolean onShowFileChooser(ValueCallback<Uri[]> filePathCallback, String acceptType) {
        if (fileUploader == null) fileUploader = new WebViewFileUploader(activity);
        fileUploader.start(filePathCallback, acceptType);
        return true;
    }

    @Override
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
        if (fileUploader != null)
            return fileUploader.onActivityResult(requestCode, resultCode, data);
        return false;
    }
}

至此,你需要实现的图片文件选择的功能已经实现!

注明:原创内容,请注明出处,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270