IntelliJ 平台中的 Disposer
本文是一个案例研究,旨在简化 IntelliJ 平台,并介绍 IntelliJ 平台中的 Disposer。
事实上,SDK 文档已经对这一点进行了更详细的描述:Disposer 和 Disposable,在开始之前,最好先看一下这里。
我在这里模拟了一个场景。有一个 ToolWindow 布局如下,这里我们使用 UI Inspector 来分析布局:
一个带有 BorderLayout 的 rootPanel,顶部(North)是一个 Toolbar,中心(Center)是一个 JPanel。点击 ➕ 将添加一个随机代码片段到 Center 的 JPanel。
场景如上所示,是一个非常简单的 ToolWindow,功能也非常简单。
这是代码片段的关键部分:
import com.intellij.openapi.project.Project;import com.intellij.openapi.wm.ToolWindow;import com.intellij.openapi.wm.ToolWindowFactory;import com.intellij.ui.content.Content;import com.intellij.ui.content.ContentFactory;import com.obiscr.template.dispose.MainPanel;import org.jetbrains.annotations.NotNull;
public class MyToolWindowFactory implements ToolWindowFactory { @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { ContentFactory contentFactory = ContentFactory.getInstance(); MainPanel panel = new MainPanel(project); Content content = contentFactory.createContent(panel.getComponent(), "Dispose", false); toolWindow.getContentManager().addContent(content); }}
and
import com.intellij.icons.AllIcons;import com.intellij.openapi.actionSystem.AnAction;import com.intellij.openapi.actionSystem.AnActionEvent;import com.intellij.openapi.actionSystem.DefaultActionGroup;import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;import com.intellij.openapi.editor.Editor;import com.intellij.openapi.editor.EditorFactory;import com.intellij.openapi.editor.EditorSettings;import com.intellij.openapi.editor.colors.EditorColorsManager;import com.intellij.openapi.editor.colors.EditorColorsScheme;import com.intellij.openapi.editor.ex.EditorEx;import com.intellij.openapi.editor.highlighter.EditorHighlighter;import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;import com.intellij.openapi.project.Project;import com.intellij.testFramework.LightVirtualFile;import com.intellij.ui.components.panels.VerticalLayout;import com.intellij.util.ui.JBUI;import com.intellij.util.ui.UIUtil;import org.jetbrains.annotations.NotNull;
import javax.swing.*;import java.awt.*;import java.io.File;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.UUID;
public class MainPanel {
private final JPanel rootPanel; private final JPanel centerPanel; private final Project myProject;
public MainPanel(Project project) { myProject = project; rootPanel = new JPanel(new BorderLayout()); centerPanel = new JPanel(new VerticalLayout(JBUI.scale(10))); centerPanel.setBorder(JBUI.Borders.empty(10));
DefaultActionGroup actionGroup = new DefaultActionGroup(); AnAction addAction = new AnAction(() -> "Add Item", AllIcons.General.Add) { @Override public void actionPerformed(@NotNull AnActionEvent e) { String text = "// Code snippet at " + LocalDateTime.now(). format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "\n"; text += HelloWorld.getRandomHelloWorldCode(); Editor editor = createEditor(myProject, text); centerPanel.add(editor.getComponent()); centerPanel.revalidate(); centerPanel.repaint(); } }; actionGroup.add(addAction); ActionToolbarImpl actionToolbar = new ActionToolbarImpl("MyPanelToolbar", actionGroup, true); actionToolbar.setTargetComponent(rootPanel); actionToolbar.setBorder(JBUI.Borders.customLine(UIUtil.getBoundsColor(), 1,0,1,0)); rootPanel.add(actionToolbar, BorderLayout.NORTH); rootPanel.add(centerPanel, BorderLayout.CENTER); }
public JPanel getComponent() { return rootPanel; }
private Editor createEditor(Project project, String text) { EditorFactory editorFactory = EditorFactory.getInstance(); EditorEx editor = (EditorEx) editorFactory.createViewer( editorFactory.createDocument(text), project); // Others settings // ... return editor; }}
这里我们只是创建了 Editor 对象,但当程序退出时,我们没有清理它占用的内存。
当关闭 IDE 时,会显示相关异常消息(当然,这些信息也会显示在 idea.log
中):
根据错误日志,我们可以知道这是因为创建的 Editor 对象在应用程序关闭时没有释放,存在内存溢出的风险。
好的,现在我们已经知道问题所在,接下来我们解决这个问题,解决方案也很简单,只需要关闭 IDE,释放这个对象即可。
首先,让 MainPanel 实现 Disposable 接口并重写 dispose()
方法。然后,在创建 editor
后,将 Disposer
注册到父 this
中,表示 当这个对象被释放时,Editor 将被销毁。
public class MainPanel implements Disposable { @Override public void dispose() { centerPanel.removeAll(); rootPanel.removeAll(); }
private Editor createEditor(Project project, String text) { EditorEx editor = ...; Disposer.register(this, () -> EditorFactory.getInstance().releaseEditor(editor)); return editor; }}
此外,由于在这种情况下组件都基于 Content,我们还将 MainPanel 注册为 Content 的 Disposer。
public class MyToolWindowFactory implements ToolWindowFactory {
@Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { MainPanel panel = new MainPanel(project); Content content = ...; content.setDisposer(panel); ... }}
ContentImpl.java 的源代码如下,你可以看到当 content
被销毁时,它会调用 Disposer.dispose(myDisposer);
来销毁设置的 myDisposer,也就是上面的 MainPanel。
@Override public void dispose() { if (myShouldDisposeContent && myComponent instanceof Disposable) { Disposer.dispose((Disposable)myComponent); }
if (myDisposer != null) { Disposer.dispose(myDisposer); myDisposer = null; }
myFocusRequest = null; clearUserData(); }
那么现在整个过程如下:
content.dispose() --(call)--> panel.dispose() --(call)--> EditorFactory.getInstance().releaseEditor(editor);
所以,当 content
被关闭时,Editor 对象也会被销毁。