Javascript桥接/ upcall to JavaFX(通过JSObject.setMember()方法)在分布时中断

时间:2022-02-08 12:24:28

The Problem

这个问题

I spent several hours trying to determine why my distributed code fails and yet my source code when debugging with the IDE (NetBeans) works without issue. I have found a solution and am posting to help others that might have similar issues. BTW: I'm a self-taught programmer and might be missing a few fundamental concepts -- feel free to educate me.

我花了几个小时试图确定为什么我的分布式代码失败,但是在使用IDE (NetBeans)进行调试时,我的源代码没有问题。我已经找到了一个解决方案,并正在发布帖子,帮助其他可能有类似问题的人。顺便说一句:我是一个自学的程序员,可能会漏掉一些基本的概念——请随意教育我。

Background Information

背景信息

Using a WebView control within JavaFX application I load a webpage from an html file. I want to use JavaScript to handle the HTML side of things but I also need to freely pass information between Java and JavaScript (both directions). Works great to use the WebEngine.executeScript() method for Java initiated transfers and to use JSObject.setMember() in Java to set up a way for JavaScript to initiate information transfer to Java.

使用JavaFX应用程序中的WebView控件,我从一个html文件加载一个网页。我希望使用JavaScript处理HTML方面的内容,但我还需要在Java和JavaScript之间*传递信息(两个方向)。使用WebEngine.executeScript()方法处理Java发起的传输,并在Java中使用JSObject.setMember()设置JavaScript初始化向Java传输信息的方法,效果非常好。

Setting up the link (this way breaks later):

建立链接(这种方式稍后会失效):

/*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*/
}

The code above will work great until distributing it and attempting to run the JAR file. After hours of searching and testing different tweaks I finally narrowed the problem down to the JavaLink object itself (I eventually learned that you can use try-catch blocks in JavaScript which enabled me to catch the error: "TypeError: showMsg is not a function...").

上面的代码在分发它并尝试运行JAR文件之前会运行得很好。经过数小时的搜索和测试,我最终将问题缩小到JavaLink对象本身(我最终了解到可以使用JavaScript中的try-catch块来捕获错误:“TypeError: showMsg不是函数…”)。

2 个解决方案

#1


4  

The Solution

解决方案

I found that declaring a global variable to hold an instance of the JavaLink class and passing that as a parameter into the setMember() method fixes it so that the app now runs both in the IDE as well as a standalone JAR:

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

JavaLink jl;

...

    jl = new JavaLink();

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

Why!?

为什么! ?

I'm guessing this has to do with garbage collection and that the JVM does not keep a reference to JavaLink unless you force it to by declaring a global variable. Any other ideas?

我猜这与垃圾收集有关,JVM不会保存对JavaLink的引用,除非您通过声明全局变量强制它这样做。任何其他想法?

#2


2  

Just to demo that the hypothesis postulated in @DatuPuti's answer appears to be correct. Here's a quick test. Pressing the HTML button increments the counter in the HTML page, and also outputs to the system console. After forcing garbage collection by pressing the "GC" button, the updates to the system console stop:

为了证明@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);
    }
}

Of course, as stated in the other answer, if you declare an instance variable

当然,如其他答案所述,如果您声明一个实例变量。

private JavaLink jl = new JavaLink();

and replace

和替换

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

with

js.setMember("javaLink", jl);

the problem is fixed, and the updates continue to appear in the console after calling System.gc();.

问题是固定的,在调用System.gc()之后,更新将继续出现在控制台。

#1


4  

The Solution

解决方案

I found that declaring a global variable to hold an instance of the JavaLink class and passing that as a parameter into the setMember() method fixes it so that the app now runs both in the IDE as well as a standalone JAR:

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

JavaLink jl;

...

    jl = new JavaLink();

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

Why!?

为什么! ?

I'm guessing this has to do with garbage collection and that the JVM does not keep a reference to JavaLink unless you force it to by declaring a global variable. Any other ideas?

我猜这与垃圾收集有关,JVM不会保存对JavaLink的引用,除非您通过声明全局变量强制它这样做。任何其他想法?

#2


2  

Just to demo that the hypothesis postulated in @DatuPuti's answer appears to be correct. Here's a quick test. Pressing the HTML button increments the counter in the HTML page, and also outputs to the system console. After forcing garbage collection by pressing the "GC" button, the updates to the system console stop:

为了证明@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);
    }
}

Of course, as stated in the other answer, if you declare an instance variable

当然,如其他答案所述,如果您声明一个实例变量。

private JavaLink jl = new JavaLink();

and replace

和替换

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

with

js.setMember("javaLink", jl);

the problem is fixed, and the updates continue to appear in the console after calling System.gc();.

问题是固定的,在调用System.gc()之后,更新将继续出现在控制台。