垃圾收集后Javascript的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()); }); } } 

HTML文件“page.html”如下所示:

      <!--  -->  var javaApp = null; function javaCallback(data) { try { alert("javaApp=" + javaApp + "(type=" + typeof javaApp + "), data=" + data); javaApp.callback(data); } catch (e) { alert("caugt exception: " + e); } }        

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

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

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

但是,过了一段时间(2-7分钟之间的任何事情),不再检索回调,但只打印如下行所示的记录:

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

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

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

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

非常感谢您的建议!

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

我通过在Java中创建一个实例变量bridge解决了这个问题,该实例变量bridge保存了通过setMember()发送到Javascript的JavaScriptBridge实例。 这样,防止了实例的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()); }); } 

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

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