我有一个想法可以让它工作。其中一部分是关于“如何绑定虚方法”的基本操作,而另一部分则是纯粹的恶意行为。
首先,我们需要一个“中间人”。由于
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
注释的情况下使用。
我说过这是纯粹的无节制的恶作剧,不是吗?