使用JFXPanel Swing interop防止JavaFX线程死亡?
我将几个JFXPanels
嵌入到Swing应用程序中,当JFXPanel不再可见时,JavaFX线程就会死掉。 这是有问题的,因为在JavaFX线程死后创建另一个JFXPanel
将不会启动另一个JavaFX线程,因此JFXPanel
将为空。
据我所知,JFXPanel ctor通过调用以下命令启动JavaFX线程:
PlatformImpl.startup(new Runnable() { @Override public void run() { // No need to do anything here } });
稍后,一旦JFXPanel
具有父组件,就会调用其addNotify
方法,该方法调用registerFinishListener
,该方法使用PlatformImpl
注册PlatformImpl.FinishListener()
。 注册FinishListener
的行为可防止JavaFX线程在调用PlatformImpl.checkIdle()
时死亡。
当JFXPanel
不再可见时,其removeNotify
方法是调用deregisterFinishListener()
调用:
private synchronized void deregisterFinishListener() { if (instanceCount.decrementAndGet() > 0) { // Other JFXPanels still alive return; } PlatformImpl.removeListener(finishListener); finishListener = null; }
当instanceCount
为零时, FinishListener
被删除,导致PlatformImpl.tkExit
在以下代码中调用PlatformImpl.tkExit
,导致JavaFX线程死亡:
private static void notifyFinishListeners(boolean exitCalled) { // Notify listeners if any are registered, else exit directly if (listenersRegistered.get()) { for (FinishListener l : finishListeners) { if (exitCalled) { l.exitCalled(); } else { l.idle(implicitExit); } } } else if (implicitExit || platformExit.get()) { tkExit(); } }
我发现修复此问题的唯一方法是在Swing应用程序开始时调用Platform.setImplicitExit(false)
,以便JavaFX线程永远不会自动死亡。 此修复需要在应用程序退出时调用Platform.exit()
,否则JavaFX线程将阻止进程停止。
这似乎是JavaFX-Swing互操作中的一个错误,或者至少应该通过讨论Platform.setImplicitExit(false)
修改互操作文档来解决这个问题。
另一种解决方案是允许在创建另一个JFXPanel但是被PlatformImpl.startup(Runnable)
阻止时创建新的JavaFX线程:
if (initialized.getAndSet(true)) { // If we've already initialized, just put the runnable on the queue. runLater(r); return; }
我错过了什么吗?
这是一个非常古老的“bug”,随着Platform.setImplicitExit(false)
的引入而有所修复。 您可以在公开发行的JDK-8090517中阅读开发人员的评论。 正如您将看到它具有低优先级并且可能永远不会得到修复(至少不会很快)。
您可能想要尝试而不是使用Platform.setImplicitExit(false)
另一个解决方案是在当前Main类中扩展Application类,并使用主Stage显示应用程序的主窗口。 只要主要舞台保持打开,FX Thread将处于活动状态(并在关闭应用程序时正确处理)。
如果您不打算使用FX Stage作为主窗口(因为它需要使用SwingNode来实现您现有的function或将UI迁移到JavaFX),您可以随意假冒这样的:
@Override public void start(Stage primaryStage) throws Exception { YourAppMainWindow mainWindow = new YourAppMainWindow(); // Load your main window Swing Stuff (remember to use // SwingUtilities.invokeLater() to run inside the Event Dispatch Thread mainWindow.initSwingUI(); // Now that the Swing stuff is loaded open a "hidden" primary stage // that will keep the FX Thread alive primaryStage.setWidth(0); primaryStage.setHeight(0); primaryStage.setX(Double.MAX_VALUE); primaryStage.setY(Double.MAX_VALUE); primaryStage.initStyle(StageStyle.UTILITY); primaryStage.show(); }
请记住,伪造主要阶段(或将主窗口迁移到FX)将比简单地使用Platform.setImplicitExit(false)
和Platform.exit()
相应地以更多代码结束。
无论如何,希望这有帮助!