【问题标题】:JavaFX: How to show error dialog when start method fails?JavaFX:启动方法失败时如何显示错误对话框?
【发布时间】:2016-06-27 16:28:34
【问题描述】:

在我的 JavaFX 应用程序中,我想在出现意外异常时显示错误对话框并退出应用程序。所以在我的主要方法中,我在启动应用程序之前设置了一个默认的未捕获异常处理程序:

setDefaultUncaughtExceptionHandler((thread, cause) -> {
    try {
        cause.printStackTrace();
        final Runnable showDialog = () -> {
           // create dialog and show
        };
        if (Platform.isFxApplicationThread()) {
           showDialog.run();
        } else {
           runAndWait(showDialog);
        }
    } catch (Throwable t) {
        // ???
    } finally {
        System.exit(-1);
    }
});

launch(MyApp.class);

说明:当未捕获的异常处理程序在 JavaFX 应用程序线程 (FXAT) 上执行时,我只运行用于显示对话框的代码。当 FXAT 不调用异常处理程序时,这当然不起作用。在这种情况下,我必须将代码推送到 FXAT。但我不能使用Platform.runLater,因为这样我的应用程序就会在显示对话框之前退出。所以,我创建了自定义方法runAndWait,它在内部通过Platform.runLater 推送runnable,但一直等到runnable 的执行(带有一些倒计时锁存机制)。

现在的问题是:当我的 start() 方法中发生异常时,我的应用程序就会卡住。因为它试图等到执行显示的对话框,但 FXAT 从不执行此操作。我猜这是因为当 start() 方法因异常而失败时,FXAT 就死了?我不确定这是否是 start() 方法的特殊情况,或者当异常被抛出并且未在 FXAT 执行的代码中捕获时,这是否会在任何情况下发生。

据我所知,在 Swing 中,EDT 是一个由多个线程组成的复杂架构。当 EDT 上的某些执行失败时,并不是整个 Swing 都崩溃了。但这似乎是发生了什么?

那么我可以在这里做什么?如何向用户显示应用程序无法启动?

【问题讨论】:

  • “我不能使用Platform.runLater(),因为这样我的应用程序会在对话框显示之前退出”。打电话Platform.setImplicitExit(false); 能解决这个问题吗?
  • 不,当然不是。应用程序退出的原因是我的异常处理程序中的 System.exit() 调用。
  • 等等,我是个白痴。程序退出太早忘记这一点。因为我也可以在 Runnable 中为 FX 线程移动 System.exit 调用。但是,当 start 方法抛出异常时,我仍然无法在 FX 线程上运行任何东西。
  • 我假设 runAndWait() 被阻塞直到可运行对象完成,这将阻止 System.exit(...) 被调用。您能否创建一个minimal reproducible example 来说明外汇平台因start() 方法中的异常而关闭?
  • 刚刚试验了一下jvisualvm,好像在start方法出现异常时FX Application Thread就退出了。我看不出有什么办法……

标签: javafx uncaughtexceptionhandler


【解决方案1】:

嗯……

我有一个解决方案,但我不特别推荐它。默认情况下Application.launch()会捕获start方法抛出的异常,退出FX平台,然后重新抛出异常。由于在执行默认的未捕获异常处理程序时 FX 应用程序线程已关闭,因此等待 FX 应用程序线程上发生的事情只会无限期地阻塞。

FX 应用程序在 Web 启动中运行时例外。启动器对此进行检查的方式是检查安全管理器是否存在。所以一个(非常非常难看的)解决方法是安装一个安全管理器,这样它看起来就像你在 web 启动模式下运行一样。此行将安装一个许可安全管理器:

System.setSecurityManager(new SecurityManager(){
    @Override
    public void checkPermission(Permission perm) {}
});

这是一个 SSCCE:

import java.lang.Thread.UncaughtExceptionHandler;
import java.security.Permission;
import java.util.concurrent.FutureTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;

public class ShowDialogOnException  {

    public static final UncaughtExceptionHandler ALERT_EXCEPTION_HANDLER = (thread, cause) -> {
        try {
            cause.printStackTrace();
            final Runnable showDialog = () -> {
               Alert alert = new Alert(AlertType.ERROR);
               alert.setContentText("An unknown error occurred");
               alert.showAndWait();
            };
            if (Platform.isFxApplicationThread()) {
               showDialog.run();
            } else {
               FutureTask<Void> showDialogTask = new FutureTask<Void>(showDialog, null);
               Platform.runLater(showDialogTask);
               showDialogTask.get();
            }
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            System.exit(-1);
        }
    };



    public static void main(String[] args) {
        System.setSecurityManager(new SecurityManager(){
            @Override
            public void checkPermission(Permission perm) {}
        });
        Thread.setDefaultUncaughtExceptionHandler(ALERT_EXCEPTION_HANDLER);
        Application.launch(App.class, args);
    }
}

还有一个测试应用:

import javafx.application.Application;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        throw new Exception("An exception");
    }

    @Override
    public void stop() {
        System.out.println("Stop");
    }

}

正如我所说,这确实是一个大技巧,除非您别无选择,否则我不建议您这样做。

【讨论】:

  • 感谢您的回答!但是,我同意您的观点,这是一个非常讨厌的解决方法。我想对我来说更好的解决方案是确保我的 start 方法中没有不稳定的代码,并将那里的逻辑限制为基础,然后在显示阶段之前在新线程中启动其他内容。
  • edit:或者实际上,更好的是,我只是将 start 方法中的所有代码放在 try-catch 块中,然后显示错误对话框并直接退出应用程序。唯一剩下的问题是这种 FX 关闭是否也发生在其他地方,而不仅仅是启动方法,例如当某些事件处理因异常而失败时?!但我会测试它,我想那里不会一样。
  • 是的,我的意思是作为评论或答案添加,这里与 Swing 的根本区别在于 start() 方法实际上是工具包启动的一个组成部分 - 所以在start 方法中的感知失败对整个 UI 来说是致命的。出于多种原因,将start() 中的代码最小化并将其他所有内容排除在外是一种很好的做法。一旦启动方法完成,就没有问题了,例如,如果在事件处理程序中发生未捕获的异常等,它可以正常工作。
  • 请参阅stackoverflow.com/questions/32464698/…,了解为什么建议最小化start() 中的代码是可取的,而stackoverflow.com/questions/32739199/javafx-software-design/… 则是一个非常极端的情况...
  • 感谢詹姆斯的所有帮助!我认为此案已结案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
相关资源
最近更新 更多