Mono for Android,WebView输入框文件选择器无法工作。

4
我有一个网页,用于上传文件。用户使用 <input type="file" /> 选择文件并按提交按钮,一切正常。现在我需要创建一个 Android 应用程序(使用 C# 和 Mono for Android),其中包含简单的 Webview,并且必须像 Web 版本一样工作。
但是我遇到了问题-当我点击 Choose file 按钮时,文件对话框不会打开。
我搜索了这个问题几天,但没有找到任何解决方案。看起来有 Java 平台的解决方法,但在 C# 上不起作用。
是否有人有任何想法如何使其工作?
2个回答

2
我有一个想法可以让它工作。其中一部分是关于“如何绑定虚方法”的基本操作,而另一部分则是纯粹的恶意行为。
首先,我们需要一个“中间人”。由于WebChromeClient没有声明openFileChooser()方法,因此我们需要声明一个版本,称为OpenFileWebChromeClient。它声明了一个virtual OpenFileChooser方法,并为其提供了绑定,以便可以对其进行重写:
using System;

using Android.App;
using Android.Content;
using Android.Runtime;
using Android.OS;
using Android.Webkit;

namespace Scratch.FileUpload
{
    [Register ("android/webkit/WebChromeClient", DoNotGenerateAcw=true)]
    class OpenFileWebChromeClient : WebChromeClient {

        static IntPtr id_openFileChooser;
        [Register ("openFileChooser", "(Landroid/webkit/ValueCallback;)V", "GetOpenFileChooserHandler")]
        public virtual void OpenFileChooser (IValueCallback uploadMsg)
        {
            if (id_openFileChooser == IntPtr.Zero)
                id_openFileChooser = JNIEnv.GetMethodID (ThresholdClass, "openFileChooser", "(Landroid/webkit/ValueCallback;)V");

            if (GetType () == ThresholdType)
                JNIEnv.CallVoidMethod  (Handle, id_openFileChooser, new JValue (JNIEnv.ToJniHandle (uploadMsg)));
            else
                JNIEnv.CallNonvirtualVoidMethod  (Handle, ThresholdClass, id_openFileChooser, new JValue (JNIEnv.ToJniHandle (uploadMsg)));
        }

#pragma warning disable 0169
        static Delegate cb_openFileChooser;
        static Delegate GetOpenFileChooserHandler ()
        {
            if (cb_openFileChooser == null)
                cb_openFileChooser = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr>) n_OpenFileChooser);
            return cb_openFileChooser;
        }

        static void n_OpenFileChooser (IntPtr jnienv, IntPtr native__this, IntPtr native_uploadMsg)
        {
            OpenFileWebChromeClient __this = Java.Lang.Object.GetObject<OpenFileWebChromeClient> (native__this, JniHandleOwnership.DoNotTransfer);
            var uploadMsg = Java.Lang.Object.GetObject<IValueCallback> (native_uploadMsg, JniHandleOwnership.DoNotTransfer);
            __this.OpenFileChooser (uploadMsg);
        }
#pragma warning restore 0169
    }
}

接下来,由于C#缺乏匿名内部类,我们需要一个显式的类,在这里命名为MyOpenFileWebChromeClient

namespace Scratch.FileUpload {
    class MyOpenFileWebChromeClient : OpenFileWebChromeClient {

        Action<IValueCallback> cb;

        public MyOpenFileWebChromeClient(Action<IValueCallback> cb)
        {
            this.cb = cb;
        }

        public override void OpenFileChooser (IValueCallback uploadMsg)
        {
            cb (uploadMsg);
        }
    }

Activity端口与您提到的博客文章几乎相同,只是它使用MyOpenFileWebChromeClient而不是匿名内部类。我还更新了一些逻辑以显示OnActivityResult()接收到的URI:

namespace Scratch.FileUpload {

    [Activity (Label = "Scratch.FileUpload", MainLauncher = true)]
    public class Activity1 : Activity
    {
        private WebView wv;
        private IValueCallback mUploadMessage;
        const int FilechooserResultcode = 1;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            wv = new WebView (this);
            wv.SetWebViewClient(new WebViewClient());
            wv.SetWebChromeClient(new MyOpenFileWebChromeClient(uploadMsg => {
                        mUploadMessage = uploadMsg;
                        var intent = new Intent (Intent.ActionGetContent);
                        intent.AddCategory(Intent.CategoryOpenable);
                        intent.SetType("image/*");
                        StartActivityForResult(Intent.CreateChooser(intent, "File Chooser"),
                            FilechooserResultcode);
            }));

            SetHtml(null);

            SetContentView(wv);
        }

        void SetHtml(string filename)
        {
            string html = @"<html>
<body>
<h1>Hello, world!</h1>
<p>Input Box:</p>
<input type=""file"" />
<p>URI: " + filename + @"
</body>
</html>";
            wv.LoadData(html, "text/html", "utf-8");
        }

