垃圾收集后Javascript的JavaFx WebView回调失败

lud*_*h_r 9 javascript java garbage-collection javafx webview

我目前正在开发基于JavaFX的应用程序,用户可以在该应用程序中与世界地图上标记的地点进行交互.为此,我使用的方法类似于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 ])用于官方教程).

运行程序一段时间后,回调变量无法预测地失去其状态!为了演示这种行为,我开发了一个最小的工作/失败示例.我在Windows 10计算机上使用64位的jdk1.8.0_121.

JavaFx应用程序如下所示:

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

}
Run Code Online (Sandbox Code Playgroud)

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

<!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>
Run Code Online (Sandbox Code Playgroud)

javaApp单击"Send data to Java in endless loop"按钮可以观察回调变量的状态.它将不断尝试运行回调方法javaApp.callback,这会在Java应用程序中生成一些日志消息.警报用作备份通信渠道(似乎总是起作用,目前用作解决办法,但事情并非如此......).

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

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

但是,过了一段时间(2-7分钟之内),不再检索回调,但只打印以下行的记录:

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

现在打印变量'undefined'而不是Java实例路径.一个奇怪的观察是,状态javaApp并非真正"未定义".使用typeofreturn object,javaApp === undefined求值为false.这与callback-call不会抛出异常的事实一致(否则,"caugt exception: "将打印一个以其开头的警报).

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

那里有什么东西?你知道如何进一步调试这个问题吗?我找不到任何相关的知道错误......

非常感谢您的建议!

PS:当包含Javascript代码以通过Leaflet显示世界地图时,问题被再现得更快(cf [1]).大部分时间加载或移动地图会立即导致GC完成其工作.在调试这个原始问题时,我将问题追溯到此处提供的最小示例.

lud*_*h_r 9

我通过bridge在Java中创建一个实例变量解决了这个问题,该变量保存了JavaScriptBridge通过Javascript发送的实例setMember().这样,防止了实例的Gargbage Collection.

相关代码段:

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

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

由于感觉更像是一种解决方法,我现在不会接受它作为正确的答案,除非有人能解释为什么这种行为应该是预期的/是正确的.