我今天才刚刚开始学习 JavaFX,我试图通过制作 Snake 克隆来了解更多关于它的信息,但我在线程处理方面遇到了麻烦。我想创建一个线程来更新蛇在屏幕上的位置,但不能以正常的 Runnable 线程方式使用一个线程,因为我在该线程中使用 JavaFX 来更新绘制到屏幕上的矩形的位置(这是我学到的)你不能做,而必须使用任务、服务、Platform.runLater 等?)我正在从扩展 JavaFX.scene.layout.Pane 创建线程的类,我正在尝试使用任务来更新蛇的位置. 我的问题是:任务似乎只运行了一次或两次就退出了,我没有给出任何中断。
扩展 Pane 的类的构造函数(Snake 类扩展了 Group):
public GameFrame(){
this.setPrefSize(800, 600);
Snake snake = new Snake();
this.getChildren().add(snake);
taskThread = new Thread(new Task<Void>() {
protected Void call() throws Exception {
while(!Thread.currentThread().isInterrupted()){
snake.updatePosition();
try{
Thread.sleep(1000);
} catch(InterruptedException e){
break;
}
}
return null;
}
});
taskThread.start();
}
Run Code Online (Sandbox Code Playgroud)
我觉得我实际上并没有掌握在这里做的最好的事情是什么,而且我正在尝试做的事情可能是骇人听闻的。对我应该做什么或如何解决这个问题有什么建议吗?
JavaFX 中线程的基本规则(如果你已经理解了其中的一些,请原谅我,我只是想完整一点)是:
Node
场景图一部分的状态的东西都应该在 FX 应用线程上执行为了帮助实现这些,JavaFX API 提供了一个Task
类。这有一个call()
返回值的方法;它Runnable
可以作为Thread
构造函数的参数提供,或传递给Executor
. 它还提供了保证将要在FX应用程序线程执行有用回调,如setOnSucceeded
,setOnFailed
和各种update...()
方法该更新属性,如progress
和message
对FX应用程序线程。
但是,Task
该类实际上是为一次性任务设计的:例如,考虑需要从数据库中检索数据的应用程序,这可能需要时间。这些执行特定操作并返回结果。您的情况有些不同,因为您的线程正在连续执行。
在这种情况下,最好使用 simple Thread
and usePlatform.runLater(...)
来更新 UI。Platform.runLater(...)
接受 aRunnable
并run()
在 FX 应用程序线程上执行其方法。
我不清楚为什么您的代码会按照您描述的方式运行,但假设方法调用snake.updatePosition()
会导致 UI 发生更改,那么应该在 FX 应用程序线程上执行该更改。无论如何我会尝试
taskThread = new Thread(new Runnable() {
public void run() {
while(!Thread.currentThread().isInterrupted()){
Platform.runLater(new Runnable() {
@Override
public void run() {
snake.updatePosition();
}
});
try{
Thread.sleep(1000);
} catch(InterruptedException e){
break;
}
}
}
});
Run Code Online (Sandbox Code Playgroud)
如果您使用的是 Java 8,那么使用 lambdas 替换匿名内部类后,这一切看起来好多了:
taskThread = new Thread( () -> {
while (! Thread.currentThread().isInterrupted()) {
Platform.runLater( snake::updatePosition );
try {
Thread.sleep(1000);
} catch (InterruptedException exc) {
break ;
}
}
});
Run Code Online (Sandbox Code Playgroud)
JavaFX 中用于定期执行某事的另一种技术是(ab?)使用动画:
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
snake.updatePosition();
}
}
));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Run Code Online (Sandbox Code Playgroud)
或者,在 Java 8 中,有点圆滑
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),
event -> snake.updatePosition()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Run Code Online (Sandbox Code Playgroud)