Liste Neuigkeiten Hintergrundartikel Kommentare Programmieren/Java IT-Sicherheit Computer Ratgeber & Tipps

JavaFX WebView-/WebKit-Fehler bei der JavaScript-to-Java-Kommunikation (Java 8)

Hinzugefügt am 04.12.2017 von Frank Hissen

Wir verwenden Java seit fast zwei Jahrzehnten zur Entwicklung von Produkten. Als eine integrierte WebKit-Engine in die Java-Plattform eingeführt wurde - seit JavaFX -, wurden die Entwicklungsmöglichkeiten erheblich erweitert. Denn so ist es nun möglich, HTML5-Web-Technologien einfach in eigene Desktop-Anwendungen zu integrieren.

JavaScript-to-Java Bridge

Jede Entwicklungsplattform mit integriertem Web-Browser unterstützt normalerweise die bidirektionale Kommunikation mit JavaScript. Dies tut auch der JavaFX-WebView bzw. die JavaFX-WebEngine. Die Verwendung ist einfach und intuitiv. Nach einem Update der JRE, die wir in eines unserer Produkte integrieren (eingebettete JVM), mussten wir leider feststellen, dass die JavaScript-Kommunikation nicht mehr funktioniert. Die Lösung - einmal gefunden - ist zwar sehr einfach, die interne Verhaltensänderung der JRE scheint aber undokumentiert zu sein.

JavaScript-to-Java Kommunikation

Möchte man Methodenaufrufe (Parameterübergabe ist natürlich ebenfalls in beiden Richtungen möglich, wird im Folgenden aber weggelassen) von JavaScript nach Java durchführen, geht dies nur auf Basis von Objekten, also mit Hilfe der Instanzierung einer eigenen "Brücken"-Klasse. So eine Klasse könnte einfach wie folgt aussehen:

public class MyBridgeClass { public void doit() { //do something } }

In HTML würde der zugehörige Upcall von JS in die Java-Welt dann einfach so aussehen:

<p><span style="cursor: pointer;" onclick="myapp.doit()">Aktion in Java</span></p>

Um nun die Verbindung der Java- mit der JavaScript-Welt herzustellen, ist folgender Java-Code notwendig, der das entsprechende WebView- bzw. WebEngine-Objekt der JavaFX-Anwendung konfiguriert:

//your WebView's WebEngine object engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() { @Override public void changed(ObservableValue<? extends State> ov, State oldState, State newState) { if (newState == State.SUCCEEDED) { JSObject win = (JSObject) engine.executeScript("window"); win.setMember("myapp", new MyBridgeClass()); //<-- !!! } } });

Dies hatte bisher auf diese einfache Weise immer funktioniert und war für unsere Erfordernisse ausreichend. Nach dem JRE-Update schien jedoch der JavaScript-Call in der integrierten HTML-Seite wie oben gezeigt keinen Effekt mehr zu haben. Letztendlich haben wir den JS-Call gekapselt und mit einem try...catch umgeben, so dass wir die folgende Exception von JavaScript erhielten:
TypeError: 'doit' is not a function

Innerhalb von Eclipse, also innerhalb der Entwicklungsumgebung, hat der alte Code jedoch nach wie vor funktioniert! Nur bei der Nutzung innerhalb der Applikation, also mit einer eingebetteten JRE, war der Upcall von JavaScript wirkungslos. Diese Veränderung im Verhalten der JRE erfolgte zwischen den Versionen u91 und u151. Wir unterstützen immer noch Windows-XP-Nutzer und verwenden daher auch parallel eine ältere Java-8-Runtime, um Probleme mit der JRE und dem integrierten WebKit unter XP zu vermeiden. Was die Verhaltensänderung angeht, so scheint einfach das Objekt new MyBridgeClass() zur Laufzeit verloren zu gehen (Garbage Collection), da keine interne Referenz auf das Objekt zu existieren scheint. Die Verwendung einer globalen Variable als Referenz auf das Objekt / die Instanz ändert das:

public class MyApplication{ public static void main(String[] args){ // your application code } //... private MyBridgeClass js2java_instance = new MyBridgeClass(); //... //your WebView's WebEngine object engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() { @Override public void changed(ObservableValue<? extends State> ov, State oldState, State newState) { if (newState == State.SUCCEEDED) { JSObject win = (JSObject) engine.executeScript("window"); win.setMember("myapp", js2java_instance); //<-- !!! } } }); //...

Dies behebt den Fehler und alles funktioniert wie vorher. Der 'alte' Ansatz der Objektübergabe an Java(FX) ist übrigens immer noch Teil der Oracle-Dokumentation (Tutorial): Making Upcalls from JavaScript to JavaFX zeigt zum Zeitpunkt des Schreibens dieses Artikels noch genau die erste Codevariante. Auch auf Stack Overflow gibt es glücklicherweise einen entsprechenden Diskussionsthread zum Thema: Javascript bridge / upcall to JavaFX (via JSObject.setMember() method) breaks when distributing.

Die Lösung ist natürlich simpel und in den meisten Fällen wird man ohnehin mit einem globalen Objekt arbeiten und den Fehler gar nicht bemerken. Dieser Artikel ist aber ein Hinweis für alle, die nur eine einfache Version des Codes abgeleitet aus dem originalen Oracle-WebView-Tutorial verwenden.

Über den Autor: Frank Hissen (Informatiker & Java-/Software-Entwickler)

Hissen IT ist ein kleines Unternehmen mit dem Fokus Software-Individualentwicklung, Programmierung und IT-Beratung. Inhaber Diplom-Informatiker Frank Hissen hat über 25 Jahre Erfahrung in verschiedenen Positionen als Experte in IT-Projekten.

Schlagworte

Java 8, JavaFX, WebKit, WebView, WebEngine, JavaScript, Java-to-JavaScript, JavaScript-to-Java, Bridge, Kommunikation, Upcalls

Kategorien: Programmieren/Java


Kommentare

Eigenen Kommentar hinzufügen

Teilen / Weiterempfehlen

Wenn Sie diese Seite gut finden, teilen Sie es doch ihren Kontakten mit:

Mail Facebook Twitter Pinterest LinkedIn
reddit Digg StumbleUpon XING
WhatsApp Telegram