使用泛型SwingWorker与EDT事件分发线程保持通讯

为什么要使用SwingWorker

在swing开发中,如果一个应用程序,执行一些任务,需要大量的时间来完成,比如下载一个大文件或执行一个复杂的数据库查询。

我们假设这些任务是由用户使用一个按钮触发的。在单线程应用程序,用户单击按钮,进入计算的过程,然后等待任务完成之前,所有的事件都在主线程EDT线程进行。

但如果某些任务耗时很长,用户将甚至不能在中途取消任务,应用程序必须响应只有当长任务完成。不幸的是,许多应用程序显着这样的行为和用户感到沮丧,程序仿佛卡死一样。

多线程可以解决这个问题。它使应用程序能够在不同的线程上执行长任务,但多线程带来一个问题,如果需要实时和主线程EDT进行数据交换,该怎么办?我们知道所有的Swing对象都只有一个线程处理,EDT事件调度线程,这导致一个问题:我们不能在EDT事件分派线程以外的其他线程共享对象的对象。

SwingWorker

SwingWorker是一个抽象类,java将它包装好,供方便调用,下面的例子使用字符串对象来通知应用程序。

提供了两个泛型参数。第一个代表返回的对象类型。另一个代表了通知(更新)应用程序的信息的类型,并在下面的例子中高亮显示。

public class MyBlankWorker extends SwingWorker<Integer, String> {

  @Override
  protected Integer doInBackground() throws Exception {
    // Start
    publish("Start");
    setProgress(1);

    // More work was done
    publish("More work was done");
    setProgress(10);

    // Complete
    publish("Complete");
    setProgress(100);
    return 1;
  }

  @Override
  protected void process(List< String> chunks) {
    // Messages received from the doInBackground() (when invoking the publish() method)
  }
}

通过setprogress() 设置0和100之间的整数。doinbackground() 用于漫长任务执行。此方法不是由事件调度线程调用的,而是由另一个线程(称为工作线程)。我们可以用publish()方法和/或setprogress()更新进度。调用两个方法都会对事件调度线程的创建新任务,它是工作线程和事件调度线程的线程之间的单向桥。

doinbackground()中调用publish()方法和/或setprogress()必须防止大量的任务发送给事件调度线程,引发洪水事件。

注意:publish()从工作线程调用,而process()由事件调度线程EDT调用。

举例:

输入:

 publish("a");
 publish("b", "c");
 publish("d", "e", "f");

结果就是:

 process("a", "b", "c", "d", "e", "f")

最后是一个文件全文检索的异步查询例子,查询某目录下所有的txt文件
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTextArea;
import javax.swing.SwingWorker;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang.StringUtils;

/**
 * Searches the text files under the given directory and counts the number of instances a given word is found in these
 * file.
 *
 * @author Albert Attard
 */
public class SearchForWordWorker extends SwingWorker<Integer, String> {

  private static void failIfInterrupted() throws InterruptedException {
    if (Thread.currentThread().isInterrupted()) {
      throw new InterruptedException("Interrupted while searching files");
    }
  }

  /** The word that is searched */
  private final String word;

  /** The directory under which the search occurs. All text files found under the given directory are searched. */
  private final File directory;

  /** The text area where messages are written. */
  private final JTextArea messagesTextArea;

  /**
   * Creates an instance of the worker
   *
   * @param word
   *          The word to search
   * @param directory
   *          the directory under which the search will occur. All text files found under the given directory are
   *          searched
   * @param messagesTextArea
   *          The text area where messages are written
   */
  public SearchForWordWorker(final String word, final File directory, final JTextArea messagesTextArea) {
    this.word = word;
    this.directory = directory;
    this.messagesTextArea = messagesTextArea;
  }

  @Override
  protected Integer doInBackground() throws Exception {
    // The number of instances the word is found
    int matches = 0;

    /*
     * List all text files under the given directory using the Apache IO library. This process cannot be interrupted
     * (stopped through cancellation). That is why we are checking right after the process whether it was interrupted or
     * not.
     */
    publish("Listing all text files under the directory: " + directory);
    final List<File> textFiles = new ArrayList<>(FileUtils.listFiles(directory, new SuffixFileFilter(".txt"),
        TrueFileFilter.TRUE));
    SearchForWordWorker.failIfInterrupted();
    publish("Found " + textFiles.size() + " text files under the directory: " + directory);

    for (int i = 0, size = textFiles.size(); i < size; i++) {
      /*
       * In order to respond to the cancellations, we need to check whether this thread (the worker thread) was
       * interrupted or not. If the thread was interrupted, then we simply throw an InterruptedException to indicate
       * that the worker thread was cancelled.
       */
      SearchForWordWorker.failIfInterrupted();

      // Update the status and indicate which file is being searched.
      final File file = textFiles.get(i);
      publish("Searching file: " + file);

      /*
       * Read the file content into a string, and count the matches using the Apache common IO and Lang libraries
       * respectively.
       */
      final String text = FileUtils.readFileToString(file);
      matches += StringUtils.countMatches(text, word);

      Thread.sleep(20);
      // Update the progress
      setProgress((i + 1) * 100 / size);
    }

    // Return the number of matches found
    return matches;
  }

  @Override
  protected void process(final List<String> chunks) {
    // Updates the messages text area
    for (final String string : chunks) {
      messagesTextArea.append(string);
      messagesTextArea.append("\n");
    }
  }
}

JFrame UI类如下

package com.javacreed.examples.swing.worker.part3;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingWorker.StateValue;

public class Application extends JFrame {

  /**  */
  private static final long serialVersionUID = -8668818312732181049L;

  private Action searchCancelAction;
  private Action browseAction;

  private JTextField wordTextField;
  private JTextField directoryPathTextField;
  private JTextArea messagesTextArea;
  private JProgressBar searchProgressBar;

  private SearchForWordWorker searchWorker;

  public Application() {
    initActions();
    initComponents();
  }

  private void cancel() {
    searchWorker.cancel(true);
  }

  private void initActions() {
    browseAction = new AbstractAction("Browse") {

      private static final long serialVersionUID = 4669650683189592364L;

      @Override
      public void actionPerformed(final ActionEvent e) {
        final File dir = new File(directoryPathTextField.getText()).getAbsoluteFile();
        final JFileChooser fileChooser = new JFileChooser(dir.getParentFile());
        fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        final int option = fileChooser.showOpenDialog(Application.this);
        if (option == JFileChooser.APPROVE_OPTION) {
          final File selected = fileChooser.getSelectedFile();
          directoryPathTextField.setText(selected.getAbsolutePath());
        }
      }
    };

    searchCancelAction = new AbstractAction("Search") {

      private static final long serialVersionUID = 4669650683189592364L;

      @Override
      public void actionPerformed(final ActionEvent e) {
        if (searchWorker == null) {
          search();
        } else {
          cancel();
        }
      }
    };
  }

  private void initComponents() {
    setLayout(new GridBagLayout());

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.insets = new Insets(2, 2, 2, 2);
    add(new JLabel("Word: "), constraints);

    wordTextField = new JTextField();
    wordTextField.setText("Hello");
    constraints = new GridBagConstraints();
    constraints.gridx = 1;
    constraints.gridy = 0;
    constraints.gridwidth = 2;
    constraints.insets = new Insets(2, 2, 2, 2);
    constraints.weightx = 1;
    constraints.fill = GridBagConstraints.BOTH;
    add(wordTextField, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 1;
    constraints.insets = new Insets(2, 2, 2, 2);
    add(new JLabel("Path: "), constraints);

    directoryPathTextField = new JTextField();
    directoryPathTextField.setText("C:\\Users\\Albert\\Work\\JavaCreed\\examples");
    constraints = new GridBagConstraints();
    constraints.gridx = 1;
    constraints.gridy = 1;
    constraints.gridwidth = 1;
    constraints.insets = new Insets(2, 2, 2, 2);
    constraints.weightx = 1;
    constraints.fill = GridBagConstraints.BOTH;
    add(directoryPathTextField, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 2;
    constraints.gridy = 1;
    constraints.insets = new Insets(2, 2, 2, 2);
    add(new JButton(browseAction), constraints);

    messagesTextArea = new JTextArea();
    messagesTextArea.setEditable(false);
    constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 2;
    constraints.gridwidth = 3;
    constraints.insets = new Insets(2, 2, 2, 2);
    constraints.weightx = 1;
    constraints.weighty = 1;
    constraints.fill = GridBagConstraints.BOTH;
    add(new JScrollPane(messagesTextArea), constraints);

    searchProgressBar = new JProgressBar();
    searchProgressBar.setStringPainted(true);
    searchProgressBar.setVisible(false);
    constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 3;
    constraints.gridwidth = 2;
    constraints.insets = new Insets(2, 2, 2, 2);
    constraints.weightx = 1;
    constraints.fill = GridBagConstraints.BOTH;
    add(searchProgressBar, constraints);

    constraints = new GridBagConstraints();
    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.insets = new Insets(2, 2, 2, 2);
    constraints.weightx = 0;
    add(new JButton(searchCancelAction), constraints);
  }

  private void search() {
    final String word = wordTextField.getText();
    final File directory = new File(directoryPathTextField.getText());
    messagesTextArea.setText("Searching for word ‘" + word + "‘ in text files under: " + directory.getAbsolutePath()
        + "\n");
    searchWorker = new SearchForWordWorker(word, directory, messagesTextArea);
    searchWorker.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(final PropertyChangeEvent event) {
        switch (event.getPropertyName()) {
        case "progress":
          searchProgressBar.setIndeterminate(false);
          searchProgressBar.setValue((Integer) event.getNewValue());
          break;
        case "state":
          switch ((StateValue) event.getNewValue()) {
          case DONE:
            searchProgressBar.setVisible(false);
            searchCancelAction.putValue(Action.NAME, "Search");
            searchWorker = null;
            break;
          case STARTED:
          case PENDING:
            searchCancelAction.putValue(Action.NAME, "Cancel");
            searchProgressBar.setVisible(true);
            searchProgressBar.setIndeterminate(true);
            break;
          }
          break;
        }
      }
    });
    searchWorker.execute();
  }
}

入口:

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Main {
  public static void main(final String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        final Application frame = new Application();
        frame.setTitle("Swing Worker Demo");
        frame.setSize(600, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
      }
    });
  }
}

引用:http://www.javacreed.com/swing-worker-example/

时间: 2024-11-02 14:53:32

使用泛型SwingWorker与EDT事件分发线程保持通讯的相关文章

Java中事件分发线程(EDT)与SwingUtilities.invokeLater相关总结

前言:这篇文章严格来说不算原创,算是我对这方面知识的一点小结,素材来至其他网友.当然我在我写的C段查询工具也用到了这方面的东西,不过由于代码太多不方便用作事例,因此用了他人的素材总结一下,望理解O(∩_∩)O~ 一 Swing线程基础 一个Swing程序中一般有下面三种类型的线程:    * 初始化线程(Initial Thread)    * UI事件调度线程(EDT)    * 任务线程(Worker Thread)每个程序必须有一个main方法,这是程序的入口.该方法运行在初始化或启动线程

Java多线程开发系列之番外篇:事件派发线程---EventDispatchThread

事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点.今天我们在多线程开发中,穿插进来这个线程.分别从线程的来由.原理和使用方法三个方面来学习事件派发线程. 一.事件派发线程的前世今生 事件(Event)派发(Dispatch)线程(Thread)简写为EDT,也就是各个首字母的简写.在一些书或者博客里边也将其译为事件分发线程.事件调度线程.巴拉巴拉,总之,知道这些名字就行.笔者认为这里翻译成派发更准确点. 熟悉Swing和awt编程的小伙伴对事件派发线程

Android输入事件从读取到分发五:事件分发前的拦截过程

在前面的文章:Android输入事件从读取到分发三:InputDispatcherThread线程分发事件的过程 一文中已经提过事件在分发前要做拦截的事情,只不过当时没有展开来分析,因此这篇文章的主要目的就是分析事件在分发前的拦截过程.(注:Android源码版本为6.0) 在Android输入事件从读取到分发三:InputDispatcherThread线程分发事件的过程 一文中我们分析到InputDispatcher类的notifyKey方法中,第一次尝试拦截事件,可以在看看这个方法: vo

Android的事件分发

一.Touch事件和绘制事件的异同之处 Touch事件和绘制事件很类似,都是由ViewRoot派发下来的,但是不同之处在绘制事件是由应用中的某个View发起请求,一层一层上传到ViewRoot,再有ViewRoot下发绘制,传递canvas给所有子View让其绘制自身,绘制好后,再通知WMS进行画到屏幕上.而Touch事件是由硬件捕获到触摸后由系统传递给应用的ViewRoot,再由ViewRoot往下一层一层传递. 他们的处理过程都是自上而下的分发,但是绘制多了一层自下往上的请求. 事件存在消耗

Android Touch事件分发过程

虽然网络上已经有非常多关于这个话题的优秀文章了,但还是写了这篇文章,主要还是为了加强自己的记忆吧,自己过一遍总比看别人的分析要深刻得多.那就走起吧. 简单演示样例 先看一个演示样例 : 布局文件 : <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id=&q

Android触摸事件分发机制

Android中的事件分为按键事件和触摸事件,这里对触摸事件进行阐述.Touch事件是由一个ACTION_DOWN,n个ACTION_MOVE,一个ACTION_UP组成onClick,onLongClick,onScroll等事件.Android中的控件都是继承View这个基类的,而控件分为两种:一种是继承View不能包含其他控件的控件:一种是继承ViewGroup可以包含其他控件的控件,暂且称为容器控件,比如ListView,GridView,LinearLayout等. 这里先对几个函数讲

iOS事件分发

前段时间项目有一个需求,要在点击闪屏的时候做一些处理,刚接到这个需求觉得很简单啊,在原有的view上加个button或者手势识别啥的,后面实现的时候发现还是有点坑.无论我在闪屏上面加button还是手势都无法响应到touch事件,后来也想了很多种可能,比如是否消息传递到了其他视图,可最终发现确是我自己把button从父视图remove的时候把消息也给remove了,具体原因是闪屏显示完成的时候我把button也remove了,而同时显示闪屏的时候项目也做了很多初始化工作,很占用主线程,导致UIA

View的事件分发和工作机制

View 的工作原理 ViewRoot和DecorView 1.ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均通过ViewRoot来完成. 2.ActivityThread中,Activity创建完成后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并建立两者的关联. 3.View的绘制流程从ViewRoot的performTraversals方法开始,经过measure.la

从ViewPager嵌套RecyclerView再嵌套RecyclerView看安卓事件分发机制

这两天伟大的PM下了一个需求,在一个竖滑列表里实现一个横向滑动的列表,没错,又是这种常见但是又经常被具有着强烈责任心和职业操守程序员所嗤之以鼻的效果,废话不多说,先上图: 实现的方式很多,因为项目中已经ViewPager+RV实现基本框架,所以现我也选择再添加一个RV实现相应的效果. 不过在写代码之前,先预估一下这个效果所有的坑. VP是横向滑动的,RV是竖向滑动的,那么现在再添加一个横向滑动的RV,肯定会有滑动冲突,主要表现在 VP和横向滑动RV 的冲突,因为两者都是横向滑动的,肯定有冲突,无