Począwszy od oprogramowania JavaFX 8, Thread.setDefaultUncaughtExceptionHandler(...)
powinno działać: patrz: RT-15332.
Rzeczy są nieco skomplikowane, jeśli wystąpi nieprzechwycony wyjątek podczas wykonywania metody start(...)
. W zależności od sposobu uruchomienia aplikacji kod wywołujący start()
(na przykład implementacja Application.launch(...)
) może wychwycić wyjątek i obsłużyć go, co oczywiście uniemożliwiłoby wywołanie domyślnej procedury obsługi wyjątku.
W szczególności, w moim systemie (JDK 1.8.0_20 na Mac OS X 10.9.5), wydaje się, że jeśli moja aplikacja uruchamia się za pomocą metody, która wywołuje main(...)
Application.launch(...)
, każdy wyjątek rzucony w metodzie start()
zostanie złapany (i nie powtórnie rzucony).
Jeśli jednak usuniemy metodę main(...)
(patrz uwaga poniżej) i uruchomię aplikację bezpośrednio, każdy wyjątek zgłoszony w metodzie start()
zostanie ponownie zgłoszony, umożliwiając wywołanie domyślnej procedury obsługi wyjątku. Zauważ, że nie tylko się rozprzestrzenia. start()
jest wywoływany w wątku aplikacji FX, a wyjątek jest ponownie wysyłany z wątku głównego.W rzeczywistości, gdy to nastąpi, kod w domyślnej procedurze obsługi, która zakłada, że wątek aplikacji FX działa, nie działa: więc domyślam się, że kod uruchamiania w tym przypadku przechwytuje wyjątki w metodzie start()
, aw bloku catch
zamyka FX Application Thread
, a następnie ponownie wyrzuca wyjątek z wątku wywołującego.
Wszystko to jest ważne - jeśli chcesz, aby Twój domyślny program obsługi obsługiwał wyjątki w metodzie start()
, nie powinieneś wywoływać żadnego kodu interfejsu użytkownika, jeśli wyjątek nie zostanie zgłoszony w wątku aplikacji FX (nawet przez a Platform.runLater(...)
).
Uwaga: (dla tych, którzy mogą nie być tego świadomi). Od wersji Java 8 można bezpośrednio uruchomić podklasę Application
, nawet jeśli nie ma ona metody main(...)
, przekazując nazwę klasy jako argument do pliku wykonywalnego JVM w zwykły sposób (tj. java MyApp
). Robi to, czego oczekujesz: uruchamia zestaw narzędzi FX, uruchamia wątek aplikacji FX, tworzy instancję Application
i wywołuje init()
, a następnie wywołuje wątek aplikacji FX start()
. Interesująco (i być może niepoprawnie), metoda , która wywołuje Application.launch()
, zachowuje się nieco inaczej w odniesieniu do niezatłoczonych wyjątków w metodzie start(...)
.
Oto podstawowy przykład. Odkomentuj kod w Controller.initialize()
, aby wyświetlić wyjątek zgłoszony w metodzie start()
.
package application;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(Main::showError);
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static void showError(Thread t, Throwable e) {
System.err.println("***Default exception handler***");
if (Platform.isFxApplicationThread()) {
showErrorDialog(e);
} else {
System.err.println("An unexpected error occurred in "+t);
}
}
private static void showErrorDialog(Throwable e) {
StringWriter errorMsg = new StringWriter();
e.printStackTrace(new PrintWriter(errorMsg));
Stage dialog = new Stage();
dialog.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader = new FXMLLoader(Main.class.getResource("Error.fxml"));
try {
Parent root = loader.load();
((ErrorController)loader.getController()).setErrorText(errorMsg.toString());
dialog.setScene(new Scene(root, 250, 400));
dialog.show();
} catch (IOException exc) {
exc.printStackTrace();
}
}
// public static void main(String[] args) {
// launch(args);
// }
}
z Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.geometry.Insets?>
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller"
alignment="center" spacing="5">
<children>
<Button text="Do something safe" onAction="#safeHandler" />
<Button text="Do something risky" onAction="#riskyHandler" />
<Label fx:id="label" />
</children>
<padding>
<Insets top="10" left="10" right="10" bottom="10" />
</padding>
</HBox>
Controller.java:
package application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
private final IntegerProperty counter = new SimpleIntegerProperty(1);
@FXML
private Label label ;
public void initialize() throws Exception {
label.textProperty().bind(Bindings.format("Count: %s", counter));
// uncomment the next line to demo exceptions in the start() method:
// throw new Exception("Initializer exception");
}
@FXML
private void safeHandler() {
counter.set(counter.get()+1);
}
@FXML
private void riskyHandler() throws Exception {
if (Math.random() < 0.5) {
throw new RuntimeException("An unknown error occurred");
}
safeHandler();
}
}
Error.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.ErrorController">
<center>
<ScrollPane>
<content>
<Label fx:id="errorMessage" wrapText="true" />
</content>
</ScrollPane>
</center>
<bottom>
<HBox alignment="CENTER">
<Button text="OK" onAction="#close"/>
</HBox>
</bottom>
</BorderPane>
ErrorController.java:
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class ErrorController {
@FXML
private Label errorMessage ;
public void setErrorText(String text) {
errorMessage.setText(text);
}
@FXML
private void close() {
errorMessage.getScene().getWindow().hide();
}
}
Teraz wiem, co spowodowało wyjątek w moim UncaughtExceptionHandler: Próbowałem wyświetlić okno dialogowe za pośrednictwem JavaFX i najwyraźniej etap nie został zainicjowany, a więc otrzymałem wyjątek. Przepraszam, że dodałem to jako komentarz, ale nie ma innego sposobu niż komunikacja na SO ;-) – Hannes