在屏幕上重复加载/删除多个图像的最佳方法

时间:2017-10-24 13:20:13

标签: java multithreading image swing jpanel

我有一个任务执行器,它有5个外部面板,每个外部面板都是JFrame,其innerPanelinnerpanel是负责绘制图像的JPanel。我一遍又一遍地显示和删除许多图像,而且我的表现非常糟糕。

有人可以建议在不使用JFrame的情况下加载和删除许多图片吗?

我在屏幕的不同部分显示图像,有时持续时间非常短50-1500ms。有时图像只是部分加载而其他图像根本不加载。

Main

public class Main {

    public static void main(String[] args) {

        new Main();
    }

    public Main() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleWithFixedDelay(new ImageTask(new OuterPanel(ScreenPosition.CENTER)), 0, 3, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleWithFixedDelay(new ImageTask(new OuterPanel(ScreenPosition.CENTER)), 0, 3, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleWithFixedDelay(new ImageTask(new OuterPanel(ScreenPosition.CENTER)), 0, 3, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleWithFixedDelay(new ImageTask(new OuterPanel(ScreenPosition.CENTER)), 0, 3, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleWithFixedDelay(new ImageTask(new OuterPanel(ScreenPosition.CENTER)), 0, 3, TimeUnit.SECONDS);
    }
}

ImageTask

public class ImageTask implements Runnable {

    private OuterPanel outerPanel;
    private int messageIndex;

    public ImageTask(OuterPanel outerPanel) {
        this.outerPanel = outerPanel;
    }

    @Override
    public void run() {
        outerPanel.setMessage(imagePath);
        outerPanel.setVisible(true);
        try {
            TimeUnit.MILLISECONDS.sleep(150);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        outerPanel.setVisible(false);
    }
}

OuterPanel

public class OuterPanel extends JFrame {

    private InnerPanel innerPanel;
    public static int height = 200;
    public static int width = 200;

    public OuterPanel(ScreenPosition screenPosition) {
        initComponents();

        setFocusable(false);
        setUndecorated(true);
        setBackground(new Color(0, 0, 0, 0));
        setAlwaysOnTop(true);
        setSize(SetScreenLocation.screenSize.width, SetScreenLocation.screenSize.height);
        setFocusableWindowState(false);
        setEnabled(false);
        pack();

        setLocation(0, 0);
    }

    private void initComponents() {
        innerPanel = new InnerPanel();
        add(innerPanel);
    }

    public void setMessage(Message message) {
        ImageIcon icon = IconFetch.getInstance().getIcon(message.getImagePath());
        if (icon != null) {
            Image img = IconFetch.getInstance().getScaledImage(icon.getImage(), width, height);
            innerPanel.setImage(img);
        }
    }

}

InnerPanel

public class InnerPanel extends JPanel {
    private String message;
    private Image img;

    public InnerPanel() {
        setOpaque(false);
        setPreferredSize(new Dimension(900, 450));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;

        if (img != null) {
            x = (this.getWidth() - img.getWidth(null)) / 2;
            y = (this.getHeight() - img.getHeight(null)) / 2;
            g2d.drawImage(img, x, y, this);
        }
    }

    public void setImage(Image image) {
        this.img = image;
        repaint();
    }
}

1 个答案:

答案 0 :(得分:1)

所以,有三件事是时间成本的:

  1. 正在载入图片
  2. 第一次创建/显示框架
  3. 垃圾收集
  4. 如果可以减少这些开销,可以提高系统的性能

    我看不出IconFetch是如何工作的,但我强烈建议:

    1. 您使用ImageIO.read加载图片,因为在图片完全加载之前它不会返回
    2. 您缓存了结果,因此您不会一遍又一遍地重新加载相同的图像,这是非常昂贵的。
    3. 这是一个非常简单的例子。你传递一个文件路径,它会将图像加载到缓存中,如果它尚未缓存,否则它将返回缓存值

      public enum ImagePool {
          INSTANCE;
      
          private Map<String, Image> images;
      
          private ImagePool() {
              images = new HashMap<>(25);
          }
      
          public synchronized Image grab(String name) throws IOException {
              Image image = images.get(name);
              if (image == null) {
                  image = ImageIO.read(new File(name));
                  images.put(name, image);
              }
              return image;
          }
      
      }
      

      第一次在屏幕上显示一个窗口是一个代价高昂的过程,并且由于系统开销,您无法始终知道窗口何时在屏幕上实际“可见”。

      为此,您应该专注于将窗口缓存到某种池中并尽可能地重新使用它们,例如:

      public enum FramePool {
          INSTANCE;
      
          private int minSize = 15;
          private int maxSize = 25;
      
          private List<ImageFrame> avaliable;
      
          private FramePool() {
              avaliable = new ArrayList<>(25);
      
              for (int index = 0; index < minSize; index++) {
                  avaliable.add(new ImageFrame());
              }
          }
      
          public synchronized ImageFrame grab() {
              ImageFrame frame;
              System.out.println(avaliable.size());
              if (avaliable.isEmpty()) {
                  System.out.println("Make new");
                  frame = new ImageFrame();
              } else {
                  frame = avaliable.remove(0);
              }
      
              return frame;
          }
      
          public synchronized void release(ImageFrame frame) {
              if (avaliable.size() < maxSize) {
                  avaliable.add(frame);
              } else {
                  System.out.println("Destory");
                  frame.dispose();
              }
          }
      
          public class ImageFrame extends JFrame {
      
              private JLabel label;
              private int timeout = 150;
      
              public ImageFrame() throws HeadlessException {
                  label = new JLabel();
                  add(label);
                  setUndecorated(true);
                  setBackground(new Color(0, 0, 0, 0));
                  setAlwaysOnTop(true);
                  setFocusableWindowState(false);
                  setFocusable(false);
                  pack();
      
                  addWindowListener(new WindowAdapter() {
                      @Override
                      public void windowOpened(WindowEvent e) {
                          System.out.println("Show me");
                          Timer timer = new Timer(timeout, new ActionListener() {
                              @Override
                              public void actionPerformed(ActionEvent e) {
                                  System.out.println("Hide me");
                                  setVisible(false);
                                  System.out.println("Return");
                                  FramePool.INSTANCE.release(ImageFrame.this);
                              }
                          });
                          timer.start();
                      }
      
                  });
              }
      
              public void setTimeout(int timeout) {
                  this.timeout = timeout;
              }
      
              public void setImage(Image image) {
                  label.setIcon(new ImageIcon(image));
                  pack();
      
                  Rectangle bounds = new Rectangle(0, 0, 0, 0);
      
                  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                  GraphicsDevice gd = ge.getDefaultScreenDevice();
      
                  GraphicsConfiguration gc = gd.getDefaultConfiguration();
                  bounds = gc.getBounds();
      
                  Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
      
                  bounds.x += insets.left;
                  bounds.y += insets.top;
                  bounds.width -= (insets.left + insets.right);
                  bounds.height -= (insets.top + insets.bottom);
      
                  int x = (int) (Math.random() * bounds.width);
                  int y = (int) (Math.random() * bounds.height);
      
                  if (x + getWidth() > bounds.width) {
                      x = bounds.width - getWidth();
                      if (x < 0) {
                          x = 0;
                      }
                  }
                  if (y + getHeight() > bounds.height) {
                      y = bounds.height - getHeight();
                      if (y < 0) {
                          y = 0;
                      }
                  }
      
                  setLocation(bounds.x + x, bounds.y + y);
              }
      
          }
      }
      

      最后,你设置了任务......

      public class ImageTask implements Runnable {
      
          private String name;
          private int timeout;
      
          public ImageTask(String name, int timeout) {
              this.name = name;
              this.timeout = timeout;
          }
      
          @Override
          public void run() {
              try {
                  System.out.println(this);
                  FramePool.ImageFrame frame = FramePool.INSTANCE.grab();
                  frame.setImage(ImagePool.INSTANCE.grab(name));
                  frame.setTimeout(timeout);
                  frame.setVisible(true);
              } catch (IOException ex) {
                  ex.printStackTrace();
              }
          }
      }
      

      现在,我采取了不同的方法。基本上,我只是将图像的名称传递给任务,然后使用框架和图像池来构建结果并将其显示在屏幕上。

      对我来说,好处是孤立的控制。要在屏幕上获取图像,我只需要知道ImageTask和图像文件名,而不需要其他内容。

      对于我的测试,我从目录中加载了一堆图像并显示它们。

      ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
      File[] files = path.listFiles((File pathname) -> pathname.getName().toLowerCase().endsWith(".png"));
      for (File file : files) {
          scheduledExecutorService.scheduleWithFixedDelay(new ImageTask(file.getPath(), 250 + (int) (Math.random() * 1000)), 0, 3, TimeUnit.SECONDS);
      }
      

      一旦创建了所有对象,它就会顺利运行

      这个想法有不同之处,例如,您可能不需要FramePool并且可以为每个ImageTask创建一个实例