JavaFX JSON 从 URL 线程解析冻结 UI

JavaFX JSON parsing from URL threading freezes UI

我在一个线程中有这个任务 运行。问题是它每次执行时都会冻结 UI 。当互联网速度慢时,冻结时间会更长。如何防止 UI 冻结,即使它仍在从 url 收集数据?

Task<Void> task = new Task<Void>(){
        @Override
        public Void call() throws Exception {
            while (true) {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run(){
                        String json = null;
                        try {
                            psname = null;
                            PumpSites n = table.getSelectionModel().getSelectedItem();
                            psname = n.getPs();
                            if(psname == "Cubacub")
                                json = readUrl(""); //read json from thingspeak.com webpage
                            else if(psname == "Canduman")
                                json = readUrl("");
                        } catch (InterruptedIOException iioe)
                        {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        }
                        catch (IOException ioe)
                        {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        }
                        catch (Exception e1) {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        } 

                        Gson gson = new Gson();        
                        Page page = gson.fromJson(json, Page.class);

                        for (Item item : page.feeds)
                        {
                            det2 = 1;
                            btn1.setText(item.field1);
                            btn2.setText(item.field2);
                            btn3.setText(item.field3);
                            btn4.setText(item.field4);
                            btn5.setText(item.field5);
                            f2 = Float.parseFloat(item.field2);
                            f3 = Float.parseFloat(item.field3);
                            //float f5 = Float.parseFloat(item.field5);
                            if (f2 <= 10.0)
                            {
                                btn1.setTextFill(Color.RED);
                                btn2.setTextFill(Color.RED);
                            }
                            else
                            {
                                btn1.setTextFill(Color.BLUE);
                                btn2.setTextFill(Color.BLUE);
                            }
                            if (f3 < 0.9 || f3 > 1.2)
                            {
                                btn3.setTextFill(Color.RED);
                            }
                            else
                            {
                                btn3.setTextFill(Color.BLUE);
                            }
                            /*if (f5 > 5.0)
                            {
                                btn5.setTextFill(Color.RED);
                            }
                            else
                            {
                                btn5.setTextFill(Color.BLUE);
                            }*/
                            btn4.setTextFill(Color.BLUE);
                        }   
                        if(det2 == 0)
                        {
                            btn1.setTextFill(Color.RED);
                            btn2.setTextFill(Color.RED);
                            btn3.setTextFill(Color.RED);
                            btn4.setTextFill(Color.RED);
                            btn5.setTextFill(Color.RED);
                            btn1.setText("NULL");
                            btn2.setText("NULL");
                            btn3.setText("NULL");
                            btn4.setText("NULL");
                            btn5.setText("NULL");
                        }
                        det2 = 0;

                    }
                });
                Thread.sleep(10000);
            }
        }
    };
    Thread th = new Thread(task);
    th.setDaemon(true);
    th.start();

问题是它每次执行时都会冻结UI。当互联网速度慢时,冻结时间会更长。如何防止 UI 冻结,即使它仍在从 url 收集数据?

UI 线程冻结,因为您仍在执行 JavaFX 应用程序线程 (Platform.runLater) 上的所有逻辑。

你应该这样做:

public Void call() throws Exception 
{
        while (true) 
        {
            try
            {   
                //get json 
            } catch(Exception e) 
            {   
                Platform.runLater(new Runnable()    
                {
                    @Override
                    public void run()
                    {
                        //set buttons color and text here
                    }   
                 }
             }
             //Rest of your logic here
         }
}

这个想法是,所有要从单独的线程修改 UI 组件的东西都应该在 Platform.runLater

中处理

如果您使用后台线程调用 Platform.runLater 并使用 long-运行ning Runnable 作为参数,您实际上什么也得不到。 Runnable 仍然 运行 在 JavaFX 应用程序线程上冻结您的应用程序。

相反,您应该在后台线程上收集所有数据并将其处理到只需要调整一些属性的程度的场景。然后您使用 Platform.runLater 进行这些更新。

但好消息是,有一个专为这种情况设计的 class 可以稍微简化您的代码:ScheduledService.

只需确保您不以任何方式从后台线程访问 GUI(无论是读取还是设置属性)。

下面的示例简化示例应演示一般方法。它在后台线程上计算通过 Spinner 选择的值的一些倍数,每次计算之间延迟 10 秒:

@Override
public void start(Stage primaryStage) {
    Spinner<Integer> spinner = new Spinner(1, 100, 1);

    // ensure the value is available in a way that allows synchronisation
    final AtomicReference<Integer> input = new AtomicReference<>(spinner.getValue());
    spinner.valueProperty().addListener((o, oldValue, newValue) -> input.set(newValue));

    final int outputCount = 10;

    GridPane root = new GridPane();
    root.add(spinner, 0, 0, 2, 1);

    // create output grid
    Text[] output = new Text[outputCount];
    for (int i = 1; i <= output.length; i++) {
        Text text = new Text(Integer.toString(spinner.getValue() * i));
        output[i - 1] = text;
        root.addRow(i, new Text("Value multiplied by " + i + " = "), text);
    }
    root.setPrefWidth(300);

    ScheduledService<int[]> service = new ScheduledService<int[]>() {

        @Override
        protected Task<int[]> createTask() {
            return new Task<int[]>() {

                @Override
                protected int[] call() throws Exception {
                    // retrieve value and set it to null to denote a change
                    // that was already handled to avoid doing unnecessary
                    // work
                    Integer value = input.getAndSet(null);

                    int[] result = null;

                    if (value != null) {
                        int valueAsInt = value;
                        result = new int[outputCount];
                        for (int i = 0; i < outputCount; i++) {
                            result[i] = (i + 1) * valueAsInt;
                        }
                    }

                    // simpulate delay
                    Thread.sleep(2000);

                    return result;
                }

            };
        }

    };

    service.valueProperty().addListener((o, oldValue, newValue) -> {
        // update GUI
        if (newValue != null) {
            for (int i = 0; i < outputCount; i++) {
                output[i].setText(Integer.toString(newValue[i]));
            }
        }
    });

    service.setPeriod(Duration.seconds(10));

    // make sure service uses a daemon thread
    service.setExecutor(Executors.newSingleThreadScheduledExecutor((Runnable r) -> {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }));

    service.start();

    Scene scene = new Scene(root);
    primaryStage.setScene(scene);
    primaryStage.show();
}

我建议查看 ScheduledService 的 javadoc 以熟悉它的功能。它还允许对任务中的异常做出反应和指定退避策略。