本文部分内容和灵感来自eclipse.org网站,特此声明。更多内容,请参考:
http://eclipse.org/articles/Article-SWT-Design-2/SWT-Design-2.html
由于SWT直接跟操作系统打交道,所以我们需要在处理系统的图形资源时格外小心,以免不必要的资源泄漏。所幸SWT提供了很好的资源管理机制,我们绝大多数情况下需要做的只是确保两条原则:
第一条原则 – 谁分配谁销毁
第二条原则 – 父控件销毁的同时销毁子控件
下面我们分别来看一看这两条在实际中是如何体现的。
先看第一条原则。乍一看这似乎是废话,但是在实际中往往并非那么简单。首先,构造方法不等于分配资源,实际上分配资源可以发生在一个类中的任何地方以及一个对象生命周期的任何时候,只要你的代码告诉操作系统这样做。你必须保证所有由你分配的资源当你不再使用时调用其dispose()方法;同时你也必须保证所有不是由你分配的资源不要随便调用其dispose()方法,否则很可能会影响到实际分配的那段相关代码的正常工作。好消息是,为了明确和简化这第一条原则所规定的分工,SWT在设计之初就确定下来,所有基于系统资源的SWT类都在其构造方法中完成所有所需的资源分配,在其他方法中则没有任何分配系统资源的动作,所以我们可以幸运的这样看待SWT的资源管理:如果你调用了某个SWT类的构造方法,那么就由你来调用其dispose()方法释放资源;如果你没有调用某个SWT类的构造方法,即便你使用了这个类的实例,也不应该由你来调用其dispose()方法。就是这么明确。
比方讲,你new了一个Font对象,那么当你不再需要它时,就应该调用dispose();如果你通过某个控件的getFont()方法取得一个Font对象并使用后,你不应该去销毁它,而应该交给那个具体的控件去处理。
对于第二条原则,SWT有一个很好的机制去支持它,那就是,所有的SWT控件,具体讲,Composite类及其子类的实例,都必须有一个父控件,这个父控件的引用在子控件的构造方法中被传入。需要注意的是,所有这些控件的不带参数的构造方法都只有默认访问级别,于是我们不能在自己的SWT程序中直接调用这样的默认构造方法而只能提供一个父控件的引用,而在Widget类(Composit的父类)的带参数构造方法中,它会调用如下方法:
void checkParent (Widget parent) { if (parent == null) error (SWT.ERROR_NULL_ARGUMENT); parent.checkWidget ();}
进而:
protected void checkWidget () { Display display = this.display; if (display == null) error (SWT.ERROR_WIDGET_DISPOSED); if (display.thread != Thread.currentThread ()) error (SWT.ERROR_THREAD_INVALID_ACCESS); if ((state & DISPOSED) != 0) error (SWT.ERROR_WIDGET_DISPOSED);}
这样的检查保证了任何控件在创建时都有父控件。当我们调用某个Composite的dispose()方法时,它会调用:
void releaseChildren () { Control [] children = _getChildren (); for (int i=0; i<children.length; i++) { Control child = children [i]; if (!child.isDisposed ()) child.releaseResources (); }}
其中的_getChildren()方法通过OS对象的方法遍历控件的子控件,然后汇总到一起分别调用releaseResources()方法释放控件和句柄。
回到上次的SimplestSWT的例子:
package sean.test.swt;import org.eclipse.swt.widgets.Display;import org.eclipse.swt.widgets.Shell;public class SimplestSWT { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.pack(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); }}
这当中,Display是一个顶层的设备,它继承自Device类,而Device类实现了Drawable接口。Shell的父类是Decoration,而Decoration继承自Canvas,Canvas继承自Composite,最终这条继承链一直连到Widget类。我们在创建Shell示例的时候,需要告诉构造方法它的父控件是什么,在这里就是display。于是当我们最后调用display.dispose()时,虽然我们没有明确写shell.dispose(),我们的Shell实例也随之销毁了。
这就是SWT的资源管理机制,稍有例外的是MenuItem的setMenu()方法和Control的setMenu()方法,它们通过显式调用setMenu的方式注册自己的父控件。