如何正确清理 popover 中的 ownerWindow 侦听器? /JavaFX

How to properly clean ownerWindow listener in popover ? /JavaFX

我使用 JDK8 创建了一个 JavaFX 应用程序,其中包含一个 window 和多个对象。 我现在正在尝试为 GarbageCollector 提供无用的已用对象。(使用 JVisualVM 进行测试)。

但我遇到了一个问题:
清除 window 元素上包含处理程序和侦听器的 Popover。

弹窗原代码:

public class CustomPopOver extends PopOver {

    /**
     * Constructor with the Content of the PopOver.
     * @param content the Node.
     */
    public CustomPopOver (Node content) {
        super(content);
        addHandler();
    }

    /**
     * Empty Constructor.
     */
    public CustomPopOver () {
        super();
        addHandler();
    }

    private void addHandler() {
        this.ownerWindowProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
                newValue.setOnCloseRequest(event -> {
                    if (this.isShowing()) {
                        this.hide(Duration.millis(0));
                    }
                    if (preExistingHandler != null) {
                        preExistingHandler.handle(event);
                    }
                });
            }
        });
    }
}

我尝试了很多方法来解决这个问题,但它无法正常工作:

public class CustomPopOver extends PopOver implements DisposableBean {

    private MyListener listener = new MyListener();

    public CustomPopOver (Node content) {
        super(content);
        addHandler();
    }

    /**
     * Empty Constructor.
     */
    public CustomPopOver () {
        super();
        addHandler();
    }

    private void addHandler() {
        this.ownerWindowProperty().addListener(listener);
    }

    @Override
    public void destroy() {
        if (this.getOwnerWindow() != null){
            this.getOwnerWindow()
                    .removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, listener.windowCloseEventHandler);
            this.getOwnerWindow()
                    .removeEventHandler(WindowEvent.WINDOW_HIDING, listener.windowHidingEventHandler);
        }
        this.ownerWindowProperty().removeListener(listener);
        listener = null;
    }

    /**
     * ChangeListener that removes itself when needed.
     */
    private class MyListener implements ChangeListener<Window> {

        EventHandler<WindowEvent> windowCloseEventHandler;
        EventHandler<WindowEvent> windowHidingEventHandler;
        @Override
        public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
            if (oldValue != null) {
                oldValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, windowCloseEventHandler);
                oldValue.removeEventHandler(WindowEvent.WINDOW_HIDING, windowHidingEventHandler);
            }
            if (newValue != null) {
                EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
                windowCloseEventHandler = new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        if (isShowing()) {
                            hide(Duration.millis(0));
                            ownerWindowProperty().removeListener(MyListener.this);
                        }
                        if (preExistingHandler != null) {
                            preExistingHandler.handle(event);
                        }
                        newValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, this);
                    }
                };
                newValue.setOnCloseRequest(windowCloseEventHandler);
                windowHidingEventHandler = new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        ownerWindowProperty().removeListener(MyListener.this);
                        newValue.removeEventHandler(WindowEvent.WINDOW_HIDING, this);
                    }
                };
                newValue.setOnHiding(windowHidingEventHandler);
            }
        }
    }
}

然后我们调用 destroy 方法从 jvm 缓存中清除 popover。

要测试的代码 class CustomPopOver:

public class PopOverViewer extends Application {

    private BorderPane pane;

    public PopOverViewer() {
        pane = new BorderPane();
        pane.setCenter(button());

    }

    private Node button() {
        HBox hBox = new HBox();
        List<CustomPopOver > lists = new ArrayList<>();
        Button show = new Button("click");
        show.setOnAction(event -> {
            CustomPopOver popOver = new CustomPopOver ();
            lists.add(popOver);
            popOver.show(show);
        });
        Button clean = new Button("clean");
        clean.setOnAction(event -> {
            lists.forEach(CustomPopOver::destroy);
            lists.clear();
        });
        hBox.getChildren().addAll(show, clean);
        return hBox;
    }

    @Override
    public void start(Stage primaryStage) {
        PopOverViewer app = new PopOverViewer();
        primaryStage.setScene(new Scene(app.getPane()));
        primaryStage.show();
    }

    private Parent getPane() {
        return pane;
    }

}

我希望 class CustomPopover 从 GC 中清除。

感谢@fabian,将 WeakEventHandler 放在强引用监听器内部的处理程序上,有助于清理它。

有效代码:

public class CustomPopOver extends PopOver implements DisposableBean {

    private MyListener listener = new MyListener();

    /**
     * Constructor with the Content of the PopOver.
     * @param content the Node.
     */
    public CustomPopOver(Node content) {
        super(content);
        addHandler();
    }

    /**
     * Empty Constructor.
     */
    public CustomPopOver() {
        super();
        addHandler();
    }

    private void addHandler() {
        this.ownerWindowProperty().addListener(listener);
    }

    @Override
    public void destroy() {
        this.ownerWindowProperty().removeListener(listener);
        listener = null;
    }

    /**
     * ChangeListener that removes itself when needed.
     */
    private class MyListener implements ChangeListener<Window> {

        @Override
        public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
            if (newValue != null) {
                EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
                EventHandler<WindowEvent> windowCloseEventHandler = new WeakEventHandler<>(new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        if (isShowing()) {
                            hide(Duration.millis(0));
                            ownerWindowProperty().removeListener(CustomPopOver.MyListener.this);
                        }
                        if (preExistingHandler != null) {
                            preExistingHandler.handle(event);
                        }
                        newValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, this);
                    }
                });
                newValue.setOnCloseRequest(windowCloseEventHandler);
                EventHandler<WindowEvent> windowHidingEventHandler = new WeakEventHandler<>(new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        ownerWindowProperty().removeListener(CustomPopOver.MyListener.this);
                        newValue.removeEventHandler(WindowEvent.WINDOW_HIDING, this);
                    }
                });
                newValue.setOnHiding(windowHidingEventHandler);
            }
        }
    }
}