让我们退后一步。您想从JavaScript调用C#代码。如果您不介意努力看清,这非常简单。
首先,让我们从Layout 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);
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”开头;稍后我们将回到这个问题。
还有三件需要注意的事情:
- 我们使用
view.Settings.JavaScriptEnabled=true
启用JavaScript。如果没有这个设置,我们就不能使用JavaScript。
我们使用一个MyWebChromeClient
类的实例来调用view.SetWebChromeClient()
(稍后定义)。这有点像“ cargo-cult programming”,如果我们不提供它,事情就无法正常工作;我不知道为什么。如果我们改用看似等效的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
我也觉得这毫无意义。
- 我们调用
view.AddJavascriptInterface()
将JavaScript名称"Foo"
与类Foo
的实例关联。
现在我们需要MyWebChromeClient
类:
class MyWebChromeClient : WebChromeClient {
}
注意,它实际上并没有做任何事情,所以更有趣的是,只使用一个
WebChromeClient
实例就会导致事情失败。 :-/
最后,我们来到了“有趣”的部分,即与JavaScript变量
"Foo"
相关联的
Foo
类:
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构建过程中,为每个Java.Lang.Object
子类创建Android Callable Wrappers,这包括了上面的Foo
类,声明了所有重写的方法和实现的Java接口,从而生成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 ();
}
当调用
view.AddJavascriptInterface(new Foo(this), "Foo")
时,它并没有将 JavaScript 的
"Foo"
变量与 C# 类型关联。它实际上是将 JavaScript 的
"Foo"
变量与与 C# 类型实例关联的 Android Callable Wrapper 实例关联起来。(哦,间接引用...)
现在我们来到了前面提到的"眯眼看"。C# 的
Foo
类实现了
Java.Lang.IRunnable
接口,这是
java.lang.Runnable
接口的 C# 绑定。因此,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()
方法,而不是一个
Run()
方法。
当 JavaScript 调用
Foo.run()
时,会调用 Android Callable Wrapper
scratch.webview.Foo.run()
方法,通过 JNI 的乐趣,最终执行
Foo.Run()
C# 方法,这实际上是你最初想做的事情。
但我不喜欢 run()!
如果您不喜欢将 JavaScript 方法命名为
run()
,或者希望添加参数等其他任何内容,您的世界会变得更加复杂(直到 Mono for Android 4.2 和
[Export]
支持)。您需要执行以下两个步骤之一:
1.找到一个已经绑定的接口或虚拟类方法,它提供了您想要的名称和签名。然后重写该方法/实现该接口,情况看起来与上面的示例相似。
2.自己编写 Java 类。在
monodroid邮件列表上询问更多详细信息。这个答案已经够长了。
[Export]
属性仅在付费版本的 Xamarin 中可用,免费版本不包含此功能。 - GSerg