让我们退后一步。您想从 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' 开头;我们稍后再讨论。
还有其他三点需要注意:
- 我们使用
view.Settings.JavaScriptEnabled=true 启用JavaScript。没有这个,我们就无法使用 JavaScript。
-
我们使用MyWebChromeClient 类(稍后定义)的实例调用view.SetWebChromeClient()。这有点像“货物崇拜编程”:如果我们不提供它,事情就不起作用;我不知道为什么。如果我们改为执行看似等效的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 实例会导致事情失败。 :-/
最后,我们来到了“有趣”的部分,即上面与 "Foo" JavaScript 变量相关联的 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,它声明了所有重写的方法和所有实现的 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
}
当 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()!
如果您不喜欢名为 run() 的 JavaScript 方法,或者您需要参数或其他任何东西,那么您的世界会变得更加复杂(至少在 Mono for Android 4.2 和 [Export] 支持之前) .您需要做以下两件事之一:
- 找到提供所需名称和签名的现有绑定接口或虚拟类方法。然后重写方法/实现接口,看起来和上面的例子很相似。
- 滚动您自己的 Java 类。在monodroid mailing list 上询问更多详情。这个答案越来越长了。