我正在尝试使用monodroid和webkit来创建一个应用程序。我有一个问题,让html页面调用一个javascript方法,这将是我的应用程序中的方法的接口。有关如何在java中执行此操作的http://developer.android.com/guide/webapps/webview.html有一个教程,但相同的代码不适用于C#。
call monodroid method from javascript example的这个交换链接了一些关于使用JNI解决monodroid和javascript接口方法问题的线程,但是我无法让它工作。
现在,我正在尝试使用一些代码指令,但没有成功:
// Java
class RunnableInvoker {
Runnable r;
public RunnableInvoker (Runnable r) {
this.r = r;
}
// must match the javascript name:
public void doSomething() {
r.run ();
}
}
From C#, you'd create a class that implements Java.Lang.IRunnable:
// C#
class SomeAction : Java.Lang.Object, Java.Lang.IRunnable {
Action a;
public void SomeAction(Action a) {this.a = a;}
public void Run () {a();}
}
Then to wire things up:
// The C# action to invoke
var action = new SomeAction(() => {/* ... */});
// Create the JavaScript bridge object:
IntPtr RunnableInvoker_Class = JNIEnv.FindClass("RunnableInvoker");
IntPtr RunnableInvoker_ctor = JNIEnv.GetMethodID (RunnableInvoker_Class, "<init>", "(Ljava/lang/Runnable;)V");
IntPtr instance = JNIEnv.NewObject(RunnableInvoker_Class, RunnableInvoker_ctor, new JValue (action));
// Hook up WebView to JS object
web_view.AddJavascriptInterface (new Java.Lang.Object(instance, JniHandleOwnership.TransferLocalRef), "Android");
这段代码应该可以让人们在应用程序内部的html页面上点击一个按钮,调用java,然后调用C#。这没有用。
我想知道是否有人知道问题是什么,或者是另一个想法,所以我可以使用monodroid让webkit中加载一个html按钮来调用ac#方法,或者能够让我的c#代码调用一个javascript方法。
答案 0 :(得分:21)
让我们退后一步。您想从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>
现在我们可以进入app本身:
[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。我们使用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
这对我来说也没有意义。
view.AddJavascriptInterface()
将JavaScript名称"Foo"
与班级Foo
的实例相关联。现在我们需要MyWebChromeClient
类:
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构建过程中,为每个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有权访问的类型声明{{1 }}方法,不 run()
方法。
当JavaScript调用Run()
时,将调用Android Callable Wrapper Foo.run()
方法,该方法通过JNI的快乐导致执行scratch.webview.Foo.run()
C#方法,即真的只是你想做的一切。
如果你不喜欢名为Foo.Run()
的JavaScript方法,或者你想要参数或其他任何东西,你的世界会变得更加复杂(至少在Mono for Android 4.2和{{1支持)。你需要做两件事之一:
答案 1 :(得分:10)
// C#
// !!!
using Java.Interop; // add link to Mono.Android.Export
public class Activity1 : Activity
{
const string html = @"
<html>
<body>
<p>This is a paragraph.</p>
<button type=""button"" onClick=""Foo.SomeMethod('bla-bla')"">Click Me!</button>
</body>
</html>";
class Foo : Java.Lang.Object // do not need Java.Lang.IRunnable
{
Context context;
public Foo (Context context)
{
this.context = context;
}
[Export] // !!! do not work without Export
[JavascriptInterface] // This is also needed in API 17+
public string SomeMethod(string param)
{
Toast.MakeText (context, "This is a Toast from C#!" + param, ToastLength.Short).Show ();
}
}
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.AddJavascriptInterface (new Foo (this), "Foo");
view.LoadData (html, "text/html", null);
}
}
答案 2 :(得分:2)
@ kogr的回答对我有用。您不需要从IRunnable继承。您做需要添加对Mono.Android.Export.dll的引用,并按照他的建议使用[Export]标记公开的公共类方法。
我公开的方法返回 void ,而不是 string ,但除此之外它是相同的。
答案 3 :(得分:2)
除了what jonp said之外,如果你想在Android / Xamarin中来回发送JavaScript和C#的字符串,例如JSON字符串,并且你不能使用[Export]属性,那么你可以尝试使用
Android.Net.UrlQuerySanitizer.IValueSanitizer
它只有一种方法
string Sanitize(string value);
这对于在JS和C#之间传递字符串(JSON)很方便。不要忘记在JavaScript代码中使用小写清理。
答案 4 :(得分:1)
我发现在AddJavascriptInterface之后调用LoadData是必要的。此外,通过此更改,不需要分配WebChromeClient。例如,下面的修改版本运行良好:
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.AddJavascriptInterface (new Foo (this), "Foo");
view.LoadData (html, "text/html", null);
}
class Foo : Java.Lang.Object, Java.Lang.IRunnable
{
Context context;
public Foo (Context context)
{
this.context = context;
}
public void Run ()
{
Toast.MakeText (context, "This is a Toast from C#!", ToastLength.Short).Show ();
}
}
}
答案 5 :(得分:1)
添加此答案以补充“但我不喜欢run()”下的两个选项!在jonp's asnwer(因为我不喜欢跑!)。
[Export]
属性现已推出,需要付费版Xamarin
如果您使用的是免费版,则以下解决方法可能适合您。
继承Android.Webkit.WebChromeClient
,覆盖OnJsAlert
方法,并使用alert()
函数让您的网页“调用方法”,并在邮件中传递序列化数据。
这应该没问题,因为我们无论如何都需要WebChromeClient,你也应该完全控制网页源代码。
E.g:
private class AlertableWebChromeClient : Android.Webkit.WebChromeClient
{
private const string XAMARIN_DATA_ALERT_TAG = "XAMARIN_DATA\0";
public override bool OnJsAlert(Android.Webkit.WebView view, string url, string message, Android.Webkit.JsResult result)
{
if (message.StartsWith(XAMARIN_DATA_ALERT_TAG))
{
//Parse 'message' for data - it can be XML, JSON or whatever
result.Confirm();
return true;
}
else
{
return base.OnJsAlert(view, url, message, result);
}
}
}
在网页中:
if (window.xamarin_alert_callback != null) {
alert("XAMARIN_DATA\0" + JSON.stringify(data_object));
}
xamarin_alert_callback
用作标志,可以通过各种方式设置(例如WebView.LoadURL(javascript:...)
或WebView.AddJavascriptInterface(something, "xamarin_alert_callback")
),让页面知道它在启用警报的浏览器下运行。< / p>