I\'m trying to use monodroid with webkit to create an app. I am having a problem with letting the html page call a javascript method, which would be an interface to a method
Let's take a step back. You want to invoke C# code from JavaScript. If you don't mind squinting just-so, it's quite straightforward.
First, let's start with our Layout XML:
Now we can get to app itself:
[Activity (Label = "Scratch.WebKit", MainLauncher = true)]
public class Activity1 : Activity
{
const string html = @"
This is a paragraph.
";
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
WebView view = FindViewById(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
is the HTML content we're going to show. The only interesting thing is that we provide a /button/@onClick
attribute which invokes the JavaScript fragment Foo.run()
. Note the method name ("run") and that it starts with a lowercase 'r'; we will return to this later.
There are three other things of note:
view.Settings.JavaScriptEnabled=true
. Without this, we can't use JavaScript.We call view.SetWebChromeClient()
with an instance of a MyWebChromeClient
class (defined later). This is a bit of "cargo-cult programming": if we don't provide it, things don't work; I don't know why. If we instead do the seemingly equivalent view.SetWebChromeClient(new WebChromeClient())
, we get an error at runtime:
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
This makes no sense to me either.
view.AddJavascriptInterface()
to associate the JavaScript name "Foo"
with an instance of the class Foo
.Now we need the MyWebChromeClient
class:
class MyWebChromeClient : WebChromeClient {
}
Note that it doesn't actually do anything, so it's all the more interesting that just using a WebChromeClient
instance causes things to fail. :-/
Finally, we get to the "interesting" bit, the Foo
class which was associated above with the "Foo"
JavaScript variable:
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();
}
}
It just shows a short message when the Run()
method is invoked.
During the Mono for Android build process, Android Callable Wrappers are created for every Java.Lang.Object
subclass, which declares all overridden methods and all implemented Java interfaces. This includes the above Foo
class, resulting in the 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")
was invoked, this wasn't associating the JavaScript "Foo"
variable with the C# type. This was associating the JavaScript "Foo"
variable with an Android Callable Wrapper instance associated with the instance of the C# type. (Ah, indirections...)
Now we get to the aforementioned "squinting." The C# Foo
class implemented the Java.Lang.IRunnable
interface, which is the C# binding for the java.lang.Runnable
interface. The Android Callable Wrapper thus declares that it implements the java.lang.Runnable
interface, and declares the Runnable.run
method. Android, and thus JavaScript-within-Android, does not "see" your C# types. They instead see the Android Callable Wrappers. Consequently, the JavaScript code isn't calling Foo.Run()
(capital 'R'), it's calling Foo.run()
(lowercase 'r'), because the type that Android/JavaScript has access to declares a run()
method, not a Run()
method.
When JavaScript invokes Foo.run()
, then the Android Callable Wrapper scratch.webview.Foo.run()
method is invoked which, through the joy that is JNI, results in the execution of the Foo.Run()
C# method, which is really all you wanted to do in the first place.
If you don't like having the JavaScript method named run()
, or you want parameters, or any number of other things, your world gets much more complicated (at least until Mono for Android 4.2 and [Export]
support). You would need to do one of two things: