Javascript桥接/上传到JavaFX(通过JSObject.setMember()方法)在分发时中断

时间:2017-03-08 00:49:39

标签: javascript javafx jar bridge jsobject

问题

我花了几个小时试图确定我的分布式代码失败的原因,但是在使用IDE(NetBeans)调试时我的源代码没有问题。我找到了一个解决方案,并发帖帮助其他可能有类似问题的人。 顺便说一句:我是一名自学成才的程序员,可能会遗漏一些基本概念 - 随时教我。

背景资料

在JavaFX应用程序中使用WebView控件我从html文件加载网页。我想使用JavaScript来处理HTML方面,但我还需要在Java和JavaScript之间自由传递信息(两个方向)。非常适合使用WebEngine.executeScript()方法进行Java启动的传输,并使用Java中的JSObject.setMember()来设置JavaScript启动向Java传输信息的方法。

设置链接(这种方式稍后会中断):

/*Simple example class that gives method for 
JavaScript to send text to Java debugger console*/
public static class JavaLink {
    public void showMsg(String msg) {
        System.out.println(msg);
    }
}

...

/*This can be added in the initialize() method of  
the FXML controller with a reference to the WebEngine*/
public void initialize(URL url, ResourceBundle rb) {
    webE = webView.getEngine();

    //Retrieve a reference to the JavaScript window object
    JSObject jsObj = (JSObject)webE.executeScript("window");
    jsObj.setMember("javaLink", new JavaLink());
    /*Now in our JavaScript code we can type javaLink.showMsg("Hello!");
    which will send 'Hello!' to the debugger console*/
}

上面的代码可以很好地运行 ,直到分发 并尝试运行JAR文件。经过几个小时的搜索和测试不同的调整,我最终将问题缩小到JavaLink对象本身(我最终了解到你可以在JavaScript中使用try-catch块,这使我能够捕获错误:“ TypeError:showMsg不是功能...... “)。

3 个答案:

答案 0 :(得分:5)

解决方案

我发现声明一个全局变量来保存JavaLink类的一个实例并将其作为参数传递给setMember()方法修复它,以便应用程序现在既可以在IDE中运行,也可以在独立的JAR中运行: / p>

JavaLink jl;

...

    jl = new JavaLink();

    //replace anonymous instantiation of JavaLink with global variable
    jsObj.setMember("javaLink", jl); 

为什么!?

我猜这与垃圾收集有关,除非你通过声明一个全局变量来强制它,否则JVM不会保留对JavaLink的引用。还有其他想法吗?

答案 1 :(得分:2)

只是为了演示在@ DatuPuti的答案中假设的假设似乎是正确的。这是一个快速测试。按HTML按钮会增加HTML页面中的计数器,并输出到系统控制台。按“GC”按钮强制进行垃圾回收后,系统控制台的更新将停止:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class WebViewCallbackGCTest extends Application {

    private final String HTML = 
              "<html>"
            + "  <head>"
            + "    <script>"
            + "    var count = 0 ;"
            + "    function doCallback() {"
            + "      count++ ;"
            + "      javaLink.showMsg('Hello world '+count);"
            + "      document.getElementById('test').innerHTML='test '+count;"
            + "    }"
            + "    </script>"
            + "  </head>"
            + "  <body>"
            + "    <div>"
            + "      <button onclick='doCallback()'>Call Java</button>"
            + "    </div>"
            + "    <div id='test'></div>"
            + "  </body>"
            + "</html>" ;

    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        webView.getEngine().loadContent(HTML);

        JSObject js = (JSObject) webView.getEngine().executeScript("window");
        js.setMember("javaLink", new JavaLink());

        Button gc = new Button("GC");
        gc.setOnAction(e -> System.gc());

        BorderPane root = new BorderPane(webView);
        BorderPane.setAlignment(gc, Pos.CENTER);
        BorderPane.setMargin(gc, new Insets(5));
        root.setBottom(gc);
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }

    public static class JavaLink {
        public void showMsg(String msg) {
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

当然,如另一个答案中所述,如果您声明一个实例变量

private JavaLink jl = new JavaLink();

并替换

js.setMember("javaLink", new JavaLink());

js.setMember("javaLink", jl);

问题已解决,调用System.gc();后,更新会继续显示在控制台中。

答案 2 :(得分:0)

@DatuPuti的解决方案工作正常,但部分有效。就我而言,我在JavaLink类中有多个嵌套类。在执行@DatuPuti公开之前,所有嵌套的类链接都会断开,而在应用公开的解决方案之后,只有被调用的类才是保持链接的类,其余所有类仍在断开。这是我的JavaLink类的模型:

public class JavaLink{

    public NestedClass1 ClassName1;
    public NestedClass2 ClassName2;

    //Constructor
    public JavaLink(){
        ClassName1 = new NestedClass1();
        ClassName2 = new NestedClass2();
    }

    //Nested classes being called from Javascript
    public class NestedClass1(){}
    public class NestedClass2(){}

}

链接将这样创建:

JavaLink javaLink;
...

    javaLink = new JavaLink();
    jsObject.setMember("JavaLink", javaLink);

然后,从Java语言中,我将类方法称为:

JavaLink.ClassName1.method();
JavaLink.ClassName2.method();

这里有一个探查:当调用ClassName1方法发生崩溃时,ClassName2会断开链接,并且使用Javascript不再可用。如果在调用ClassName2方法时发生崩溃,也会发生同样的情况。

最适合我的解决方案(除了公开的解决方案之外):

除了在更高的范围内声明JavaLink之外,声明所有嵌套的类还将使它们保留链接。例如(与示例保持相同的参考 类模型):

JavaLink javaLink;
JavaLink.NestedClass1 nestedClass1;
JavaLink.NestedClass2 nestedClass2;

    javaLink = new JavaLink();
    nestedClass1 = javaLink.new NestedClass1();
    nestedClass2 = javaLink.new NestedClass2();

    //Creating an Object array to store classes instances
    Object[] javaLinkClasses = new Object[2];
    javaLinkClasses[0] = nestedClass1;
    javaLinkClasses[1] = nestedClass2;
    jsObject.setMember("JavaLink", javaLinkClasses); //Setting member as an array

最后,为了从Java嵌套类中调用方法,就需要对象重新分配,就像这样(Javascript):

JavaLink.NestedClass1 = JavaLink[0];
JavaLink.NestedClass2 = JavaLink[1];

//Now we are able to call again methods from nested classes without them unlinking
JavaLink.NestedClass1.method();

我希望这可以帮助面临同样问题的人们。我正在将Java JDK 1.8与IntelliJIDEA一起使用。