我正在尝试定期更新FXML中的Google地图标记。我尝试使用Timer和新的Thread
执行此操作,但无法使其中任何一个工作。
我使用简单的任务测试了新的Thread
,以更新我的用户界面中的TextField
,这很正常。
但是,当我使用我需要更新地图的实际代码时:
@FXML
public void handleTracking() throws IOException, InterruptedException {
new Thread() {
@Override
public void run() {
while (true) {
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);");
try {
Thread.sleep(555);
} catch (InterruptedException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}.start();
}
我收到输出消息:
Exception in thread "Thread-26" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-26
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.scene.web.WebEngine.checkThread(WebEngine.java:1216)
at javafx.scene.web.WebEngine.executeScript(WebEngine.java:980)
at de.fkfs.v2x.eval.FXMLDocumentController$1.run(FXMLDocumentController.java:84=)
当我使用Timer时会发生类似的事情,它适用于更新标签的任务,但是如果我尝试更新标记位置,它会抛出消息:
Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
答案 0 :(得分:1)
对用户界面的更新,包括对webEngine.executeScript(...)
的调用必须才能在FX应用程序主题上执行。
另一方面,FX应用程序线程(有效地)是用于呈现UI和处理用户输入的线程。因此,如果您使用无限循环或其他长时间运行的进程阻止此线程,或者如果您计划在该线程上运行太多事情,则会使UI无响应。
您在代码中尝试执行的操作似乎是尽可能快地更新UI。如果您将循环放在FX应用程序线程中,您将完全阻止它:如果您将它放在后台线程上并使用Platform.runLater(...)
计划更新,您将使FX应用程序线程泛滥太多更新并阻止它执行它通常的工作,它会变得没有反应。
这里的一般解决方案围绕这样一个事实:经常更新UI真的很多。人眼只能以有限的速率检测可见的变化,并且在技术方面,您受到限制,例如,物理屏幕和底层图形软件的刷新率。 JavaFX尝试以不超过60Hz的速度更新UI(在当前实现中)。因此,更新频率比基础JavaFX工具包更新场景更为重要。
AnimationTimer
提供了handle
方法,无论出现频率如何,每个场景更新都会保证调用一次。在FX应用程序线程上调用AnimationTimer.handle(...)
,因此您可以在此安全地更改UI。因此,您可以使用以下方式实现跟踪:
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();
}
唯一需要注意的是,因为在FX应用程序线程上调用handle()
,所以不应在此处执行任何长时间运行的代码。看起来好像FileImport.getGpsPosition()
方法执行了一些IO操作,所以应该将它委托给后台线程。这里的技巧,即Task
等JavaFX类使用的技巧,是从后台线程不断更新值,仅调度Platform.runLater(...)
的调用。一个尚未处理。
首先,只需定义一个表示位置的简单类(使其不可变,因此它是线程安全的):
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 ;
}
}
现在:
@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();
}
这种方式的工作原理是它为当前位置创建(线程安全)持有者,并尽快更新它。当它更新它时,它(原子地)也检查当前值是否为null
。如果是null
,则会通过Platform.runLater()
安排更新。如果没有,它只是更新值,但不安排新的UI更新。
UI更新(原子地)获取当前(即最近的)值并将其设置为null,表示它已准备好接收新的UI更新。然后它处理新的更新。
通过这种方式,您可以“限制”UI更新,以便仅在处理当前更新时调度新更新,从而避免使用过多请求来充斥UI线程。
答案 1 :(得分:0)
必须在FX应用程序线程内更新所有JavaFX UI元素!
如果使用其他主题,请务必使用platform.Runlater()
更新您的UI元素!