Updates für die Benutzeroberfläche, einschließlich Anrufe an webEngine.executeScript(...)
müssen auf dem FX-Anwendungs-Thread ausgeführt werden.
Auf der anderen Seite ist der FX-Anwendungs-Thread (effektiv) der Thread, der zum Rendern der Benutzeroberfläche und zur Verarbeitung von Benutzereingaben verwendet wird. Wenn Sie also diesen Thread mit einer Endlosschleife oder einem anderen lang andauernden Prozess blockieren oder wenn Sie zu viele Dinge planen, die in diesem Thread ausgeführt werden, reagiert die UI nicht mehr.
Was Sie in Ihrem Code zu tun versuchen, scheint zu sein, die Benutzeroberfläche so schnell wie möglich zu aktualisieren. Wenn du die Schleife in den FX-Anwendungs-Thread legst, wirst du sie komplett blockieren: Wenn du sie in einen Hintergrund-Thread legst und die Updates unter Verwendung von Platform.runLater(...)
einplanst, überflutet du den FX-Anwendungs-Thread mit zu vielen Updates und verhinderst, dass er seine übliche Arbeit erledigt, und es wird nicht mehr reagieren.
Die allgemeine Lösung hier dreht sich um die Tatsache, dass es wirklich überflüssig ist, die Benutzeroberfläche so oft zu aktualisieren. Das menschliche Auge kann nur sichtbare Veränderungen mit einer begrenzten Rate erkennen, und in technologischer Hinsicht sind Sie durch z.B. die Aktualisierungsrate des physischen Bildschirms und der zugrunde liegenden grafischen Software. JavaFX versucht, die Benutzeroberfläche nicht mehr als 60 Hz zu aktualisieren (in der aktuellen Implementierung). Es macht also keinen Sinn, öfter zu aktualisieren, als das zugrunde liegende JavaFX-Toolkit die Szene aktualisiert.
Die AnimationTimer
bietet eine handle
Methode, die garantiert einmal pro Szeneupdate aufgerufen wird, egal wie oft das auftritt. AnimationTimer.handle(...)
wird im FX-Anwendungs-Thread aufgerufen, sodass Sie hier Änderungen an der Benutzeroberfläche vornehmen können. So könnten Sie Ihre Tracking-Implementierung mit:
private AnimationTimer tracker ;
public void initialize() {
tracker = new AnimationTimer() {
@Override
public void handle(long timestamp) {
try {
double ar[] = FileImport.getGpsPosition();
// System.out.println("Latitude: " + ar[0] + " Longitude: " + ar[1]);
double Ltd = ar[0];
double Lng = ar[1];
webEngine.executeScript(""
+ "window.lat = " + Ltd + ";"
+ "window.lon = " + Lng + ";"
+ "document.goToLocation(window.lat, window.lon);");
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
}
@FXML
public void handleTracking() {
tracker.start();
}
das einzige, was von hier vorsichtig zu sein, ist, dass, weil handle()
auf dem FX-Anwendung Gewinde aufgerufen wird, sollten Sie keine ausführen lang laufende Code hier. Es sieht so aus, als ob Ihre FileImport.getGpsPosition()
-Methode einige IO-Operationen ausführt, daher sollte sie wahrscheinlich an einen Hintergrundthread delegiert werden.Der Trick hier, der von JavaFX-Klassen wie Task
verwendet wird, besteht darin, einen Wert kontinuierlich von einem Hintergrundthread zu aktualisieren, und nur einen Aufruf an Platform.runLater(...)
planen, wenn einer nicht bereits aussteht.
Zunächst definieren nur eine einfache Klasse für den Standort darstellt (es unveränderlich, so ist es Thread-sicher):
class Location {
private final double longitude ;
private final double latitude ;
public Location(double longitude, double latitude) {
this.longitude = longitude ;
this.latitude = latitude ;
}
public double getLongitude() {
return longitude ;
}
public double getLatitude() {
return latitude ;
}
}
und jetzt:
@FXML
private void handleTracking() {
AtomicReference<Location> location = new AtomicReference<>(null);
Thread thread = new Thread(() -> {
try {
while (true) {
double[] ar[] = FileImport.getGpsPosition();
Location loc = new Location(ar[0], ar[1]);
if (location.getAndSet(loc) == null) {
Platform.runLater(() -> {
Location updateLoc = location.getAndSet(null);
webEngine.executeScript(""
+ "window.lat = " + updateLoc.getLatitude() + ";"
+ "window.lon = " + updateLoc.getLongitude() + ";"
+ "document.goToLocation(window.lat, window.lon);");
});
}
}
} catch (IOException exc) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
});
thread.setDaemon(true);
thread.start();
}
Die Art und Weise dies funktioniert, ist, dass Es erstellt einen (Thread-sicheren) Halter für den aktuellen Standort und aktualisiert ihn so schnell wie möglich. Wenn es aktualisiert wird, prüft es (atomar) auch, ob der aktuelle Wert null
ist. Wenn es null
ist, plant es ein UI-Update über Platform.runLater()
. Ist dies nicht der Fall, wird lediglich der Wert aktualisiert, es wird jedoch kein neues Benutzeroberflächen-Update geplant.
Das UI-Update (atomar) ruft den aktuellen (d. H. Neuesten) Wert ab und setzt es auf null, um anzuzeigen, dass es für ein neues UI-Update bereit ist. Es verarbeitet dann das neue Update.
Auf diese Weise "drosseln" Sie die UI-Aktualisierungen, so dass neue nur geplant werden, wenn die aktuelle verarbeitet wird. Vermeiden Sie, den UI-Thread mit zu vielen Anfragen zu überfluten.
Mögliches Duplikat von [java.lang.IllegalStateException: Nicht im FX-Anwendungs-Thread; currentThread = Thread-4] (http://stackoverflow.com/questions/29449297/java-lang-illegalstateexception-not-on-fx-application-thread-currentthread-t) – DVarga
Sie finden ein Beispiel für die Aktualisierung der Benutzeroberfläche von ein anderer Thread hier: http://stackoverflow.com/documentation/javafx/2230/threading/7291/updating-the-ui-using-platform-runlater#t=201607280912032640534 – fabian