JavaFX 时钟小部件随机抛出异常
JavaFX Clock widget throws Exceptions randomly
我写了一个小的时钟小部件,扩展了 JavaFX 的 Text
class。要更新时间,我使用 Task
基本上将文本设置为当前系统时间。当我在 Eclipse 中 运行 这个应用程序时,它有时会在我调用 stage.show()
.
的行中抛出一个 NullpointerException
这是我的小部件源代码的样子:
package clock;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javafx.concurrent.Task;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class CurrentClockText extends Text {
private final DateTimeFormatter formatter;
public static final int HOUR = 1;
public static final int HOUR_MINUTE = 2;
public static final int HOUR_MINUTE_SECOND = 3;
public static final int HOUR_MINUTE_SECOND_MILLISECOND = 4;
private final long updateInterval;
private final Task<Void> updater = new Task<Void>() {
@Override
protected Void call() throws Exception {
while (true) {
LocalDateTime now = LocalDateTime.now();
String nowTextual = formatter.format(now);
setText(nowTextual);
try {
Thread.sleep(updateInterval);
} catch (InterruptedException ignore) {
}
}
}
};
public CurrentClockText() {
this(HOUR_MINUTE);
}
public CurrentClockText(final int detailLevel) {
String timeFormat = "";
switch (detailLevel) {
case HOUR:
timeFormat = "HH";
updateInterval = 60000;
break;
case HOUR_MINUTE:
timeFormat = "HH:mm";
updateInterval = 15000;
break;
case HOUR_MINUTE_SECOND:
timeFormat = "HH:mm:ss";
updateInterval = 500;
break;
case HOUR_MINUTE_SECOND_MILLISECOND:
updateInterval = 1;
timeFormat = "HH:mm:ss.S";
break;
default:
throw new IllegalArgumentException(
"Unknown detail level for Clock: " + detailLevel);
}
setFont(new Font("Verdana", 28));
formatter = DateTimeFormatter.ofPattern(timeFormat);
Thread updaterThread = new Thread(updater, "CurrentClockText.updaterThread");
updaterThread.setDaemon(true);
updaterThread.start();
}
}
这是主要的class:
package clock;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Boot extends Application {
@Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
CurrentClockText clock = new CurrentClockText(4);
pane.setCenter(clock);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Boot.launch(args);
}
}
这是堆栈跟踪:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication2(Unknown Source)
at com.sun.javafx.application.LauncherImpl$$Lambda/14845382.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NullPointerException
at com.sun.javafx.text.PrismTextLayout.addTextRun(Unknown Source)
at com.sun.javafx.text.GlyphLayout.addTextRun(Unknown Source)
at com.sun.javafx.text.GlyphLayout.breakRuns(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.buildRuns(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.layout(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.ensureLayout(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.getBounds(Unknown Source)
at javafx.scene.text.Text.getLogicalBounds(Unknown Source)
at javafx.scene.text.Text.impl_computeLayoutBounds(Unknown Source)
at javafx.scene.Node.computeBounds(Unknown Source)
at javafx.scene.Node$LazyBoundsProperty.get(Unknown Source)
at javafx.scene.Node$LazyBoundsProperty.get(Unknown Source)
at javafx.scene.Node.getLayoutBounds(Unknown Source)
at javafx.scene.Node.prefWidth(Unknown Source)
at javafx.scene.Node.minWidth(Unknown Source)
at javafx.scene.layout.Region.computeChildPrefAreaWidth(Unknown Source)
at javafx.scene.layout.BorderPane.getAreaWidth(Unknown Source)
at javafx.scene.layout.BorderPane.computePrefWidth(Unknown Source)
at javafx.scene.Parent.prefWidth(Unknown Source)
at javafx.scene.layout.Region.prefWidth(Unknown Source)
at javafx.scene.Scene.getPreferredWidth(Unknown Source)
at javafx.scene.Scene.resizeRootToPreferredSize(Unknown Source)
at javafx.scene.Scene.preferredSize(Unknown Source)
at javafx.scene.Scene.impl_preferredSize(Unknown Source)
at javafx.stage.Window.invalidated(Unknown Source)
at javafx.beans.property.BooleanPropertyBase.markInvalid(Unknown Source)
at javafx.beans.property.BooleanPropertyBase.set(Unknown Source)
at javafx.stage.Window.setShowing(Unknown Source)
at javafx.stage.Window.show(Unknown Source)
at javafx.stage.Stage.show(Unknown Source)
at clock.Boot.start(Boot.java:19)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication19(Unknown Source)
at com.sun.javafx.application.LauncherImpl$$Lambda/19600960.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait2(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda/18503843.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null0(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda/27167109.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater1(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda/2180324.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null5(Unknown Source)
at com.sun.glass.ui.win.WinApplication$$Lambda/3326003.run(Unknown Source)
... 1 more
Exception running application clock.Boot
正如我所说,此异常仅发生在某些 运行 中,并非总是如此。它主要发生在我更改 detailLevel = 4
的格式模式时,但在再次将其更改回来后仍然保留另一个 运行 。我猜这实际上可能与 Eclipse 有关,但我也无法调试代码,因为在 stage.show
调用中抛出了异常,我完全不明白。这个随机异常的原因是什么,我该如何解决?
在 JavaFX 中,与大多数其他 GUI 工具包一样,有一个特定线程处理所有 UI 相关操作。应用程序不得在此线程之外更新 UI。如果 UI 需要从另一个线程更新,通常有可用的 API 确保代码在 UI 线程的上下文中执行。如果是 JavaFX,请参阅 Concurrency in JavaFX Tutorial 了解更多信息。
对于您的情况,最简单的解决方案是确保您的 setText()
调用在 JavaFX 应用程序线程上执行,而不是在与您的 Task
关联的线程上执行:
...
Platform.runLater(() -> setText(nowTextual));
...
JavaFX 中还有其他 API 可用于执行动画,可用于在特定时间间隔调用处理程序方法 - 这将从您的循环中删除 Thread.sleep()
调用。有关详细信息,请参阅 Timeline。
我写了一个小的时钟小部件,扩展了 JavaFX 的 Text
class。要更新时间,我使用 Task
基本上将文本设置为当前系统时间。当我在 Eclipse 中 运行 这个应用程序时,它有时会在我调用 stage.show()
.
NullpointerException
这是我的小部件源代码的样子:
package clock;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import javafx.concurrent.Task;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class CurrentClockText extends Text {
private final DateTimeFormatter formatter;
public static final int HOUR = 1;
public static final int HOUR_MINUTE = 2;
public static final int HOUR_MINUTE_SECOND = 3;
public static final int HOUR_MINUTE_SECOND_MILLISECOND = 4;
private final long updateInterval;
private final Task<Void> updater = new Task<Void>() {
@Override
protected Void call() throws Exception {
while (true) {
LocalDateTime now = LocalDateTime.now();
String nowTextual = formatter.format(now);
setText(nowTextual);
try {
Thread.sleep(updateInterval);
} catch (InterruptedException ignore) {
}
}
}
};
public CurrentClockText() {
this(HOUR_MINUTE);
}
public CurrentClockText(final int detailLevel) {
String timeFormat = "";
switch (detailLevel) {
case HOUR:
timeFormat = "HH";
updateInterval = 60000;
break;
case HOUR_MINUTE:
timeFormat = "HH:mm";
updateInterval = 15000;
break;
case HOUR_MINUTE_SECOND:
timeFormat = "HH:mm:ss";
updateInterval = 500;
break;
case HOUR_MINUTE_SECOND_MILLISECOND:
updateInterval = 1;
timeFormat = "HH:mm:ss.S";
break;
default:
throw new IllegalArgumentException(
"Unknown detail level for Clock: " + detailLevel);
}
setFont(new Font("Verdana", 28));
formatter = DateTimeFormatter.ofPattern(timeFormat);
Thread updaterThread = new Thread(updater, "CurrentClockText.updaterThread");
updaterThread.setDaemon(true);
updaterThread.start();
}
}
这是主要的class:
package clock;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Boot extends Application {
@Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
CurrentClockText clock = new CurrentClockText(4);
pane.setCenter(clock);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Boot.launch(args);
}
}
这是堆栈跟踪:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source)
at com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication2(Unknown Source)
at com.sun.javafx.application.LauncherImpl$$Lambda/14845382.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NullPointerException
at com.sun.javafx.text.PrismTextLayout.addTextRun(Unknown Source)
at com.sun.javafx.text.GlyphLayout.addTextRun(Unknown Source)
at com.sun.javafx.text.GlyphLayout.breakRuns(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.buildRuns(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.layout(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.ensureLayout(Unknown Source)
at com.sun.javafx.text.PrismTextLayout.getBounds(Unknown Source)
at javafx.scene.text.Text.getLogicalBounds(Unknown Source)
at javafx.scene.text.Text.impl_computeLayoutBounds(Unknown Source)
at javafx.scene.Node.computeBounds(Unknown Source)
at javafx.scene.Node$LazyBoundsProperty.get(Unknown Source)
at javafx.scene.Node$LazyBoundsProperty.get(Unknown Source)
at javafx.scene.Node.getLayoutBounds(Unknown Source)
at javafx.scene.Node.prefWidth(Unknown Source)
at javafx.scene.Node.minWidth(Unknown Source)
at javafx.scene.layout.Region.computeChildPrefAreaWidth(Unknown Source)
at javafx.scene.layout.BorderPane.getAreaWidth(Unknown Source)
at javafx.scene.layout.BorderPane.computePrefWidth(Unknown Source)
at javafx.scene.Parent.prefWidth(Unknown Source)
at javafx.scene.layout.Region.prefWidth(Unknown Source)
at javafx.scene.Scene.getPreferredWidth(Unknown Source)
at javafx.scene.Scene.resizeRootToPreferredSize(Unknown Source)
at javafx.scene.Scene.preferredSize(Unknown Source)
at javafx.scene.Scene.impl_preferredSize(Unknown Source)
at javafx.stage.Window.invalidated(Unknown Source)
at javafx.beans.property.BooleanPropertyBase.markInvalid(Unknown Source)
at javafx.beans.property.BooleanPropertyBase.set(Unknown Source)
at javafx.stage.Window.setShowing(Unknown Source)
at javafx.stage.Window.show(Unknown Source)
at javafx.stage.Stage.show(Unknown Source)
at clock.Boot.start(Boot.java:19)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication19(Unknown Source)
at com.sun.javafx.application.LauncherImpl$$Lambda/19600960.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait2(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda/18503843.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null0(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda/27167109.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater1(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda/2180324.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null5(Unknown Source)
at com.sun.glass.ui.win.WinApplication$$Lambda/3326003.run(Unknown Source)
... 1 more
Exception running application clock.Boot
正如我所说,此异常仅发生在某些 运行 中,并非总是如此。它主要发生在我更改 detailLevel = 4
的格式模式时,但在再次将其更改回来后仍然保留另一个 运行 。我猜这实际上可能与 Eclipse 有关,但我也无法调试代码,因为在 stage.show
调用中抛出了异常,我完全不明白。这个随机异常的原因是什么,我该如何解决?
在 JavaFX 中,与大多数其他 GUI 工具包一样,有一个特定线程处理所有 UI 相关操作。应用程序不得在此线程之外更新 UI。如果 UI 需要从另一个线程更新,通常有可用的 API 确保代码在 UI 线程的上下文中执行。如果是 JavaFX,请参阅 Concurrency in JavaFX Tutorial 了解更多信息。
对于您的情况,最简单的解决方案是确保您的 setText()
调用在 JavaFX 应用程序线程上执行,而不是在与您的 Task
关联的线程上执行:
...
Platform.runLater(() -> setText(nowTextual));
...
JavaFX 中还有其他 API 可用于执行动画,可用于在特定时间间隔调用处理程序方法 - 这将从您的循环中删除 Thread.sleep()
调用。有关详细信息,请参阅 Timeline。