2017-01-27 36 views
6

Obecnie pracuję nad aplikacją opartą na JavaFX, w której użytkownicy mogą wchodzić w interakcję z miejscami oznaczonymi na mapie świata. Aby to zrobić, używam podejścia podobnego do opisanego w http://captaincasa.blogspot.de/2014/01/javafx-and-osm-openstreetmap.html ([1]).Wywołanie zwrotne JavaFx WebView z błędu Javascript po pobraniu śmieci

Mam jednak do czynienia z trudnym do debugowania problemem związanym ze zmienną zwrotną JavaScript wstrzykniętą na osadzoną stronę HTML przy użyciu metody setMember() WebEngine (patrz także https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/js-javafx.htm ([2]) w celu uzyskania oficjalnego samouczka) .

Podczas uruchamiania programu przez pewien czas zmienna wywołania zwrotnego traci nieprzewidywalny stan! Aby zademonstrować to zachowanie, opracowałem minimalny przykład pracy/awarii. Używam 64-bitowego jdk1.8.0_121 na komputerze z systemem Windows 10.

JavaFX aplikacji wygląda następująco:

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

} 

Plik HTML "page.html" wygląda następująco:

<!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> 

Stan zwrotnego zmiennej javaApp można zaobserwować klikając przycisk "Send data to Java in endless loop". Będzie stale próbował uruchomić metodę wywołania zwrotnego za pośrednictwem javaApp.callback, która generuje komunikat logowania w aplikacji Java. Alarmy są używane jako dodatkowy kanał komunikacyjny do podtrzymania działania (zawsze wydaje się, że działa i jest obecnie używany jako obejście, ale to nie jest tak, jak powinno być ...).

Jeśli wszystko działa tak jak powinien, za każdym razem zalogowaniu podobne do następujących linii powinna zostać wydrukowane:

callback retrieved: Test 
2017/01/27 21:26:11 alerted: [email protected]c693(type=object), data=Test 

Jednak po pewnym czasie (wszystko od 2-7 minut), nie więcej wywołania zwrotne są pobierane, ale tylko Loggings jak następującej linii są drukowane:

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

Drukowanie zmienną daje teraz 'undefined' zamiast instancji ścieżki Java. Dziwną obserwacją jest to, że stan javaApp nie jest naprawdę "nieokreślony". użycie typeof zwraca object, javaApp === undefined ocenia na false. Jest to zgodne z tym, że wywołanie zwrotne nie rzuca wyjątku (w przeciwnym razie wydrukowałby się alert rozpoczynający się od "caugt exception: ").

Korzystanie z Java VisualVM pokazało, że czas awarii przypadał na czas aktywacji Garbage Collectora. Można to zaobserwować obserwując zużycie pamięci Heap, która spada z ok. 60 MB do 16 MB dzięki GC.

Co tam jest? Czy masz pojęcie, jak mogę dalej debugować problem? Nie mogłem znaleźć żadnego powiązanego z nim błędu ...

Bardzo dziękuję za radę!

PS: problem został odtworzony znacznie szybciej, gdy kod JavaScript został włączony, aby wyświetlić mapę świata za pośrednictwem ulotki (patrz [1]). Ładowanie lub przesuwanie mapy przez większość czasu natychmiast spowodowało, że GC wykonał swoje zadanie. Podczas debugowania tego oryginalnego problemu wyśledziłem problem w przedstawionym tutaj minimalnym przykładzie.

+0

Miałem ten sam problem z jdk 1.8.0_121. Wtedy zdałem sobie sprawę, że działa znaleźć z innego komputera, który działa jdk 1.8.0_60. Przełączono na 1.8.0_60, a problem został rozwiązany. –

+0

@AliAyadJalil: Powrót do starej wersji nie zawsze jest pożądany, a nawet możliwy. : -/ –

Odpowiedz

7

Rozwiązałem problem przez utworzenie zmiennej instancji bridge w języku Java, która przechowuje instancję JavaScriptBridge wysłaną do Javascript za pośrednictwem setMember(). W ten sposób zapobiega się pobieraniu kolekcji instancji Gargbage.

Odpowiedni fragment kodu:

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

Jakoże kod teraz działa płynnie (także w połączeniu z pacjenta), nadal jestem zirytowany tego nieoczekiwanego zachowania ...

Ponieważ czuje się bardziej jak obejść, na razie nie przyjmuję go jako poprawnej odpowiedzi, chyba że ktoś może wyjaśnić, dlaczego to zachowanie powinno być oczekiwane/jest prawidłowe.

+0

Zobacz: https://bugs.openjdk.java.net/browse/JDK-8154127 –

+0

Dziękuję bardzo twój kod zapisał mój dzień, powinien być zaakceptowany odpowiedź – Dinesh

+0

OMG. Cóż za okropny Heisenbug. Dzięki za tę odpowiedź milion. Spędziłem kilka dni szukając źródła problemu. –