Java Swing 常识篇之EDT
从毕业到现在用SWING已经一年多,在这里想总结一下过去学到的东西和经验,和各位兄弟姐妹们一起分享。在以后的文章中也会和大家一起来分享一些好的框架。说起JAVA SWING,普遍给人的感觉是“丑、慢、难”,丑是界面丑、慢是速度慢、难是开发难。其实熟悉LAF的同学都知道SWING确实不丑;说到‘慢’,SWING也不慢,只要懂得如何处理长时间的TASK,而不是什么都在EDT处理;说到难,只要我们有一套自己专门开发SWINGUI的类库,也不难了。所以在接下来的文章中,我将会和大家构建一个SWING UI FRAMEWORK(暂时称为:Fast-DUI),在这个Fast-DUI中将会整合SWING中的动画(淡入淡出、滑上滑下)、效果(shadow、drop-shadow)、感官(LookAndFeel)等等。构建这样的一个框架主要是为了能够快速地开发出“漂亮的、用户体验好的”桌面应用程序。好了,接下来马上开始。今天的这篇文章在Swing的应用中起到举足轻重的作用。这篇文章是开头篇,可能写得不好,感觉自己的写作水平,和小学生有得一比。
今天就一起来学习一下EDT(Event Dispatching Thread,字面上翻译成“事件分配线程”),那什么是EDT,EDT就简单地认为就是一个线程(特殊的线程),这个线程主要是管理着整个SWING GUI的事件,管理着整个UI界面(熟悉Java2D的朋友都知道,我们所看到的界面就是Java2D绘图绘出来的)。大家都知道,也许大学的时候,老师也会提到SWING不是线程安全的,不是线程安全的,那意味着我们用多线程的时候,要格外的小心,我们要把SWING里面东西的修改、获取独立放到一个线程上,但是这个刚好SWING的开发人员已经为我们解决了,我们不必自己管理一个额外的线程,我们只需要把他们放到EDT里面,下面我会举两个例子来说明一下如何使用EDT。
第一个例子来讲一下如果不在EDT中修改Swing的东西会出现什么问题,请看下面的例子:
- pakage org.dui.sample
- import java.awt.BorderLayout;
- import javax.swing.JFrame;
- import javax.swing.JTextField;
- import javax.swing.SwingUtilities;
- /**
- * <code>NotInEDTSample</code> just demonstrates the usage of Swing EDT simply.
- *
- * @author Jimmy.haung(SZ Team)
- * @since <i>DUI (Mar 25, 2013)</i>
- */
- public class NotInEDTSample extends JFrame {
- private static final long serialVersionUID = 1L;
- private JTextField m_txt;
- public NotInEDTSample() {
- initGUI();
- notInEDT();
- }
- /**
- * Init the GUI
- */
- public void initGUI() {
- this.setTitle("a simple EDT Sample");
- m_txt = new JTextField();
- getContentPane().add(m_txt, BorderLayout.CENTER);
- }
- /**
- * Process not under the EDT. 这里我启动了10个线程来改变<code>m_txt</code>的内容.
- */
- private void notInEDT() {
- for (int i = 0; i < 4; ++i) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- m_txt.setText("我不在EDT中操作!");
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
- }
- /**
- * Launch the application.
- */
- public static void main(String[] args) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- NotInEDTSample oFrame = new NotInEDTSample();
- oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- oFrame.setLocationRelativeTo(null);
- oFrame.setSize(300, 200);
- oFrame.setVisible(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
- }
运行上面的程序,然后点击几下界面后,界面就‘死掉’了(大家可以Resize看一下,或者点击一下窗口的Close按钮),‘死掉’有可能是死锁造成的,只是可能,但是程序也没有报Exception,这就是不确定性,如果Swing的stub不在EDT里面运行,带给程序很多的不确定性。在我以前看到的项目中,由于EDT的原因,引发可各种不可预测的Exception。
如果我们将上面的程序稍微改一下,将notInEDT的方法改成下面的样子:
- private void notInEDT() {
- for (int i = 0; i < 4; ++i) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- m_txt.setText("我在EDT中操作!");
- }
- });
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- }
- }
通过修改是不是发现没前面的问题了。但是有一些新学的朋友会问,如果本来就在EDT中就不用再加入到EDT, 但是如何才知道当前的线程是否是EDT呢,请看SwingUtilities.isEventDispatchThread();想得多周到的开发Swing的人员!SwingUtilities、SwingUtilities2、SwingUtilities3这几个类在以后都会经常用到,非常方便。看了上面的例子,大家有什么感想呢?
下面我们接着来写一个例子,我们试想一下,如果在EDT中执行一些时间长的任务会怎样呢?
- import java.awt.BorderLayout;
- /**
- * <code>NotInEDTSample</code> just demonstrates the usage of Swing EDT simply.
- *
- * @author Jimmy.haung(SZ Team)
- * @since <i>DUI (Mar 25, 2013)</i>
- */
- public class LongTaskSample extends JFrame {
- private static final long serialVersionUID = 1L;
- private JButton m_btn;
- public LongTaskSample() {
- initGUI();
- initEventHandler();
- }
- /**
- * Init the GUI
- */
- public void initGUI() {
- this.setTitle("a simple EDT Sample");
- m_btn = new JButton("click me to process long task");
- getContentPane().add(m_btn, BorderLayout.CENTER);
- }
- /**
- * Bind the event to the component.
- */
- public void initEventHandler(){
- m_btn.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- longTask();
- }
- });
- }
- /**
- * Use about five seconds to process this task.
- */
- private void longTask() {
- //为true,请看 #main(String[])
- System.err.println(SwingUtilities.isEventDispatchThread());
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- /**
- * Launch the application.
- */
- public static void main(String[] args) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- LongTaskSample oFrame = new LongTaskSample();
- oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- oFrame.setLocationRelativeTo(null);
- oFrame.setSize(300, 200);
- oFrame.setVisible(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
- }
点击了按钮后,我们发现程序死掉了大约5秒后,就醒来了,这就是“装死”的现象。所以我们看到在EDT里面运行时间长的任务的结果会引发什么后果,就会觉得很慢。上面的两个例子只是说明了一丁点的问题。在大家以后的项目中将会遇到更多问题。那么现在的问题是如果来改善上面的问题,我们应该在其他的线程中执行时间长的任务,而在EDT只是负责界面的显示,以及界面数据的修改、获取等,这以后还会讲,在这里提示一下,SwingWorker可以很好解决上面的问题。马马虎虎终于完成了第一篇文章。如果新学的朋友,还是不懂得话,请看http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html。如果还是不明白,不要紧,以后会慢慢体会到的。前面的几篇的文章将会将一些Swing基本的东西,讲完基础的东西后将会开始我们的Fast-DUI之旅。预告一下,下一章将会是布局(Layout),如何简单使用布局管理器来实现复杂的布局。
这里先预告一下我们以后要构建Fast-DUI的LAF(这只是其中的一种Skin,大家可以根据自己的情况来改变Skin),如下图: