我有一个运行java http服务器的java应用程序。此 Java 应用程序应连续运行。我不想在程序第一次运行时打开javafx gui。
正如我所说,应用程序应该连续运行。用户应该能够通过单击系统托盘图标随时打开用户界面。 或者应该能够关闭界面中的十字按钮。
我使用Platform.setImplicitExit (false)
来不阻止 java 应用程序按下界面上的十字按钮。
如果用户想再次看到屏幕,我想通过按系统托盘重新渲染屏幕。
我想在不关闭 java 程序的情况下显示和隐藏用户界面。 什么是最佳实践 我在等你的帮助。
相关代码如下。
public class Gui extends Application {
@Override
public void start(Stage stage) throws Exception {
Platform.setImplicitExit(false);
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
new Gui().start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
Scene scene = new Scene(new StackPane());
LoginManager loginManager = new LoginManager(scene);
loginManager.showLoginScreen();
stage.setScene(scene);
stage.show();
// stage.setOnCloseRequest(e -> Platform.exit());
}
}
主类
public static void main(String[] args) throws IOException, Exception, FileNotFoundException {
ServerSocket ss = null;
try {
ss = new ServerSocket(9090);
if (ss != null) {
ss.close();
}
} catch (BindException e) {
System.out.println("Sikke Node Server is already running.");
System.exit(0);
}
launchh();
}
主类中的方法
private static void createAndShowGUI() {
if (SystemTray.isSupported()) {
final PopupMenu popup = new PopupMenu();
final TrayIcon trayIcon = new TrayIcon(createImage("/sikke24.gif", "Sikke Node "), "Sikke Node Server",
popup);
trayIcon.setImageAutoSize(true);
final SystemTray tray = SystemTray.getSystemTray();
final int port = Integer.parseInt(_System.getConfig("rpcport").get(0));
// Create a popup menu components
MenuItem aboutItem = new MenuItem("About");
Menu displayMenu = new Menu("Display");
MenuItem infoItem = new MenuItem("Info");
MenuItem noneItem = new MenuItem("None");
MenuItem exitItem = new MenuItem("Exit Sikke Node Server");
// Add components to popup menu
popup.add(aboutItem);
popup.addSeparator();
popup.add(displayMenu);
displayMenu.add(infoItem);
displayMenu.add(noneItem);
popup.add(exitItem);
trayIcon.setPopupMenu(popup);
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.out.println("Sikke Node Icon could not be added.");
return;
}
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/*
* JOptionPane.showMessageDialog(null,
* "Server started successfully. The server works on port number:" + port);
*/
Application.launch(Gui.class, "");
}
});
aboutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null,
"Server started successfully. The server works on port number:" + port);
}
});
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
MenuItem item = (MenuItem) e.getSource();
System.out.println(item.getLabel());
if ("Error".equals(item.getLabel())) {
trayIcon.displayMessage("Sikke Node Server", "This is an error message",
TrayIcon.MessageType.ERROR);
} else if ("Warning".equals(item.getLabel())) {
trayIcon.displayMessage("Sikke Node Server", "This is a warning message",
TrayIcon.MessageType.WARNING);
} else if ("Info".equals(item.getLabel())) {
// GUI runs
trayIcon.displayMessage("Sikke Node Server", "This is an info message",
TrayIcon.MessageType.INFO);
} else if ("None".equals(item.getLabel())) {
trayIcon.displayMessage("Sikke Node Server", "This is an ordinary message",
TrayIcon.MessageType.NONE);
}
}
};
trayIcon.displayMessage("Sikke Node Server", "Sikke Node Server started successfully on port : " + port,
TrayIcon.MessageType.INFO);
infoItem.addActionListener(listener);
noneItem.addActionListener(listener);
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tray.remove(trayIcon);
System.exit(0);
}
});
}
}
注意这里
Application.launch(Gui.class, "");
托盘图标操作侦听器已更新
trayIcon.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
if (Platform.isFxApplicationThread()) {
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
new Gui().start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
} else {
Application.launch(Gui.class, "");
}
}
}
});
一些观察
首先,在更新的侦听器中:
trayIcon.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
if (Platform.isFxApplicationThread()) {
Platform.runLater(new Runnable() {
@Override public void run() { /* OMITTED FOR BREVITY */ }
});
} else {
Application.launch(Gui.class, "");
}
}
}
});
您检查Platform.isFxApplicationThread
,如果为 true,则调用Platform.runLater
。对Platform.runLater
的调用调度要在JavaFX 应用程序线程上执行的操作;如果您已经在该线程上,则无需(通常)调用Platform.runLater
。当然,isFxApplicationThread
永远不会返回 true,因为SystemTray
是 AWT 的一部分,并且会在 AWT 相关线程上调用侦听器。这意味着将始终调用else
分支,这是一个问题,因为您不能在单个 JVM 实例中多次调用Application.launch
;这样做会导致抛出IllegalStateException
。
此外,在您的start
方法中:
@Override
public void start(Stage stage) throws Exception {
Platform.setImplicitExit(false);
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
new Gui().start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
/* SOME CODE OMITTED FOR BREVITY */
}
该Platform.runLater
调用应该会导致"循环"。当您调用start
时,您可以通过Platform.runLater
调用安排Runnable
稍后运行。在这个Runnable
里面,你称之为new Gui().start(new Stage())
.它的作用是再次调用start
(在Gui
的新实例上),它将再次调用Platform.runLater
,这将再次调用new Gui().start(new Stage())
,这将再次调用start
,哪个...你明白了。
请注意,Application.launch(Gui.class)
将创建Gui
的实例,并使用主Stage
调用start
。但如上所述,launch
只能调用一次。从概念上讲,Application
子类表示整个应用程序。理想情况下,该类应该只有一个实例。
使用SystemTray
的小示例
下面是一个使用SystemTray
打开 JavaFX 窗口的小示例。在用户单击(双击,至少在 Windows 上)托盘图标之前,不会显示该窗口。
import java.awt.AWTException;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.image.BufferedImage;
import java.util.function.Predicate;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
public class Main extends Application {
private Stage primaryStage;
private boolean iconAdded;
@Override
public void start(Stage primaryStage) throws AWTException {
if (SystemTray.isSupported()) {
installSystemTray();
Platform.setImplicitExit(false);
StackPane root = new StackPane(new Label("Hello, World!"));
primaryStage.setScene(new Scene(root, 500, 300));
primaryStage.setTitle("JavaFX Application");
primaryStage.setOnCloseRequest(this::promptUserForDesiredAction);
this.primaryStage = primaryStage;
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText(null);
alert.setContentText("SystemTray is not supported. Will exit application.");
alert.showAndWait();
Platform.exit();
}
}
@Override
public void stop() {
if (iconAdded) {
SystemTray tray = SystemTray.getSystemTray();
for (TrayIcon icon : tray.getTrayIcons()) {
tray.remove(icon);
}
}
}
private void promptUserForDesiredAction(WindowEvent event) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.initOwner((Window) event.getSource());
alert.setTitle("Choose Action");
alert.setHeaderText(null);
alert.setContentText("Would you like to exit or hide the application?");
// Use custom ButtonTypes to give more meaningful options
// than, for instance, OK and CANCEL
ButtonType exit = new ButtonType("Exit");
ButtonType hide = new ButtonType("Hide");
alert.getDialogPane().getButtonTypes().setAll(exit, hide);
alert.showAndWait().filter(Predicate.isEqual(exit)).ifPresent(unused -> Platform.exit());
}
private void installSystemTray() throws AWTException {
TrayIcon icon = new TrayIcon(createSystemTrayIconImage(), "Show JavaFX Application");
// On Windows 10, this listener is invoked on a double-click
icon.addActionListener(e -> Platform.runLater(() -> {
if (primaryStage.isShowing()) {
primaryStage.requestFocus();
} else {
primaryStage.show();
}
}));
SystemTray.getSystemTray().add(icon);
iconAdded = true;
}
// Creates a simple red circle as the TrayIcon image. This is here
// to avoid needing an image resource for the example.
private BufferedImage createSystemTrayIconImage() {
Circle circle = new Circle(6.0, Color.FIREBRICK);
Scene scene = new Scene(new Group(circle), Color.TRANSPARENT);
return SwingFXUtils.fromFXImage(circle.snapshot(null, null), null);
}
}
关于示例
在我的示例中,我保留了对Stage
的强烈引用,当调用添加到TrayIcon
的ActionListener
时,我显示了该。请注意,在ActionListener
中我如何使用Platform.runLater
.对于您添加到SystemTray
相关对象的每个侦听器(例如java.awt.MenuItem
),将任何将与 JavaFX 对象交互的代码包装在Platform.runLater
调用中。
现在,我的示例首先启动 JavaFX 运行时,然后添加TrayIcon
。JavaFX 运行时不仅会立即启动,而且我还预先创建了场景图并存储了对它的强引用。这可能是大量不必要的开销和内存消耗。由于您的应用程序是一个可以在没有 JavaFX 运行时的情况下运行的 HTTP 服务器,因此您可以进行一些优化。
关闭后,不要存储对
关闭Stage
的强引用,允许对其进行垃圾回收。可能的选项包括:Stage
时立即移除参照。这将要求您每次都重新创建场景图。
在关闭
Stage
一段时间后任意删除引用。这将通过某种计时器完成(例如PauseTransition
或Timeline
),当Stage
在经过的时间之前重新打开时重置。
当用户请求GUI时,您将在必要时(重新)创建场景图并使用模型(重新)初始化它。在处理场景图时,不要忘记任何必要的清理,例如删除观察模型的侦听器;任何位置的任何强引用都会将对象保留在内存中,从而导致内存泄漏。
懒洋洋地启动 JavaFX 运行时。
Application
子类中没有任何服务器初始化/运行逻辑。特别是,不要将main
方法放在该类中,因为它会间接启动 JavaFX 运行时。在第一个请求显示 GUI 时,使用
Application.launch
.注意:我认为对
launch
的调用必须放在单独的线程上。在 JavaFX 运行时退出之前,调用launch
的线程不会返回。由于 AWT 线程上调用了TrayIcon
侦听器,这将导致该线程被阻塞,这并不好。在后续请求中,只需显示窗口,必要时重新创建它。如何做到这一点取决于您的架构。一种选择是使您的
Application
类成为一种通过Application.launch
设置的懒惰单例;您将获得一个引用并调用一个方法来显示窗口(在 FX 线程上)。
这两个选项都将使 JavaFX 运行时在启动后保持活动状态,直到整个应用程序退出。从技术上讲,您可以独立退出 JavaFX 运行时,但如前所述,多次调用Application.launch
是一个错误;如果这样做,您将无法再次显示 GUI,直到重新启动整个应用程序。如果确实要允许应用程序的 JavaFX 端退出并重新启动,则可以使用单独的进程。但是,使用单独的过程可能并非易事。