我有用于上传文件的网页。用户使用 <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/