当需要在Widget被销毁(dispose)的时候执行一些比如释放资源的动作时,有两个途径来执行这些动作:
1. 重写dispose()方法,在dispose中释放资源的动作。
2. 添加DisposeListener,监听SWT.Dispose事件,在Listener中执行释放资源的动作。
应该用哪种方式呢?
答案是方法2,因为虽然都能执行dispose动作,但SWT隐式销毁对象的时候并不调用dispose()。
dispose()是提供给外部调用者显式销毁对象的方法,在应用程序正常关闭时逐层销毁组件对象时,是一种隐式销毁,并不调用dispose()方法。所以如果使用方法1,那么程序关闭的时候,并不会执行到你的代码。
使用DisposeListener临听SWT.Dispose事件,才能保证不论是显式销毁还是隐式销毁自己的代码都能被执行。
下面是测试代码:
MyComposite.java
package testwb;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.DisposeEvent;
public class MyComposite extends Composite {
/**
* Create the composite.
* @param parent
* @param style
*/
public MyComposite(Composite parent, int style) {
super(parent, style);
addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
System.out.println("DisposeListener 被调用");
}
});
}
@Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
@Override
public void dispose() {
System.out.println("dispose 被调用");
super.dispose();
}
}
TestDispose.java
package testwb;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.SWT;
public class TestDispose {
protected Shell shell;
/**
* Launch the application.
* @param args
*/
public static void main(String[] args) {
try {
TestDispose window = new TestDispose();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open() {
Display display = Display.getDefault();
createContents();
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Create contents of the window.
*/
protected void createContents() {
shell = new Shell();
shell.setSize(450, 300);
shell.setText("SWT Application");
MyComposite myComposite = new MyComposite(shell, SWT.NONE);
myComposite.setBounds(46, 20, 64, 64);
}
}
在eclipse中调试运行TestDispose.java,并给MyComposite.java的System.out.println("DisposeListener 被调用");
这一行加上断点。
关闭程序的时候,就会执行到断点行,下面是调用堆栈:
上图中红框标的部分可以看出,shell在执行关闭动作时会调用releaseChildren来销毁它的子组件,在销毁每个子widget的时候,调用的是widget的release()方法(默认访问属性,外部代码不可访问),release()中先调用sendEvent 发送SWT.Dispose事件给所有的Listener,再调用自己的releaseChildren()方法,一层层向下销毁(递归)所有widget。
所以可以发现这个销毁过程根本没有dispose()方法什么事儿。
Widget.release() 方法代码如下
void release (boolean destroy) {
if ((state & DISPOSE_SENT) == 0) {
state |= DISPOSE_SENT;
sendEvent (SWT.Dispose);
}
if ((state & DISPOSED) == 0) {
releaseChildren (destroy);
}
if ((state & RELEASED) == 0) {
state |= RELEASED;
if (destroy) {
releaseParent ();
releaseWidget ();
destroyWidget ();
} else {
releaseWidget ();
releaseHandle ();
}
}
}
当显式调用dispse()方法销毁对象的时候,dispose()还是调用release()方法来执行销毁动作。
Widget.dispose()方法的代码如下:
public void dispose () {
/*
* Note: It is valid to attempt to dispose a widget
* more than once. If this happens, fail silently.
*/
if (isDisposed ()) return;
if (!isValidThread ()) error (SWT.ERROR_THREAD_INVALID_ACCESS);
release (true);
}
再回头看看dispose()的javaDoc原文说明:
void org.eclipse.swt.widgets.Widget.dispose()
Disposes of the operating system resources associated with the receiver and all its descendants. After this method has been invoked, the receiver and all descendants will answer true when sent the message isDisposed(). Any internal connections between the widgets in the tree will have been removed to facilitate garbage collection. This method does nothing if the widget is already disposed.
NOTE: This method is not called recursively on the descendants of the receiver. This means that, widget implementers can not detect when a widget is being disposed of by re-implementing this method, but should instead listen for the Dispose event.
注意NOTE部分:该方法不会被子组件递归调用,所以widget被销毁的时候不能通过重写此方法检测到,应该用检测Dispose事件代替。
参考:
Widget.html.dispose()
http://help.eclipse.org/luna/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Widget.html#dispose()