簡體   English   中英

垃圾收集后來自Javascript的JavaFx WebView回調失敗

[英]JavaFx WebView callback from Javascript failing after Garbage Collection

我目前正在開發基於 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 機器上使用 jdk1.8.0_121 64 位。

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”如下所示:

<!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的狀態可以通過點擊"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

然而,一段時間后(2-7 分鍾),不再檢索回調,但只打印如下行的日志:

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

現在打印變量會給出'undefined'而不是 Java 實例路徑。 一個奇怪的觀察是javaApp的狀態並不是真正的“未定義”。 使用typeof返回objectjavaApp === undefined計算為false 這與回調調用不會引發異常的事實一致(否則,將打印以"caugt exception: "開頭的警報)。

使用 Java VisualVM 顯示故障時間恰好與垃圾收集器被激活的時間一致。 這可以通過觀察堆內存消耗來看出,它從大約下降。 由於 GC,60MB 到 16MB。

怎么回事? 您知道我如何進一步調試問題嗎? 我找不到任何相關的知識錯誤...

非常感謝您的建議!

PS:當包含 Javascript 代碼以通過 Leaflet 顯示世界地圖時,該問題的重現速度要快得多(參見 [1])。 大多數情況下加載或移動地圖會立即導致 GC 完成其工作。 在調試這個原始問題時,我將問題追溯到這里提供的最小示例。

我通過在 Java 中創建一個實例變量bridge來解決這個問題,它保存通過setMember()發送到 Javascript 的JavaScriptBridge實例。 這樣,可以防止實例的垃圾回收。

相關代碼片段:

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 結合使用),但我仍然對這種意外行為感到惱火......

編輯:自Java 9起就記錄了此行為的解釋(感謝 @dsh 的澄清評論!當時我正在使用 Java 8,不幸的是手頭沒有這些信息......)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM