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

标签 java exception javafx

我正在尝试创建一个简单的游戏,但我已经在背景方面失败了。 在出现此错误之前,我的代码仅运行几秒钟:

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();
            }
        }
    }
}

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

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);

createStars 内增强的 for 循环中。

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

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

但是您的逻辑比实际需要的更复杂。如果我们忽略 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 提供此功能)

关于java - 如何找出导致此 Java FX 应用程序线程异常的原因?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59796573/

相关文章:

java - itext5 在上下文中签署图像

C#:如何声明一个已检查的 switch-expression-bodyed 函数?

JavaFX:为什么当有 setter 时 Window.xProperty() 和 Window.yProperty() 是只读的?

java - 为什么我的按钮在 Javafx 中表现得很奇怪

java - 如何获取发送、接收字节和网络带宽利用率

java - 查找整数的所有因式分解的算法

java - 无法解决 "java.sql.SQLException: [Microsoft][ODBC SQL Server Driver]Invalid Descriptor Index"错误

java - 关于抛出异常的基本问题

java - 异常(exception)情况。我该如何解决这个问题?第一步

java - 我应该将哪个版本的 OpenJFX (JavaFX) 和 Scene Builder 与 Amazon Corretto 11 (OpenJDK) 一起使用?