5.4.3 SwingWorker类

在Swing中,如果要实现后台运行的工作线程,更好的做法是使用Java 6中引入的javax.swing.SwingWorker类。SwingWorker类的作用是把工作线程的任务和用户界面的更新这两者做了一个良好的切分。对于SwingWorker类的对象中的方法,一部分在工作线程中被调用,而另外一部分则在事件分发线程中被调用。开发人员只需要按照SwingWorker类的约定实现这些方法即可,不需要考虑这些方法所对应的线程的含义。SwingWorker类会负责保证这些方法在正确的线程中被调用。

SwingWorker类中唯一需要实现的方法是doInBackground。这个方法用来执行具体的工作,是在工作线程中运行的。方法doInBackground的返回值是工作线程的执行结果。如果希望得到doInBackground方法的执行结果,可以调用SwingWorker类的get方法。这个方法会阻塞直到doInBackground方法执行结束。一般来说,get方法会在事件分发线程中被调用。在这种情况下,会无法处理事件队列中的其他事件,使程序的界面暂时处于没有响应的状态。因此,如果需要在事件分发线程中等待工作线程的完成,比较好的做法是在等待过程中弹出一个模态对话框来提示用户。

在任务的执行过程中,可能会需要在用户界面上进行更新来显示任务的执行进度,比如更新进度条的指示值。一般通过3种做法来实现进度信息在工作线程和事件分发线程之间的传递。第一种做法是通过SwingWorker类的setProgress方法来更新任务的执行进度。进度的值从0到100,非常适合直接供进度条组件来使用。第二种做法是使用publish和process方法,其中publish方法是在工作线程中被调用的,用来发布任务执行过程中产生的中间结果;process方法是在事件分发线程中被调用的,利用publish方法产生的中间结果来更新用户界面。通过publish方法发布的数据会在process方法调用时的实际参数中得到。第三种做法是使用自定义的属性变化事件。在SwingWorker类中可以通过firePropertyChange方法来发布属性变化的事件。可以在事件分发线程中通过SwingWorker类的对象的addPropertyChangeListener方法来添加属性变化事件的处理方法。实际上,第一种做法中提到的setProgress方法也是以属性变化的方式来实现的,只不过用的是预定义的属性名称“progress”。

完成doInBackground方法之后,done方法会在事件分发线程中被调用,用来在任务完成之后进行界面的更新。自定义的SwingWorker类如果需要在任务完成之后更新界面,应该直接覆写done方法。如果希望取消一个任务的执行,可以使用SwingWorker类的cancel方法,不过cancel方法的成功完成,要求doInBackground方法在执行的过程中不时地通过isCancelled方法来检测是否发出了取消的请求,以取消任务。如果在doInBackground方法的实现中没有处理取消请求的相关逻辑,调用cancel方法实际上是不起作用的。一个设计良好的doInBackground方法的实现,应该允许用户随时取消任务的执行。在创建了SwingWorker类的对象之后,通过它的execute方法就可以启动其任务的执行。

代码清单5-11给出了SwingWorker类的一个示例,用来模拟网络上远程文件的下载过程。在doInBackground方法中通过setProgress方法来更新下载的进度,同时通过publish方法把当前的下载速度发布出去。而在事件分发线程中,通过SwingWorker类的addPropertyChangeListener方法来检查“process”属性的变化,并更新进度条组件的显示。在SwingWorker类的process方法中,则把publish方法发布出来的下载速度的值显示在标签上。在process方法被调用的时候,可能会收到多次publish方法调用的结果,程序应该根据需要从结果列表中选择合适的数据来显示。

代码清单5-11 SwingWorker类的使用示例


public class UseSwingWorker{

public void downloadFile(){

JFrame frame=new JFrame();

final JProgressBar progressBar=new JProgressBar();

frame.add(progressBar, BorderLayout.NORTH);

final JLabel label=new JLabel();

frame.add(label, BorderLayout.CENTER);

DownloadWorker worker=new DownloadWorker(label);

worker.addPropertyChangeListener(new PropertyChangeListener(){

public void propertyChange(PropertyChangeEvent evt){

if("progress".equals(evt.getPropertyName())){

progressBar.setValue((Integer)evt.getNewValue());

}

}

});

worker.execute();

frame.setSize(400,300);

frame.setVisible(true);

}

private static class DownloadWorker extends SwingWorker<String, Double>{

private JLabel label;

public DownloadWorker(JLabel label){

this.label=label;

}

public String doInBackground()throws Exception{

Random random=new Random();

for(int i=0;i<100;i++){

Thread.sleep(random.nextInt(1000));

setProgress(i+1);

publish(random.nextDouble()*30);

}

return"<Path>";

}

protected void process(List<Double>chunks){

Double speed=chunks.get(chunks.size()-1);

label.setText(MessageFormat.format("下载速度:{0,number,#.##}kb/s",speed));

}

}

}