4

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

编译完这两个类后,我发现使用 Swing Timer 的 AnimationFrame2 显示的动画更流畅,不像 AnimationFrame1 中显示的动画那样卡顿,后者使用 while 循环和 sleep 方法。我的问题是:为什么 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();
            }
        });
    }

}
4

2 回答 2

8

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

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

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

注意:Thread.sleep 版本中存在潜在的线程安全问题。您在没有适当同步的情况下在工作线程和 UI 线程之间共享变量。尽管似乎在repaint内部引入了一个同步屏障,以确保工作线程对 UI 线程所做的更改的可见性,但这是一个偶然的效果,明确地确保可见性是一种更好的做法,例如通过声明变量 volatile .

于 2013-01-16T23:44:08.500 回答
2

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

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

于 2013-01-17T01:24:36.517 回答