        protected override void OnActivityResult (int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult (requestCode, resultCode, data);

            if (requestCode == FilechooserResultcode) {
                if (mUploadMessage == null)
                    return;
                var result = data == null || resultCode != Result.Ok
                    ? null
                    : data.Data;
                SetHtml(result.ToString());
                mUploadMessage.OnReceiveValue(result);
                mUploadMessage = null;
            }
        }
    }
}

很遗憾,现在是纯粹的恶行时刻。上述声明对于MyOpenFileWebChromeClient存在问题,因为它不起作用,原因与M0S的博客不能在匿名内部类声明中使用@Override相同:您构建应用程序的android.jar未声明openFileChooser()方法。
构建过程将生成Android Callable Wrappers,其中必须包含有效的Java代码。问题在于,生成的代码对于重写的方法和接口方法使用@Override,导致MyOpenFileWebChromeClient的Android Callable Wrapper出现问题。
package scratch.fileupload;


public class MyOpenFileWebChromeClient
extends android.webkit.WebChromeClient
{
    static final String __md_methods;
    static {
        __md_methods = 
            "n_openFileChooser:(Landroid/webkit/ValueCallback;)V:GetOpenFileChooserHandler\n" +
            "";
        mono.android.Runtime.register ("Scratch.FileUpload.MyOpenFileWebChromeClient, Scratch.FileUpload, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", MyOpenFileWebChromeClient.class, __md_methods);
    }

    @Override
    public void openFileChooser (android.webkit.ValueCallback p0)
    {
        n_openFileChooser (p0);
    }

    private native void n_openFileChooser (android.webkit.ValueCallback p0);

    java.util.ArrayList refList;
    public void monodroidAddReference (java.lang.Object obj)
    {
        if (refList == null)
            refList = new java.util.ArrayList ();
        refList.add (obj);
    }

    public void monodroidClearReferences ()
    {
        if (refList != null)
            refList.clear ();
    }
}

显然,在 MyOpenFileWebChromeClient.openFileChooser() 上使用 @Override 会生成编译器错误,那我们该如何解决呢?通过提供自己的 @Override 注释来实现!
package scratch.fileupload;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

将上述内容放入名为Override.java的文件中,将其添加到项目中,并将其构建操作设置为AndroidJavaSource
生成的项目之所以有效,是因为我们在与MyOpenFileWebChromeClient类型相同的包中提供了自定义的@Override注释。(这要求您知道生成的包名称,并且为您执行此操作的每个包提供单独的@Override注释。)在同一包中的类型优先于导入的名称,甚至优于来自java.lang的名称,因此我们的自定义@Override注释不仅可以编译,而且由MyOpenFileWebChromeClient Android可调用包在优先于java.lang.Override注释的情况下使用。
我说过这是纯粹的无节制的恶作剧,不是吗?

1
我的天啊!这是一些绕过方式来让它工作。幸运的是,在Mono for Android 4.2中已经修复了这个问题!http://docs.xamarin.com/android/Releases/Mono_For_Android_4/Mono_For_Android_4.2#Bug_Fixes - Cheesebaron
这个问题解决了吗?在我的网页视图中,文件上传弹出窗口无法正常工作。 - Heemanshu Bhalla

0

步骤1 = 文件上传将工作,我们需要在Android清单中授予读/写权限。在MainActivity.cs中

step2=
private Action<int, Result, Intent> resultCallbackvalue;

public void StartActivity(Intent intent, int requestCode, Action<int, Result, Intent> resultCallback)
{
this.resultCallbackvalue = resultCallback;
StartActivityForResult(intent, requestCode);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (this.resultCallbackvalue != null)
{
this.resultCallbackvalue(requestCode, resultCode, data);
this.resultCallbackvalue = null;
}
}
step3= add ExtendedChromeClient,cs Inheriting from : WebChromeClient

private static int filechooser = 1;
private IValueCallback message;
private MainActivity activity = null;

  public ExtendedChromeClient(MainActivity context)
    {
        this.activity = context;
    }

  public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
    {
        this.message = filePathCallback;
        Intent chooserIntent = fileChooserParams.CreateIntent();
        chooserIntent.AddCategory(Intent.CategoryOpenable);
        this.activity.StartActivity(Intent.CreateChooser(chooserIntent, "File Chooser"), filechooser, this.OnActivityResult);
        return true;
    }

  private void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        if (data != null)
        {
            if (requestCode == filechooser)
            {
                if (null == this.message)
                {
                    return;
                }

                this.message.OnReceiveValue(WebChromeClient.FileChooserParams.ParseResult((int)resultCode, data));
                this.message = null;
            }
        }
    }

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