I'm developing a JavaFX application that needs to run in the background and send notifications to the user. To achieve this, I'm using FXTrayIcon for system tray integration and handling the application lifecycle to ensure it continues running in the background even when the main window is closed. However, I've encountered a couple of issues specific to macOS:
Persistent Dock Icon After Closure: Even after closing the application window, the application icon remains in the Dock. While the background process continues as expected (which is intended), I cannot quit the application from the Dock; it requires a force quit.
Reopening Issues: If the application is closed to the Dock (icon still visible) and I try to reopen it by clicking the Dock icon, no window appears, making it seem like the application is unresponsive.
Here's a simplified version of my code to illustrate how I'm setting up the application:
HelloApplication.java: (main)
@Override
public void start(Stage stage) throws Exception {
this.primaryStage = stage;
setupPrimaryStage();
FXTrayIcon trayIcon = new FXTrayIcon(stage, getClass().getResource("/path/to/icon.png"));
trayIcon.show();
Platform.setImplicitExit(false);
javax.swing.SwingUtilities.invokeLater(this::addAppToTray);
}
private void addAppToTray() {
if (!Platform.isFxApplicationThread()) {
Platform.runLater(this::addAppToTray);
return;
}
if (primaryStage == null) {
return;
}
if (primaryStage.isShowing()) {
return;
}
primaryStage.show();
}
The FXTrayIcon library isn't needed for this, and it's not what it really does other than as a side effect of managing a JavaFX application from a tray icon.
This is what FXTrayIcon does, but nothing else in your question suggests you need system tray integration.
This is done from the standard JavaFX API, mostly by overriding the
init(),start(), andstop()methods in theApplicationclass and by calling some utility methods inPlatform(such assetImplicitExit(...)).This is the expected behavior, since your application is still running (you specifically say
The dock item represents a running application. Closing all the windows opened by an application doesn't necessarily mean that an application stops running. On my Mac, I have (for example) Microsoft Word installed. If I start Word and open a couple of windows, then close all the windows associated with the application, the icon remains in the dock and I can right click on it. I can still switch to the application (e.g. by using Command-Tab) and can access the menu, and open new windows. So again, because your application (the JVM) is still running, this is entirely expected.
The default behavior of JavaFX is for the JavaFX platform to exit when all windows are closed. This means the JavaFX Application Thread (and some other threads) will terminate and you can no longer display any JavaFX UI. This behavior can be turned off (as you do, and in fact as is also done for you by the FXTrayIcon library) by calling
Platform.setImplicitExit(false), in which case you must callPlatform.exit()to exit the JavaFX platform.When the JavaFX platform exits (either implicitly, or by calling
Platform.exit), if no non-daemon threads are running, then the JVM will also exit. This is how the dock icon is usually removed from the dock when you close a JavaFX window (or all windows, if multiple are open).The dock icon fails to be removed for two reasons in your example: first because you call
Platform.setImplicitExit(false), so the JavaFX Platform remains running, but also because the FXTrayIcon library relies on the AWT toolkit and starts the AWT event dispatch thread to manage the AWT tray icon behind the scenes. So even if you callPlatform.exit(), the JVM will not exit because the AWT platform is still running.I am not really convinced you need FXTrayIcon: it has basically nothing to do with the functionality you say you want. However, this is a quick overview of how it works.
AWT provides tray icon integration, but JavaFX does not. To use AWT tray icons in a JavaFX application would require managing multiple threads (the FX Application Thread and the AWT event dispatch thread), which is complex at best, as well as working with, for example, AWT menu items instead of JavaFX menu items. FXTrayIcon thus provides a JavaFX programmer's interface to the AWT tray icon, allowing the JavaFX programmer to work in a single thread and use familiar API such as JavaFX MenuItems, etc.
The typical use case for this is to place an icon in the system tray (which, of course, is completely different from the dock on Mac OSX). The user can pull up a menu from the tray icon, which typically does things like open or close the application window. Since a major use case involves having a non-JavaFX mechanism to manage the windows, FXTrayIcon turns off implicit exit for you, as long as one or more tray icons are displayed. There is also
showMessage(...)type functionality, which shows notifications "close to" the tray icon. See the Javadocs for more details.So the threading in the code in your post is completely unnecessary, since FXTrayIcon manages this for you, and completely wrong anyway. You use
SwingUtilities.invokeLater(...)to calladdAppToTray()on the AWT event dispatch thread. The first clause inaddAppToTray()checks to see if you are on the JavaFX Application Thread: you are not, since you explicitly invoked the method on the AWT Event Dispatch Thread. So it then usesPlatform.runLater(...)so send another invocation ofaddAppToTray()to the FX Application Thread.When the FX Application Thread receives this second invocation, it first checks if
primaryStageis null: it's not, because you necessarily initialized it earlier instart(), and then checks if the stage is not showing, and callsshow()if it is not showing. Note this last check is redundant:show()is a no-op if the stage is already showing. Sincestart()is invoked on the FX Application Thread (read the docs), the entire method can be reduced to a single line (primaryStage.show()) if you omit the pointlessSwingUtilities.invokeLater(...)call.Also note that
Platform.setImplicitExit(false)is redundant, because FXTrayIcon is going to do that for you.To exit the application entirely, you need to arrange for
Platform.exit()to be called, and you need to remove any tray icons. If you hide all the tray icons, FXTrayIcon will restore the implicit exit status to its default, so closing all JavaFX windows after that will exit the platform.Here is an example of using FXTrayIcon and exiting the entire application either via a button or via a menu item in the tray icon.
Closing the window will not exit the application, but simply close the window. Using the Exit button, or the "Exit Application" menu item from the tray icon will exit the application completely. If you close the window, you can reopen it from the tray icon.
However, nothing in your question indicates you need any of this functionality. If all you want is a background thread running, which keeps running when all windows are closed, just call
Platform.setImplicitExit(false);. Then just show and hide windows as needed, ensuring you do that on the FX Application Thread. A nice example of doing this is shown here.