Swing的事件处理过程为:事件调度线程(Event Dispatch Thread)从事件队列(EventQueue)中获取底层系统捕获的原生事件,如鼠标、键盘、焦点、PAINT事件等。接着调用该事件源组件的 dispachEvent。该方法过滤出特殊事件后,调用processEvent进行处理。processEvent方法根据事件类型调用注册在这个组件上的相应事件处理器函数。事件处理器函数根据这些事件的特征,判断出用户的期望行为,然后根据期望行为改变组件的状态,然后根据需要刷新组件外观,触发带有特定语义的高级事件。此事件继续传播下去,直至调用Swing应用程序注册在该组件上的处理器函数.
事件调度线程在Swing应用程序事件处理函数actionPerformed没有完成之前是不能处理下一个事件的,如果Swing应用程序处理函数是一个时间复杂的任务(比如查询数据库并将结果显示到表格中),后面包括PAINT事件将在长时间内得不到执行。由于PAINT事件负责将界面更新,所以这就使用户界面失去响应。
打一个比方,事件处理线程就像进入某城唯一的单行道一样,事件相当于汽车。有种PAINT汽车负责为城市运输非常重要的生活物资。但是有一天,PAINT 前面有一辆汽车突然坏掉了,司机下来修车。但是这车太难修,一修就是几天,结果后面的PAINT汽车无法前进,物资无法按时运到城里。市民急了,市长虽然不停的打电话催PAINT公司,但即使PAINT公司多添加几辆车也没用。由于进城的唯一条路被那辆车给占着,所以再多的PAINT车也只能堵在路上。
不了解Swing的这种事件处理模型的人往往将时间复杂的任务放在处理函数中完成,这是造成Swing应用程序速度很慢的原因。用户触发这个动作,用户界面就失去了响应,于是给用户的感觉就是Swing太慢了。其实这个错误是程序员造成的,并不是Swing的过失。
那么如何避免这个问题,编写响应速度快的Swing应用程序呢?在SwingWorker的javadoc中有这样两条原则:
◆Time-consuming tasks should not be run on the Event Dispatch Thread. Otherwise the application becomes unresponsive. 耗时任务不要放到事件调度线程上执行,否则程序就会失去响应。
◆Swing components should be accessed on the Event Dispatch Thread only. Swing组件只能在事件调度线程上访问。
因此处理耗时任务时,首先要启动一个专门线程,将当前任务交给这个线程处理,而当前处理函数立即返回,继续处理后面未决的事件。这就像前面塞车的例子似的,那个司机只要简单的把车开到路边或者人行道上修理,整个公路系统就会恢复运转。
其次,在为耗时任务启动的线程访问Swing组件时,要使用SwingUtilties. invokeLater或者SwingUtilities.invokeAndWait 来访问,invokeLater和invokeAndWait的参数都是一个Runnable对象,这个Runnable对象将被像普通事件处理函数一样在事件调度线程上执行。这两个函数的区别是,invokeLater不阻塞当前任务线程,invokeAndWait阻塞当前线程,直到Runnable 对象被执行返回才继续。在前面塞车的例子中,司机在路边修车解决了塞车问题,但是他突然想起来要家里办些事情,这时他就可以打个电话让家里开车来。假如修车不受这件事情的影响,比如叫家人送他朋友一本书,他可以继续修车,这时就相当于invokeLater;假如修车受影响,比如缺少某个汽车零件,叫家人给他送过来,那么在家人来之前,他就没法继续修车,这时就相当于invokeAndWait。
下面举一个例子说明这两点,比如按下查询按钮,查询数据量很大的数据库,并显示在一个表中,这个过程需要给用户一个进度提示,并且能动态显示表格数据动态增加的过程。假设按钮的处理函数是myButton_actionPerformed,则:
- voidmyButton_actionPerformed(ActionEventevt){
- newMyQueryTask().start();
- }
- publicclassMyQueryTaskextendsThread{
- publicvoidrun(){
- //查询数据库
- finalResultSetresult=...;
- //显示记录
- for(;result.next();){
- //往表的Model中添加一行数据,并更新进度条,注意这都是访问组件
- SwingUtilities.invokeLater(newRunnable(){
- publicvoidrun(){
- addRecord(result);
- }
- });
- }
- ....
- }
- voidaddRecord(ResultSetresult){
- //往表格中添加数据
- jTable.add....
- //更新进度条
- jProgress.setValue(....);
- }
- }
【编辑推荐】