Monodroid Javascript 回调 [英] Monodroid Javascript Call-back

查看:18
本文介绍了Monodroid Javascript 回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 in my app. There is a tutorial on this at http://developer.android.com/guide/webapps/webview.html for how to do it in java, but the same code does not work on C#.

从javascript示例调用monodroid方法的这个交流链接了几个线程关于使用 JNI 解决 monodroid 和 javascript 接口方法的问题,但我无法让它工作.

This exchange at call monodroid method from javascript example linked a few threads about using JNI to get around a problem with monodroid and the javascript interface method, but I haven't been able to get it to work.

现在,我正在尝试使用一些代码指令,但没有成功:

Now, I am trying to use some code instructions but to no success:

// 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#.这没有用.

This code is supposed to be able to make it so someone on the html page inside the app can click on a button, invoke the java, which will then invoke C#. This hasnt worked.

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

I was wondering if anyone had an idea of what was the problem, or another idea so I can use monodroid to let a html button loaded in webkit to call a c# method, or to be able to have my c# code call a javascript method.

推荐答案

让我们退后一步.您想从 JavaScript 调用 C# 代码.如果你不介意斜视,那就很简单了.

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.

首先,让我们从布局 XML 开始:

First, let's start with our 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>

现在我们可以访问应用程序本身:

Now we can get to app itself:

[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"开头;我们稍后会回到这个话题.

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:

  1. 我们使用 view.Settings.JavaScriptEnabled=true 启用 JavaScript.没有这个,我们就不能使用 JavaScript.
  2. 我们使用 MyWebChromeClient 类(稍后定义)的实例调用 view.SetWebChromeClient().这有点像货物崇拜编程":如果我们不提供它,事情就行不通;我不知道为什么.如果我们改为执行看似等效的 view.SetWebChromeClient(new WebChromeClient()),我们会在运行时收到错误:

  1. We enable JavaScript with view.Settings.JavaScriptEnabled=true. Without this, we can't use JavaScript.
  2. 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.

现在我们需要 MyWebChromeClient 类:

class MyWebChromeClient : WebChromeClient {
}

请注意,它实际上并没有做任何事情,因此更有趣的是仅使用 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. :-/

最后,我们到了有趣"的部分,上面与 "Foo" JavaScript 变量相关联的 Foo 类:

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();
    }
}

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

It just shows a short message when the Run() method is invoked.

在 Mono for Android 构建过程中,Android Callable Wrappers 是为每个 Java.Lang.Object 子类,它声明所有重写的方法和所有实现的 Java 接口.这包括上面的 Foo 类,产生了 Android Callable Wrapper:

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
}

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

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...)

现在我们来看看前面提到的眯眼".C# Foo 类实现了Java.Lang.IRunnable 接口,它是java.lang.Runnable 接口的C# 绑定.因此,Android Callable Wrapper 声明它实现了java.lang.Runnable 接口,并声明了Runnable.run 方法.Android 以及 JavaScript-within-Android 不会看到"您的 C# 类型.他们看到的是 Android Callable Wrappers.因此,JavaScript 代码没有调用 Foo.Run()(大写 'R'),而是调用 Foo.run()(小写 'r'),因为Android/JavaScript 有权访问的类型声明了 run() 方法,不是Run() 方法.

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.

当 JavaScript 调用 Foo.run() 时,Android Callable Wrapper scratch.webview.Foo.run() 方法被调用JNI,导致 Foo.Run() C# 方法的执行,这确实是您首先想要做的.

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.

如果你不喜欢使用名为 run() 的 JavaScript 方法,或者你想要参数,或者任何数量的其他东西,你的世界会变得更加复杂(至少在 Mono for Android 之前)4.2 和 [Export] 支持).您需要做以下两件事之一:

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:

  1. 找到一个现有的绑定接口或虚拟类方法,提供您想要的名称和签名.然后覆盖方法/实现接口,事情看起来与上面的例子非常相似.
  2. 滚动您自己的 Java 类.在 monodroid 邮件列表上询问更多详情.这个答案越来越长.
  1. Find an existing bound interface or virtual class method that provides the name and signature that you want. Then override the method/implement the interface, and things look fairly similar to the example above.
  2. Roll your own Java class. Ask on the monodroid mailing list for more details. This answer is getting long as it is.

这篇关于Monodroid Javascript 回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