让我们退后一步。您想要从 JavaScript 调用 C# 代码。如果你不介意眯着眼睛看的话,那就很简单了。
首先,让我们从布局 XML 开始:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<WebView
android:id="@+id/web"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
现在我们可以访问应用程序本身:
[Activity (Label = "Scratch.WebKit", MainLauncher = true)]
public class Activity1 : Activity
{
const string html = @"
<html>
<body>
<p>This is a paragraph.</p>
<button type=""button"" onClick=""Foo.run()"">Click Me!</button>
</body>
</html>";
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
WebView view = FindViewById<WebView>(Resource.Id.web);
view.Settings.JavaScriptEnabled = true;
view.SetWebChromeClient (new MyWebChromeClient ());
view.LoadData (html, "text/html", null);
view.AddJavascriptInterface(new Foo(this), "Foo");
}
}
Activity1.html
是我们要显示的 HTML 内容。唯一有趣的是我们提供了一个/button/@onClick
调用 JavaScript 片段的属性Foo.run()
。请注意方法名称(“run”)并且它以小写“r”开头;我们稍后再讨论这一点。
还有其他三件事值得注意:
- 我们启用 JavaScript
view.Settings.JavaScriptEnabled=true
。没有这个,我们就无法使用 JavaScript。
-
We call view.SetWebChromeClient()
与一个实例MyWebChromeClient
类(稍后定义)。这有点像“货物崇拜编程”:如果我们不提供它,事情就不起作用;如果我们不提供它,事情就无法进行。我不知道为什么。如果我们做看似等价的事情view.SetWebChromeClient(new WebChromeClient())
,我们在运行时遇到错误:
E/Web Console( 4865): Uncaught ReferenceError: Foo is not defined at data:text/html;null,%3Chtml%3E%3Cbody%3E%3Cp%3EThis%20is%20a%20paragraph.%3C/p%3E%3Cbutton%20type=%22button%22%20onClick=%22Foo.run()%22%3EClick%20Me!%3C/button%3E%3C/body%3E%3C/html%3E:1
这对我来说也毫无意义。
- We call
view.AddJavascriptInterface()
关联 JavaScript 名称"Foo"
与该类的一个实例Foo
.
现在我们需要MyWebChromeClient
class:
class MyWebChromeClient : WebChromeClient {
}
请注意,它实际上并没有执行任何操作,因此仅使用WebChromeClient
实例导致事情失败。 :-/
最后,我们到了“有趣”的地方,Foo
与上面关联的类"Foo"
JavaScript 变量:
class Foo : Java.Lang.Object, Java.Lang.IRunnable {
public Foo (Context context)
{
this.context = context;
}
Context context;
public void Run ()
{
Console.WriteLine ("Foo.Run invoked!");
Toast.MakeText (context, "This is a Toast from C#!", ToastLength.Short)
.Show();
}
}
只显示一条短消息Run()
方法被调用。
这是如何运作的
在 Mono for Android 构建过程中,Android 可调用包装器是为每一个创建的Java.Lang.Object
子类,它声明所有重写的方法和所有实现的 Java 接口。这包括以上内容Foo
类,生成 Android Callable Wrapper:
package scratch.webkit;
public class Foo
extends java.lang.Object
implements java.lang.Runnable
{
@Override
public void run ()
{
n_run ();
}
private native void n_run ();
// details omitted for clarity
}
When view.AddJavascriptInterface(new Foo(this), "Foo")
被调用,这没有关联 JavaScript"Foo"
C# 类型的变量。这是关联 JavaScript"Foo"
带有与 C# 类型实例关联的 Android Callable Wrapper 实例的变量。 (啊,间接...)
现在我们开始前面提到的“眯眼”。 C#Foo
类实现了Java.Lang.IRunnable
接口,这是 C# 的绑定java.lang.Runnable
界面。 Android Callable Wrapper 因此声明它实现了java.lang.Runnable
接口,并声明Runnable.run
方法。 Android 以及 Android 中的 JavaScript 不会“看到”您的 C# 类型。相反,他们看到的是 Android Callable Wrappers。因此,JavaScript 代码不会调用Foo.Run()
(大写“R”),它在召唤Foo.run()
(小写“r”),因为 Android/JavaScript 有权访问的类型声明了run()
方法,not a Run()
method.
当 JavaScript 调用时Foo.run()
,然后是 Android Callable Wrapperscratch.webview.Foo.run()
调用方法,通过 JNI 的乐趣,导致执行Foo.Run()
C# 方法,这确实是您最初想要做的。
但我不喜欢 run()!
如果您不喜欢命名 JavaScript 方法run()
,或者你想要参数,或者任何其他东西,你的世界会变得更加复杂(至少在 Mono for Android 4.2 和[Export]
支持)。您需要执行以下两件事之一:
- 查找提供所需名称和签名的现有绑定接口或虚拟类方法。然后重写方法/实现接口,事情看起来与上面的示例非常相似。
- 推出您自己的 Java 类。询问monodroid 邮件列表更多细节。这个答案越来越长了。