系统资源的管理
SWT是用Java开发的,Java语言本身的一大优势就是JVM的"垃圾回收机制",程序员通常不用理会变量的释放,内存的回收等问题。那么对SWT而言,系统资源的操作是不是也是如此?答案是一个坏消息,一个好消息。
坏消息是SWT并没采用JVM的垃圾回收机制去处理操作系统的资源回收问题,一个关键的因素是因为JVM的垃圾回收机制是不可控的,也就是说程序员不能知道,也不可能做到在某一时刻让JVM回收资源!这对系统资源的处理是致命的,试想你的程序希望在一个循环语句中去查看数万张图片,常规的处理方式是每次调入一张,查看,然后就立即释放该图片资源,而后在循环调入下一张图片,这对操作系统而言,任何时刻程序占用的仅仅是一张图片的资源。但如果这个过程完全交给JVM去处理,也许会是在循环语句结束后,JVM才会去释放图片资源,其结果可能是你的程序还没有运行结束,操作系统已经宕掉。
但下面的好消息也许会让这个坏消息变得无关紧要。对于SWT,只需了解两条简单的"黄金"法则就可以放心的使用系统资源!之所以称为黄金法则,一是因为少,只有两条,二是因为它们出奇的简单。第一条是"谁占用,谁释放",第二条是"父构件被销毁,子构件也同时被销毁"。第一条原则是一个无任何例外的原则,只要程序调用了系统资源类的构造函数,程序就应该关心在某一时刻要释放这个系统资源。比如调用了
Font font = new Font (display, "Courier", 10, SWT.NORMAL);
那么就应该在不在需要这个Font的时候调用
font.dispose();
对于第二个原则,是指如果程序调用某一构件的dispose()方法,那么所有这个构件的子构件也会被自动调用dispose()方法而销毁。通常这里指的子构件与父构件的关系是在调用构件的构造函数时形成的。比如,
Shell shell = new Shell();
Composite parent = new Composite(shell,SWT.NULL);
Composite child = new Composite(parent,SWT.NULL);
其中parent的父构件是shell,而shell则是程序的主窗口,所以没有相应的父构件,同时parent又包括了child子构件。如果调用shell.dispose()方法,应用第二条法则,那么parent和child构件的dispose()方法也会被SWT API自动调用,它们也随之销毁。
线程问题
在任何操作平台的GUI系统中,对构件或一些图形API的访问操作都要被严格同步并串行化。例如,在一个图形界面中的按键构件可被设成可用状态(enable)或禁用状态(disable),正常的处理方式是,用户对按键状态设置操作都要被放入到GUI系统的事件处理队列中(这意味着访问操作被串行化),然后依次处理(这意味着访问操作被同步)。想象当按键可用状态的设置函数还没有执行结束的时候,程序就希望再设置该按键为禁用状态,势必会引起冲突。实际上,这种操作在任何GUI系统都会触发异常。
Java语言本身就提供了多线程机制,这种机制对GUI编程来说是不利的,它不能保证图形构件操作的同步与串行化。SWT采用了一种简单而直接的方式去适应本地GUI系统对线程的要求:在SWT中,通常存在一个被称为"用户线程"的唯一线程,只有在这个线程中才能调用对构件或某些图形API的访问操作。如果在非用户线程中程序直接调用这些访问操作,那么SWTExcepiton异常会被抛出。但是SWT也在*.widget.Display类中提供了两个方法可以间接的在非用户线程的进行图形构件的访问操作,这是通过的syncExec(Runnable)和asyncExec(Runnable)这两个方法去实现的。例如:
//此时程序运行在一个非用户线程中,并且希望在构件panel上加入一个按键。
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
Button butt = new Button(panel,SWT.PUSH);
butt.setText("Push");
}
});
方法syncExec()和asyncExec()的区别在于前者要在指定的线程执行结束后才返回,而后者则无论指定的线程是否执行都会立即返回到当前线程。
SWT的扩展:JFace
JFace与SWT的关系好比Microsoft的MFC与SDK的关系,JFace是基于SWT开发,其API比SWT更加易于使用,但功能却没SWT来的直接。比如下面的代码应用JFace中的MessageDialog打开一个警告对话框:
MessageDialog.openWarning(parent,"Warning","Warning message");
如果只用SWT完成以上功能,语句不会少于30行!
JFace原本是为更加方便的使用SWT而编写的一组API,其主要目的是为了开发Eclipse IDE环境,而不是为了应用到其它的独立应用程序。因此在Eclipse 2.1版本之前,很难将JFace API完整的从Eclipse的内核API中剥离出来,总是要多多少少导入一些非JFace以外的Eclipse核心代码类或接口才能得到一个没有任何编译错误的JFace开发包。但目前Eclipse组织似乎已经逐渐意识到了JFace在开发独立应用程序起到的重要作用,在正在开发的2.1版本中,JFace也开始变成了和SWT一样的完整独立的开发包,只是这个开发包还在变动中(笔者写本文时,应用的Eclipse2.1M3版本)。JFace开发包的包前缀是以org.eclipse.jface开头。JAR包和源代码也和SWT一样,也在${你的eclipse安装路径}\plugins路径下去找。
对开发人员来说,在开发一个图形构件的时候,比较好的方式是先到JFace包去找一找,看是不是有更简洁的实现方法,如果没有再用SWT包去自己实现。比如JFace为对话框提供了很好的支持,除了各种类型的对话框(比如上面用的MessageDialog,或是带有Title栏的对话框),如要实现一个自定义的对话框也最好从JFace中的Dialog类继承,而不是从SWT中的*.widget.Dialog继承。
应用JFace中的Preference包中的类很容易为自己的软件做出一个很专业的配置对话框。对于Tree、Table等图形构件,它们在显示的同时也要和数据关联,例如Table中显示的数据,在JFace中的View包中为此类构件提供了Model-View方式的编程方法,这种方法使显示与数据分开,更加利于开发与维护。JFace中提供最多的功能就是对文本内容的处理。可以在org.eclipse.jface.text.*包中找到数十个与文本处理相关类。
与应用程序更近一步
Java程序通常是以class文件的方式发布的,运行class需要JRE或JDK的支持。这又是Java GUI程序的另一个致命的弱点,想象对一个面向广大用户的应用程序来说,无论你的程序功能有多简单,或是你的代码十分的精简,你都不得不让用户去下载一个7、8M的JRE,那是多么令人沮丧的一件事。而且对程序员来说,Class通常意味着源代码的暴露,反编译的工具让那些居心叵测的人轻易得到你的源代码。虽然有很多对Class的加密方法,但那总是以牺牲性能为代价的。好在我们还有其它的方式可用:把Class编译成exe文件!
通过SWT开发包,简单、跨平台、可靠等这些Java语言本身所具有的优点正渐渐融合到图形界面的应用程序开发中去。因此,我相信Java语言的另一扇成功之门正在逐渐打开
2反方观点:Swing速度优于SWT
说这句话,意思是Swing可以快过SWT,而且事实已经证明了这一点。前面文章已经说过,MiG Layout Site网站的Mikael Grev曾编写了一个GUI benchmark,来测试和比较Swing和SWT的性能,最终结论是除在Windows上Swing和SWT的速度几乎一样快之外,在其他操作系统上Swing的速度都要快于SWT。
这好像违背人们的常识,模拟的组件如何能快过本地组件?实际如果你按照下面的想法想,这个现象也就不难解释:
1. 本地组件实际上也是画出来,只不过是操作系统画罢了。这个通常是人们所忽略的,仿佛只有Swing是画出来的,而本地组件就是屏幕上的附属物似的,忘记了它们也是画出来的。
2. 现在Java平台的运行速度已经不慢于C/C++等静态编译型语言的产生的代码。人们往往提到本地仿佛就比Java实现要快。在过去也许可以这样看,但是现在随着Java 6平台性能的提高,许多地方Java平台的速度反而快过本地调用。因此是应该让人们从过去对于Java速度的印象中醒过来时候了,现在人们提到Java速度想到的应该是和C/C++相提并论了。
3. JNI调用耗费的时间是不能忽略的。JNI调用速度要比普通Java方法调用慢好几倍甚至几十倍。即便是在Java 6中,这种情况并没有改善。问题的实质在于Java运行时系统和本地库之间毕竟是两个异质系统,它们之间的调用必然有大量的转换开销。Java普通方法调用和JNI调用之间的关系就好像是本地调用和远程过程调用之间的区别,远程过程调用有网络数据通信和数据类型系统转换的开销,而JNI也有数据传递(主要是内存拷贝,速度虽然快过网络通信,但是相对直接调用是不能忽略的)和数据类型系统转换(Java数据类型和C语言数据类型)的开销。
4. Swing绝大部分是用Java平台模拟出的组件,这个过程都在一个系统平台内完成。而SWT是部分在本地系统完成,部分在Java平台完成,要在这两个平台之间需要进行频繁的数据交互。
5. Swing可以享受JVM的特殊待遇,进行特殊优化,比如inline,JIT代码,Swing事件队列对于事件的预处理(合并Paint事件,批处理Java 2D光栅指令等),这就像本地组件可以利用操作系统进行优化一样。
因此如果排除人的因素,也就是说排除Java平台的工程师、Windows的工程师、Linux的工程师和Solaris的工程师本身能力的区别,从理论上来说,Swing的速度是完全可以超过SWT的。理解这一点你只要记住最关键的两个原因是:
1. Java平台的速度可以和C/C++一样快。这个已经被很多benchmark证明了,它们的速度是相当的。因此Java模拟组件不一定会慢过本地组件,这要看谁的实现算法好。
2. 跨系统之间的调用(JNI调用)开销是不能忽略的。这个也在很多论文和benchmark得到证明。Swing中组件和数据之间的更新速度要远快于SWT,其原因就是Swing不需要进行数据传输,而SWT需要跨两个系统进行数据传输。这在大数据量编辑组件如Table中尤其明显。原因就在于Swing直接显示了数据模型,而SWT需要将这些数据使用JNI发送到本地组件中去。
不可否认,Microsoft在Windows上浸淫了这么多年,图形用户界面的优化已经非常完善,而反观Linux和Unix系列的桌面系统却没有如此优化过。这就是为什么前文的benchmark显示SWT能在Windows上和Swing速度相持平,而在其他平台上远慢于Swing的原因。因为SWT使用本地组件,它既占了Windows的光,也因此吃了Linux和Unix的亏
现在Java6.0出来了,据说在性能方面有了显著的提升,而且在桌面领域增加了许多特性就像,有人预测就java2掀起了服务器端的革命一样,java6将掀起桌面领域的革命。不过预测归预测,到底结果怎么样,还有待时间和实践的证明。