为什么Java swing计时器导致动画口吃少于睡眠?

时间:2013-01-16 23:34:34

标签: java performance swing animation timer

我有两个几乎相同的类:AnimationFrame1和AnimationFrame2。这两个类都显示一个蓝色的球在500 x 500的窗口上水平来回移动。除了runAnimation()和createAndShowGUI()方法之外,这两个类是相同的。在其runAnimation()方法中,AnimationFrame1使用while循环和sleep方法创建动画循环,而AnimationFrame2使用Swing Timer。在其createAndShowGUI()方法中,AnimationFrame1创建一个新线程并在其上调用runAnimation()方法,而AnimationFrame2只调用没有新线程的runAnimation()方法。

在编译了这两个类之后,我发现使用Swing Timer的AnimationFrame2显示了一个更平滑的动画,它不会像使用while循环和sleep方法的AnimationFrame1中显示的动画那样断断续续。我的问题是:为什么AnimationFrame1在动画中显示出比AnimationFrame2更多的口吃?我一直在寻找原因,但迄今为止一无所获。

另外,我显然是Java新手,所以如果您发现我的代码有任何问题或者您知道我可以改进它的任何方式,请告诉我。

这是AnimationFrame1:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;

class AnimationFrame1 extends JPanel {

    int ovalX;
    int prevX;
    Timer timer;
    boolean moveRight;
    BufferedImage img;

    public AnimationFrame1() {
        setPreferredSize(new Dimension(500, 500));
    }

    public void runAnimation() {
        moveRight = true;
        img = null;
        ovalX = 0;
        prevX = 0;
        while(true) {
            if (moveRight == true) {
                prevX = ovalX;
                ovalX = ovalX + 4;
            }
            else {
                prevX = ovalX - 4;
                ovalX = ovalX - 4;
            }
            repaint();
            if (ovalX > 430) {
                moveRight = false;
            }
            if (ovalX == 0) {
                moveRight = true;
            }
            try {
                Thread.sleep(25);
            }
            catch(Exception e) {
            }
        }
    }

    public void paintComponent(Graphics g) {
        if (img == null) {
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = getGraphicsConfiguration();
            img = gc.createCompatibleImage(78, 70);
            Graphics gImg = img.getGraphics();
            gImg.setColor(getBackground());
            gImg.fillRect(0, 0, getWidth(), getHeight());
            gImg.setColor(Color.BLUE);
            gImg.fillOval(4, 0, 70, 70);
            gImg.dispose();
        }
        g.drawImage(img, ovalX, 250, null);
    }

    public static void createAndShowGUI() {
        JFrame mainFrame = new JFrame();
        final AnimationFrame1 animFrame = new AnimationFrame1();
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.add(animFrame);
        mainFrame.pack();
        mainFrame.createBufferStrategy(2);
        mainFrame.setVisible(true);
        new Thread(new Runnable() {
            public void run() {
                animFrame.runAnimation();
            }
        }).start();
    }    

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

}

这是AnimationFrame2:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;

class AnimationFrame2 extends JPanel {

    int ovalX;
    int prevX;
    Timer timer;
    boolean moveRight;
    BufferedImage img;

    public AnimationFrame2() {
        setPreferredSize(new Dimension(500, 500));
    }

    public void runAnimation() {
        moveRight = true;
        img = null;
        ovalX = 0;
        prevX = 0;
        timer = new Timer(25, new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                if (moveRight == true) {
                    prevX = ovalX;
                    ovalX = ovalX + 4;
                }
                else {
                    prevX = ovalX - 4;
                    ovalX = ovalX - 4;
                }
                repaint();
                if (ovalX > 430) {
                    moveRight = false;
                }
                if (ovalX == 0) {
                    moveRight = true;
                }
            }
        });
        timer.start();
    }

    public void paintComponent(Graphics g) {
        if (img == null) {
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = getGraphicsConfiguration();
            img = gc.createCompatibleImage(78, 70);
            Graphics gImg = img.getGraphics();
            gImg.setColor(getBackground());
            gImg.fillRect(0, 0, getWidth(), getHeight());
            gImg.setColor(Color.BLUE);
            gImg.fillOval(4, 0, 70, 70);
            gImg.dispose();
        }
        g.drawImage(img, ovalX, 250, null);
    }

    public static void createAndShowGUI() {
        JFrame mainFrame = new JFrame();
        final AnimationFrame2 animFrame = new AnimationFrame2();
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.add(animFrame);
        mainFrame.pack();
        mainFrame.createBufferStrategy(2);
        mainFrame.setVisible(true);
        animFrame.runAnimation();
    }    

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

}

2 个答案:

答案 0 :(得分:8)

在代码中放置标记后,似乎Timer版本实际上每30毫秒运行一次,而Thread.sleep版本每25毫秒运行一次。可能有几种解释,包括:

  • 计时器的分辨率,不如Thread.sleep
  • 的分辨率
  • 事实上,计时器是单线程的(除了等待,一切都在EDT中运行)所以如果任务(如重新绘制)需要超过25毫秒,它将延迟下一个任务

如果我将睡眠时间增加到30毫秒,则2个动画相似(实际数量可能因机器而异)。

注意:Thread.sleep版本中存在潜在的线程安全问题。您在工作线程和UI线程之间共享变量而没有正确的同步。虽然repaint内部似乎在内部引入了同步障碍,该障碍确保了工作线程从UI线程所做的更改的可见性,但它是一种偶然的效果,并且明确确保可见性是更好的做法,例如通过声明变量volatile。

答案 1 :(得分:2)

问题的原因很可能是由于第一个版本中“违反”AWT语义。你不能在EDT之外运行gui更新代码。

更新:即使repaint()方法可以安全地从另一个线程调用,它所做的只是对将在EDT上运行的事件进行排队。这意味着在修改ovalx的线程和正在读取它的线程EDT线程之间存在竞争条件。这将导致移动不均匀,因为绘图代码可能会看到与信令代码意图不同的值。