Java Swing Timer比预期慢

时间:2014-06-16 19:23:57

标签: java swing timer

我正在编写一个必须每隔一段时间重新绘制的挥杆程序。出于某种原因,即使我指定较低的延迟,javax.swing.Timer似乎也只是每800-900ms重新绘制一次。 (例如100ms)我认为延迟可能是由于repaint()方法需要800ms才能运行,但我计时它只需要0.3ms。我尝试使用Thread.sleep()并按所需间隔重新绘制。任何人都可以帮忙解释为什么会这样吗?

我正在尝试做的事情似乎是javax.swing.Timer的预期目的,所以我很困惑为什么它会如此减慢代码的速度。 主要驾驶员类:

import java.awt.Dimension;

import javax.swing.JFrame;


public class GUIDriver{

public AnimationPanel animationPanel;

public static void main(String[] args){
    GUIDriver gui = new GUIDriver();
    gui.createAndShowGUI();

}

public void createAndShowGUI(){
    animationPanel = AnimationPanel(50);
    JFrame frame = new JFrame("Animations");

    frame.setContentPane(animationPanel);
    frame.setPreferredSize(new Dimension(1015, 840));

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    frame.pack();
    frame.validate();
    frame.doLayout();
    frame.setVisible(true);
    animationPanel.draw(); //only here when using Thread.sleep() method
}


}

自定义JPanel扩展程序:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
import java.text.SimpleDateFormat;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * This extension of the JPanel overrides the paintComponent method to provide a     framework for
 * creating 2-D animations.
 */
@SuppressWarnings("serial")
public class AnimationPanel extends JPanel implements ActionListener{

public ArrayList<LeakInstance> data;
public Timer timer;
public SimpleDateFormat dateOutput = new SimpleDateFormat("MM/dd/yyyy");
BufferedImage background;


public Color lineColor = Color.black;
public Color under = Color.blue;
public Color over = Color.red;
public Font defaultFont = new Font("default", Font.BOLD, 14);

public float cutoff = 50;

public int timeStep = 0;
public long startTime = 0;

public Graphics2D screen;

public AnimationPanel(float cutoff) {
    super();
    setLayout(null);
    read("Data.txt");
    this.cutoff = cutoff;
    timer = new Timer(100, this); //commented out when using Thread.sleep()
    try {
        background = ImageIO.read(new File("img.png"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    repaint();
    timer.start(); //commented out when using Thread.sleep()
}


/**
 * This method overrides JPanel's paintComponent method in order to customize the rendering of 2-D graphics
 * and thus make animation possible. It is not called directly; it is called by repaint() which fully refreshes
 * the screen.
 */
@Override
public void paintComponent(Graphics g) {
    ArrayList<String> drawn = new ArrayList<String>();
    screen = (Graphics2D)g;
    RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
    renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    screen.setRenderingHints(renderHints);


    screen.drawImage(background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH), 0, 0, this);

    g.setFont(defaultFont);

    screen.setColor(Color.orange);
    screen.fillRect(485, 735, 100, 20); //cover old date
    screen.setColor(lineColor);
    screen.drawString(dateOutput.format(data.get(timeStep).getDate()), 500, 750);
    screen.drawString(Integer.toString(timeStep), 300, 750);
    System.out.println((System.nanoTime() - startTime)/1e9f);
    startTime = System.nanoTime();

    float x, y;
    float z;
    int xoffset, yoffset;
    for(int i = 0; drawn.size() < 24 && (timeStep-i) > -1; i++){
        if(!drawn.contains(data.get(timeStep-i).getName())){
            xoffset = 0;
            yoffset = 15;
            String name = data.get(timeStep-i).getName();
            drawn.add(name);
            x = data.get(timeStep-i).getLocation().x;
            y = data.get(timeStep-i).getLocation().y;
            z = data.get(timeStep-i).getZ();

            if(z > cutoff)
                screen.setColor(over);
            else
                screen.setColor(under);
            switch(name){
                            //various cases to change x or y offset
            }
            screen.drawString(Float.toString(z), x+xoffset, y+yoffset);
            screen.setColor(lineColor);
            screen.drawLine((int)x-2, (int)y, (int)x+2,(int) y);
            screen.drawLine((int)x, (int)y-2, (int)x, (int)y+2);
        }
    }
}

public void draw(){
    try{
        for(; timeStep < data.size()-1; timeStep++){
            Thread.sleep(100);
            repaint();
        }
    }catch(Exception e){
        e.printStackTrace();
    }
}

public void read(String filename){
    File file = new File(filename);
    data = new ArrayList<MyType>(100);
    try(Scanner scan = new Scanner(file)){
        while(scan.hasNextLine())
            data.add(new MyType(scan.next(), scan.next(), scan.nextFloat(),
                    new Point2D.Float(scan.nextFloat(), scan.nextFloat())));
    }catch (Exception e){
        e.printStackTrace();
    }
    Collections.sort(data);
}


@Override
public void actionPerformed(ActionEvent e) {
    if(e.getSource().equals(timer)){
        if(timeStep < data.size()-1){
            timeStep++;
            repaint();
        }
        else
            timer.stop();
    }
}
 }

1 个答案:

答案 0 :(得分:3)

我遇到的主要问题是

screen.drawImage(background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH), 0, 0, this);

这会导致repaint事件不断发生,同时将更新重绘时间缩短为0.25毫秒

当我预先缩放图像时(我必须将其更改为Image),例如......

try {
    background = ImageIO.read(new File("C:\\Users\\Shane Whitehead\\Dropbox\\Wallpapers\\5781217115_4182ee16da_o.jpg"));
    background = background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH);
} catch (IOException e) {
    e.printStackTrace();
}

