c# - Android 的单声道,WebView 输入字段文件选择器不起作用

标签 c# android file-upload webview xamarin.android

我有用于上传文件的网页。用户使用 <input type="file" /> 选择文件并按下提交按钮,一切正常。现在我需要创建包含简单 webview 并且必须像 web 版本一样工作的 android 应用程序(在 C# 上使用 mono for android)。

但我偶然发现了这个问题 - 当我点击 Choose file 时按钮,然后文件对话框不会打开。

我用谷歌搜索了几天这个问题,但没有找到任何解决方案。看起来有 workaround on Java platform ,但它不适用于 C#。

有人知道如何让它发挥作用吗?

最佳答案

我知道如何让它发挥作用。它的一部分是标准的“如何绑定(bind)虚拟方法”,而另一部分是纯粹的邪恶。

首先,我们需要一个“中介”。由于 WebChromeClient 没有声明 openFileChooser() 方法,我们需要声明一个声明的版本,名为 OpenFileWebChromeClient。它声明了一个 virtual OpenFileChooser 方法,并为其提供了一个绑定(bind)以便可以覆盖它:

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 覆盖方法和接口(interface)方法,导致 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的文件中,将其添加到项目中,并将其Build action设置为AndroidJavaSource

生成的项目之所以有效,是因为我们在与 MyOpenFileWebChromeClient 类型相同的包中提供了自定义 @Override 注释。 (因此这要求您知道生成的包名称是什么,并且您为每个包提供单独的 @Override 注释。)同一包中的类型优先于导入名称,甚至来自 java.lang 的名称,因此我们的自定义 @Override 注释不仅可以编译,而且还被 MyOpenFileWebChromeClient android 可调用包装器使用优先于 java.lang.Override 注释。

我确实说过这纯粹是彻头彻尾的邪恶,不是吗?

关于c# - Android 的单声道,WebView 输入字段文件选择器不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9376142/

相关文章:

javascript - 将 C# 列表(多个)转换为 JavaScript 关联数组

android - 如何通过json数据或字符串将uiimage发送到另一个设备?

android - 快速联系人徽章无法使用 Intent i=new Intent(Intent.ACTION_PICK,Contacts.CONTENT_URI) startActivityForResult(i 0) 传递 Intent

android - 恢复堆栈中不是最顶层的 fragment

文件上传验证

c# - Windows 10 中的 Html 编辑类似于邮件应用程序?

c# - 如何在 C# 中使用 DateTime 值显示没有时间的日期?

c# - FormsAuthentication.SetAuthCookie 在 MVC 5 中没有 [Authorize]

java - 批量上传和 Java servlet

php - 在 OpenShift 上使用 PHP 上传文件