防止JavaFX线程在JFXPanel Swing互操作时死亡?

7
我正在将几个JFXPanels嵌入到Swing应用程序中,当这些JFXPanels不再可见时,JavaFX线程会死亡。这是有问题的,因为在JavaFX线程死亡后创建另一个JFXPanel将不会启动另一个JavaFX线程,因此JFXPanel将是空白的。
据我所知,JFXPanel构造函数通过调用以下方法启动JavaFX线程:
PlatformImpl.startup(new Runnable() {
   @Override public void run() {
      // No need to do anything here
   }
});

稍后,一旦 JFXPanel 有了父组件,它的 addNotify 方法就会被调用,该方法调用 registerFinishListener 并向 PlatformImpl 注册一个 PlatformImpl.FinishListener()。注册 FinishListener 的行为在调用 PlatformImpl.checkIdle() 时防止 JavaFX 线程死亡。

当一个 JFXPanel 不再可见时,其 removeNotify 方法将被调用,并调用 deregisterFinishListener()

private synchronized void deregisterFinishListener() {
    if (instanceCount.decrementAndGet() > 0) {
        // Other JFXPanels still alive
        return;
    }
    PlatformImpl.removeListener(finishListener);
    finishListener = null;
}

instanceCount为零时,将删除FinishListener,这将导致在以下代码中PlatformImpl调用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时创建一个新的JavaFX线程,但是被PlatformImpl.startup(Runnable)阻止。
if (initialized.getAndSet(true)) {
   // If we've already initialized, just put the runnable on the queue.
   runLater(r);
   return;
}

我有所遗漏吗?


感谢您提供这段代码:'''Platform.setImplicitExit(false).''' 我的JavaFX线程出现了问题(我仍然不知道原因),但是您提供的代码解决了它。我的用例是,每当有人更改下拉列表中的内容时,我会删除旧的javafx面板并添加新的面板。 - joseph
1个回答

8

这是一个非常老的“漏洞”,在引入 Platform.setImplicitExit(false) 之后有所修复。您可以在开放问题 JDK-8090517 中阅读开发人员的评论。正如您所看到的,它的优先级很低,可能永远不会得到修复(至少不会很快)。

除了使用 Platform.setImplicitExit(false),您可能想尝试的另一个解决方案是在当前的 Main 类中扩展 Application 类,并使用主要的 Stage 显示应用程序的主窗口。只要主要的 Stage 保持打开状态,FX 线程就会保持活动状态(并在关闭应用程序时正确地释放)。

如果您不想将 FX Stage 作为主窗口使用(因为它需要为现有内容使用 SwingNode 或将 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()更多的代码。
无论如何,希望这可以帮到你!

感谢您提供JDK错误链接。很高兴知道它被认为是一个实际的问题,但没有修复计划。隐藏窗口的有趣替代解决方案。现在将坚持使用Platform.setImplicitExit(false)。 - jenglert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接