如何找出导致此 Java FX 应用程序线程异常的原因?



我正在尝试创建一个简单的游戏,但我已经在后台失败了。 在收到此错误之前,我的代码只能工作几秒钟:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException 
at javafx.scene.Parent.updateCachedBounds(Parent.java:1592) 
at javafx.scene.Parent.recomputeBounds(Parent.java:1535) 
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1388) 
at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
at javafx.scene.Node.updateGeomBounds(Node.java:3579) 
at javafx.scene.Node.getGeomBounds(Node.java:3532) 
at javafx.scene.Node.getLocalBounds(Node.java:3480) 
at javafx.scene.Node.updateTxBounds(Node.java:3643) 
at javafx.scene.Node.getTransformedBounds(Node.java:3426) 
at javafx.scene.Node.updateBounds(Node.java:559) 
at javafx.scene.Parent.updateBounds(Parent.java:1719) 
at javafx.scene.Parent.updateBounds(Parent.java:1717) 
at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2404) 
at com.sun.javafx.tk.Toolkit.lambda$runPulse$29(Toolkit.java:398) 
at java.security.AccessController.doPrivileged(Native Method) 
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:397) 
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:424) 
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$403(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method) 
at com.sun.glass.ui.gtk.GtkApplication.lambda$null$48(GtkApplication.java:139)
at java.lang.Thread.run(Thread.java:748)

我试着自己找出来,但我不知道。

这是我的代码:

public class RocketMain extends Application{
int width = 640;
int height = 1000;
Pane root;
Stars stars;
public static void main(String[] args){
Application.launch();
}
@Override
public void start(Stage primaryStage) throws Exception {
root = new Pane();
Scene sc = new Scene(root, width, height);
primaryStage.setScene(sc);
primaryStage.setResizable(false);
primaryStage.centerOnScreen();
stars = new Stars(width, height);
new Thread(() -> stars.createStars()).start();
new Thread(() -> update()).start();
root.getChildren().add(stars);
primaryStage.show();
primaryStage.setOnCloseRequest(e -> {
Platform.exit();
System.exit(0);
});
}
public void update(){
while(true){
Platform.runLater(() -> {
stars.getChildren().removeAll(stars.getChildren());
stars.getChildren().addAll(stars.stars);
root.getChildren().removeAll(root.getChildren());
root.getChildren().add(stars);
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

不断生成新精灵并在屏幕外移动/删除它们的星星类:

public class Stars extends Pane {
public ArrayList<Rectangle> stars;
ArrayList<Rectangle> rem;
double width, height;
public Stars(int width, int height){
stars = new ArrayList<>();
rem = new ArrayList<>();
this.width = width;
this.height = height;
}
public void createStars(){
int slowing = 3;
while(true){
if (Math.random() < (1.0/slowing)){
int size = (int) Math.floor(Math.random()*8);
Rectangle temp = new Rectangle(Math.random() * width, -10, size, size);
temp.setFill(Color.BLACK);
this.stars.add(temp);
Platform.runLater(() -> {
this.getChildren().remove(temp);
this.getChildren().add(temp);
});
}
for (Rectangle r : stars) {
r.setY(r.getY() + r.getHeight() / slowing);
if (r.getY() > height / 2) {
rem.add(r);
}
}
stars.removeAll(rem);
rem.clear();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}    
}    
}

蒂亚我希望这是可以理解

您似乎已将几乎所有从作为场景一部分的后台线程节点更新的代码移动到Platform.runLater调用中。堆栈跟踪表明情况确实如此。

不过,您错过了一个发生更新的地方:

r.setY(r.getY() + r.getHeight() / slowing);

在增强型for循环中createStars.

但是,还有另一个问题可能会导致类似的问题,但堆栈跟踪表明在此运行中情况并非如此:

您可以从后台线程更新列表,但从 javafx 应用程序头读取它们。没有为此进行同步,但这可能会导致其中一个线程访问处于不一致状态的列表。

但是,您的逻辑比需要的要复杂得多。如果我们忽略Thread.sleep,您在每次循环迭代中运行的代码不会花费很长时间来执行。JavaFX 为您提供了一个类,允许您在 JavaFX 应用程序线程上调度定期运行的逻辑,我建议使用 (Timeline(。

public class Stars extends Pane {
double width, height;
private final Timeline timeline;
public void startAnimation() {
timeline.play();
}
public Stars(int width, int height){
timeline = new Timeline(new KeyFrame(Duration.millis(20), new EventHandler<ActionEvent>() {
private int iteration = 0;
private final Random random = new Random();
private final List<Rectangle> stars = new LinkedList<>();
private final Set<Rectangle> toRemove = new HashSet<>();
@Override
public void handle(ActionEvent event) {
// iteration is 0 on every 5th iteration to simulate both background threads at once
iteration = (iteration + 1) % 5;
final int slowing = 3;
if (random.nextInt(slowing) == 0) { // also true in 1 of 3 cases
int size = random.nextInt(8);
Rectangle temp = new Rectangle(random.nextDouble() * Stars.this.width, -10, size, size);
temp.setFill(Color.BLACK);
this.stars.add(temp);
getChildren().add(temp);
}
Iterator<Rectangle> iter = stars.iterator();
while (iter.hasNext()) {
Rectangle r = iter.next();
if (r.getY() > Stars.this.height / 2) {
// move rect list to set
toRemove.add(r);
iter.remove();
}
}
if (iteration == 0) {
// logic from background thread with lower frequency
getChildren().removeAll(toRemove);
toRemove.clear();
}
}
}));
timeline.setCycleCount(Animation.INDEFINITE);
this.width = width;
this.height = height;
}
}

这样您需要做的就是调用startAnimation,不需要线程,没有并发问题。

进一步说明:

list.removeAll(list);

应改为

list.clear();

由于您通过以下方式跟进

list.addAll(list2);

您也可以将这两行替换为

list.setAll(list2);

(ObservableList提供此功能(

最新更新