我能够获得0.1ms的重绘时间。

我尝试在data列表中使用100,1000和10,000个元素而没有太多问题(只是为了好玩,我尝试了100,000,仍然重新绘制了0.1ms)

您需要特别注意确保油漆工艺得到很好的优化

根据评论更新

  

然而,它仍然没有解释为什么用油漆方法   〜。3ms导致定时器耗时~800ms。或者为什么使用   javax.swing.Timer似乎比使用慢一个数量级   Thread.sleep()方法

实际上确实如此,但您需要了解事件调度线程,事件队列和RepaintManager的工作原理。

基本上...

  • paintComponent被呼叫,您拨打getScaledInstance,通过repaint触发ImageObserver请求。 getScaledInstance速度慢,100毫秒慢。 RepaintManager优化重绘请求并尝试减少在EDT上发布的绘制事件的数量,这有时是好的,有时是坏的。这些绘制事件放在事件队列
  • javax.swing.Timer触发事件,此事件被放置到事件队列中,由EDT处理。现在,这就是它变得很奇怪的地方#34; {em>&#34;如果coalesce为true(默认值),则javax.swing.Timer进行优化,只允许一个Runnable在EventQueue上排队并等待&#34; - 所以如果有预先存在的&#34;计时器&#34;事件已经在事件队列中,没有新事件发布。
  • {最终}调用actionPerformed方法,也是在EDT的上下文中,从处理其他事件开始(甚至很短的时间)......这一切都加起来...... < / LI>
  • 和/或再次调用paintComponent,重复......

所以,可能发生的事情,由getScaledInstance引起的重复更新相距甚远,以防止RepaintManager优化这些调用,这会给EDT带来压力,{{{1} 1}}正在逐渐消失,但是因为事件可能没有得到足够快的处理,所以有些东西会被丢弃,从长远来看,这会导致&#34; paint&#34;进一步分开。

Timer方法不会遭遇这些问题的原因是因为它可以通过新的绘制请求向EDT发送垃圾邮件,而不考虑事件队列的状态......

此外,我可能会破坏您Thread更新,这意味着在处理Thread列表中的所有项目之前,不会绘制任何内容,请查看Initial Threads以获取更多详细信息< / p>

有关绘画如何在Swing中工作的更多详细信息,请查看Painting in AWT and Swing

您可能还想查看The Perils of Image.getScaledInstance()Java: maintaining aspect ratio of JPanel background image,了解有关缩放的更多详情......

更新了一些其他测试

所以我补充说......

data

要测试缩放过程,通常需要大约200-240 ms。剩下的油漆工艺只增加了大约10毫秒。

我在使用long scaleStart = System.currentTimeMillis(); screen.drawImage(background.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH), 0, 0, this); System.out.println("Scaled in: " + ((System.currentTimeMillis()- scaleStart) / 1000f)); 时这样做了,所以我没有从转换合并中获得额外的好处。

通过预先缩放图像,我获得了0.1ms的恒定更新(有和没有timer.setCoalesce(false);