垃圾收集后来自Javascript的JavaFx WebView回调失败 [英] JavaFx WebView callback from Javascript failing after Garbage Collection

查看:24
本文介绍了垃圾收集后来自Javascript的JavaFx WebView回调失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在开发基于 JavaFX 的应用程序,用户可以在其中与世界地图上标记的地点进行交互.为此,我使用了一种类似于 中描述的方法http://captaincasa.blogspot.de/2014/01/javafx-and-osm-openstreetmap.html([1]).

I am currently working on a JavaFX based application, where users can interact with places that are marked on a world map. To do this, I am using an approach similiar to the one described in http://captaincasa.blogspot.de/2014/01/javafx-and-osm-openstreetmap.html ([1]).

但是,我面临着一个难以调试的问题,该问题与使用 WebEngine 的 setMember() 方法注入嵌入式 HTML 页面的 Javascript 回调变量有关(另请参见 https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/js-javafx.htm ([2]) 官方教程.

However, I am facing a hard-to-debug problem related to the Javascript callback variable injected to the embedded HTML-page using the WebEngine's setMember() method (see also https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/js-javafx.htm ([2]) for an official tutorial).

当程序运行一段时间后,回调变量出乎意料地失去了它的状态!为了演示这种行为,我开发了一个最小的工作/失败示例.我在 Windows 10 机器上使用 jdk1.8.0_121 64 位.

When running the program for a while, the callback variable is loosing its state unpredictably! To demonstrate this behaviour, I developed a minimal working/failing example. I am using jdk1.8.0_121 64-bit on a Windows 10 machine.

JavaFx 应用程序如下所示:

The JavaFx App looks as follows:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javafx.application.Application;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class WebViewJsCallbackTest extends Application {

    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

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

    public class JavaScriptBridge {
        public void callback(String data) {
            System.out.println("callback retrieved: " + data);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        WebView webView = new WebView();
        primaryStage.setScene(new Scene(new AnchorPane(webView)));
        primaryStage.show();

        final WebEngine webEngine = webView.getEngine();
        webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());

        webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
            if (newValue == State.SUCCEEDED) {
                JSObject window = (JSObject) webEngine.executeScript("window");
                window.setMember("javaApp", new JavaScriptBridge());
            }
        });

        webEngine.setOnAlert(event -> {
            System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
        });
    }

}

HTML 文件page.html"如下所示:

The HTML file "page.html" looks as follows:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<!-- use for in-browser debugging -->
<!-- <script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> -->
<script type="text/javascript">
    var javaApp = null;

    function javaCallback(data) {           
        try {
            alert("javaApp=" + javaApp + "(type=" + typeof javaApp + "), data=" + data);
            javaApp.callback(data);
        } catch (e) {
            alert("caugt exception: " + e);
        }
    }
</script>
</head>
<body>
    <button onclick="javaCallback('Test')">Send data to Java</button>
    <button onclick="setInterval(function(){ javaCallback('Test'); }, 1000)">Send data to Java in endless loop</button>
</body>
</html>

回调变量javaApp的状态可以通过点击无限循环发送数据到Java"按钮来观察.它将不断尝试通过 javaApp.callback 运行回调方法,这会在 Java 应用程序中产生一些日志消息.警报用作备份的额外通信渠道(似乎总是有效,目前用作变通方法,但事情并非如此......).

The state of the callback variable javaApp can be observed by clicking on the "Send data to Java in endless loop" button. It will continuously try to run the callback method via javaApp.callback, which produces some logging message in the Java app. Alerts are used as an additional communication channel to back things up (always seems to work and currently used as work-around, but that's not how things are ment to be...).

如果一切都按预期工作,则每次应打印类似于以下行的日志:

If everything is working as supposed, each time logging similiar to the following lines should be printed:

callback retrieved: Test
2017/01/27 21:26:11 alerted: javaApp=webviewtests.WebViewJsCallbackTest$JavaScriptBridge@51fac693(type=object), data=Test

然而,一段时间后(2-7 分钟),不再检索回调,但只打印如下行的日志:

However, after a while (anything from 2-7 minutes), no more callbacks are retrieved, but only loggings like the following line are printed:

2017/01/27 21:32:01 提醒:javaApp=undefined(type=object), data=Test

打印变量现在给出 'undefined' 而不是 Java 实例路径.一个奇怪的观察是 javaApp 的状态并不是真正的未定义".使用 typeof 返回 objectjavaApp === undefined 计算结果为 false.这是因为回调调用不会抛出异常(否则,将打印以 "caugt exception: " 开头的警报).

Printing the variable now gives 'undefined' instead of the Java instance path. A strange observation is that the state of javaApp is not truly "undefined". using typeof returnsobject, javaApp === undefined evaluates to false. This is in accordance with the fact that the callback-call does not throw an exception (otherwise, an alert starting with "caugt exception: " would be printed).

使用 Java VisualVM 显示故障时间恰好与垃圾收集器被激活的时间一致.这可以通过观察堆内存消耗来看出,它从大约下降.由于 GC,60MB 到 16MB.

Using Java VisualVM showed that the time of failure happens to coincide with the time the Garbage Collector is activated. This can be seen by observing the Heap memory consumption, which drops from approx. 60MB to 16MB due to GC.

那里发生了什么?您知道我如何进一步调试问题吗?我找不到任何相关的已知错误...

What's goining on there? Do you have any idea how I can further debug the issue? I could not find any related know bug...

非常感谢您的建议!

PS:当包含 Javascript 代码以通过 Leaflet 显示世界地图时,该问题的重现速度要快得多(参见 [1]).大多数情况下加载或移动地图会立即导致 GC 完成其工作.在调试这个原始问题时,我将问题追溯到这里提供的最小示例.

PS: the problem was reproduced much faster when including Javascript code to display a world map via Leaflet (cf [1]). Loading or shifting the map most of the time instantly caused the GC to do its job. While debugging this original issue, I traced the problem to the minimal example presented here.

推荐答案

我通过在 Java 中创建一个实例变量 bridge 来解决这个问题,该变量保存了发送到的 JavaScriptBridge 实例Javascript 通过 setMember().这样可以防止实例的垃圾回收.

I solved the problem by creating an instance variable bridge in Java that holds the JavaScriptBridge instance sent to Javascript via setMember(). This way, Gargbage Collection of the instance is prevented.

相关代码片段:

public class JavaScriptBridge {
    public void callback(String data) {
        System.out.println("callback retrieved: " + data);
    }
}

private JavaScriptBridge bridge;

@Override
public void start(Stage primaryStage) throws Exception {
    WebView webView = new WebView();
    primaryStage.setScene(new Scene(new AnchorPane(webView)));
    primaryStage.show();

    final WebEngine webEngine = webView.getEngine();
    webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm());

    bridge = new JavaScriptBridge();
    webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> {
        if (newValue == State.SUCCEEDED) {
            JSObject window = (JSObject) webEngine.executeScript("window");
            window.setMember("javaApp", bridge);
        }
    });

    webEngine.setOnAlert(event -> {
        System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData());
    });
}

尽管代码现在可以顺利运行(也与 Leaflet 结合使用),但我仍然对这种意外行为感到恼火......

Altough the code now works smoothly (also in conjunction with Leaflet), I am still irritated of this unexpected behaviour...

Java 9(感谢@dsh 的澄清评论!当时我正在使用 Java 8,不幸的是手头没有这些信息......)

The explanation for this behaviour is documented since Java 9 (thanks @dsh for your clarifying comment! I was working with Java 8 at the time and unfortunately didn't have this information at hand...)

这篇关于垃圾收集后来自Javascript的JavaFx WebView回调失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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