Monodroid Javascript回拨

d10*_*fan 8 javascript xamarin.android

我正在尝试使用monodroid和webkit来创建一个应用程序.我有一个问题,让html页面调用一个javascript方法,这将是我的应用程序中的方法的接口.http://developer.android.com/guide/webapps/webview.html上有一个关于如何在java中执行此操作的教程,但相同的代码不适用于C#.

这个来自javascript示例的调用monodroid方法的交换链接了一些关于使用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");
Run Code Online (Sandbox Code Playgroud)

这段代码应该能够让人们在应用程序内的html页面上点击一个按钮,调用java,然后调用C#.这没有用.

我想知道是否有人知道问题是什么,或者另一个想法,所以我可以使用monodroid让webkit中的html按钮调用ac#方法,或者能够让我的c#代码调用javascript方法.

jon*_*onp 22

我们退一步吧.您想从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>
Run Code Online (Sandbox Code Playgroud)

现在我们可以进入应用程序本身:

[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");
    }
}
Run Code Online (Sandbox Code Playgroud)

Activity1.html是我们要展示的HTML内容.唯一有趣的是我们提供了一个/button/@onClick调用JavaScript片段的属性Foo.run().注意方法名称("run"),它以小写的"r"开头; 我们稍后会回到这里.

还有三个值得注意的事项:

  1. 我们启用JavaScript view.Settings.JavaScriptEnabled=true.没有这个,我们就不能使用JavaScript.
  2. 我们调用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
    
    Run Code Online (Sandbox Code Playgroud)

    这对我来说也没有意义.

  3. 我们调用view.AddJavascriptInterface()将JavaScript名称"Foo"与类的实例相关联Foo.

现在我们需要这个MyWebChromeClient类:

class MyWebChromeClient : WebChromeClient {
}
Run Code Online (Sandbox Code Playgroud)

请注意,它实际上并没有做任何事情,因此使用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 Code Online (Sandbox Code Playgroud)

它只是在Run()调用方法时显示一条短消息.

这是如何工作的

在Mono for Android构建过程中,为每个子类创建了Android Callable WrappersJava.Lang.Object,它声明了所有重写的方法和所有实现的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
}
Run Code Online (Sandbox Code Playgroud)

view.AddJavascriptInterface(new Foo(this), "Foo")调用时,这并未将JavaScript "Foo"变量与C#类型相关联.这是将JavaScript "Foo"变量与与C#类型的实例关联的Android Callable Wrapper实例相关联.(啊,间接......)

现在我们进入前面提到的"眯眼".C#Foo类实现了Java.Lang.IRunnable接口,即接口的C#绑定java.lang.Runnable.因此,Android Callable Wrapper声明它实现了java.lang.Runnable接口,并声明了该Runnable.run方法.Android,以及Android内部的JavaScript,不会"看到"您的C#类型.他们改为看到Android Callable Wrappers.因此,JavaScript代码是不是要求Foo.Run()(资本"R"),它调用Foo.run()(小写字母"R"),因为安卓/ 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邮件列表上询问更多详情.这个答案越来越长.


小智 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 好答案.请注意,如果您想要定位Android API 17+,则需要使用`[JavascriptInterface]`以及`[Export]`注释您的`SomeMethod(string)`方法.您可以选择通过将`[Export]`注释更改为`[Export("SomeOtherMethod")]`来指定要导出的方法的名称. (2认同)