在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按钮调用c#方法,或者能够让我的c#代码调用javascript方法.
解决方法
首先,让我们从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); // 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(); } }
这是如何工作的
在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询问更多详情.这个答案越来越长.