大概通过SwingWorker

时间:2018-08-12 14:58:44

标签: java swing swingworker

我有一个希望SwingWorker可以帮助我的问题,但是我不确定如何将其集成到我的程序中。

问题:

在CardLayout中,我在Card1上有一个打开Card2的按钮。 Card2的JList带有自定义渲染器(扩展了JLabel),平均将显示1到6张图像,这些图像是:

  • PNG
  • 大约500kb的大小
  • 通过imageIO随卡的更换而加载

渲染器执行繁重的操作,例如图像缩放或模糊化,然后将图像设置为JLabel图标。

如果必须渲染大约6张图像,这几乎需要花费一秒钟的时间,这种情况并不经常发生,但是即使偶尔出现一秒钟的无响应感也很糟糕。

现在,我认为SwingWorker可能会在这里提供帮助,但是对于如何集成它,我感到非常困惑。

假设我们有此代码段

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class Example {

    private JPanel mainPanel = new JPanel();
    private JList<Product> list = new JList();
    private JScrollPane scroll = new JScrollPane();
    private Map<String, Color> colorMap = new HashMap<>();

    public Example() {
        colorMap.put("red", Color.red);
        colorMap.put("blue", Color.blue);
        colorMap.put("cyan", Color.cyan);
        colorMap.put("green", Color.green);
        colorMap.put("yellow", Color.yellow);

        mainPanel.setBackground(new Color(129, 133, 142));

        scroll.setViewportView(list);
        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scroll.setPreferredSize(new Dimension(80,200));

        list.setCellRenderer(new CustomRenderer());
        DefaultListModel model = new DefaultListModel();

        model.addElement(new Product("red"));
        model.addElement(new Product("yellow"));
        model.addElement(new Product("blue"));
        model.addElement(new Product("red"));
        model.addElement(new Product("cyan"));
        list.setModel(model);

        mainPanel.add(scroll);
    }

    public static void main(String[] args) throws IOException {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("WorkerTest");
                frame.setContentPane(new Example().mainPanel);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLocation(300, 300);
                frame.setMinimumSize(new Dimension(160, 255));
                frame.setVisible(true);
            }
        });
    }

    class CustomRenderer extends JLabel implements ListCellRenderer<Product> {

        private  Product product;
        public CustomRenderer() {
            setOpaque(false);
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends Product> list, Product product, int index, boolean isSelected, boolean cellHasFocus) {
            this.product = product;

            /**
             * in the actual code image is png with alpha channel respectively named to the productID of the JList object
             *
             * String id = product.getId();
             * image = ImageIO.read(getClass().getResource("../../resources/images/" + id + ".png"));
             */

            BufferedImage image1 = new BufferedImage(80, 50, BufferedImage.TYPE_INT_RGB);
            BufferedImage image2 = new BufferedImage( 80, 75, BufferedImage.TYPE_INT_RGB);
            Graphics g = image2.getGraphics();

            /**
             * this is only an example, in the actual code I might also apply gaussian blurs or rescale several time
             */
            g.drawImage(image1,0,0,null);

            setIcon(new ImageIcon(image2));

            return this;
        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.setColor(colorMap.get(product.getColor()));
            g.fillRect(0,0,80,75);
        }
    }

    class Product {

        String productID;
        String color;

        public Product(String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }

        public String getProductID() {
            return productID;
        }
    }
}

我必须从每个getListCellRendererComponent调用中调用SwingWorker 接管图像操作?

SwingWorker甚至是解决此问题的正确工具吗?

我将如何更快地完成GUI的这一部分的任何帮助,将不胜感激。

编辑: 装满鳗鱼的气垫船提到,预加载图像可能会有所帮助,并且从渲染器加载图像根本上是错误的。

这使我想到另一个问题:

我有一个大约有3000个对象的列表(我们称之为list1),每个对象都有一个8kb jpg缩略图,该缩略图通过对象ID加载(也在渲染过程中) 列表同时显示这些缩略图的6至12个(由于列表的尺寸)

当用户选择一个对象时,他可以按按钮以显示原始问题中提到的Cardlayout及其对象的list(list2)中的Card2 以及与之相关的所有对象(在非缩略图视图中)(500kb png +大量图像操作)。现在,我认为预加载对象的非缩略图图像及其在第一个列表中选择的关系(大约为1-6张图像)是可行的。如果我正确理解了《气垫船充满鳗鱼》所说的话,那么在从list1中选择一个对象之后,我可以使用SwingWorker加载这些图像。

但是关于list1的约3000张图像呢,该程序似乎并没有减慢速度或变得没有响应,因为它们的尺寸很小,并且缩略图上没有繁琐的操作,但是它们仍然从list1的渲染器加载。预加载数千个缩略图是否有意义?

顺便说一句。随时告诉我是否不希望进行此类问题编辑,以及是否应将其本身作为一个问题。

2 个答案:

答案 0 :(得分:5)

一种方法可能是:

每当请求某个元素(产品)的单元格渲染器组件时,您都要检查是否已加载匹配的图像。如果不是,则启动一个Swing工作程序,该工作程序在后台加载和处理图像。工作程序完成后,会将图像放入缓存中以供以后查找。同时,您让渲染器只说"Loading..."或其他内容。

非常的快速实施方法在这里:

enter image description here

作为MCVE:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingWorker;

public class LazyImageLoadingCellRendererTest
{

    private JPanel mainPanel = new JPanel();
    private JList<Product> list = new JList<Product>();
    private JScrollPane scroll = new JScrollPane();

    public LazyImageLoadingCellRendererTest()
    {
        mainPanel.setBackground(new Color(129, 133, 142));

        scroll.setViewportView(list);
        scroll.setHorizontalScrollBarPolicy(
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scroll.setPreferredSize(new Dimension(80, 200));

        list.setCellRenderer(new LazyImageLoadingCellRenderer<Product>(list,
            LazyImageLoadingCellRendererTest::loadAndProcessImage));
        DefaultListModel<Product> model = new DefaultListModel<Product>();

        for (int i=0; i<1000; i++)
        {
            model.addElement(new Product("id" + i));
        }
        list.setModel(model);

        mainPanel.add(scroll);
    }

    public static void main(String[] args) throws IOException
    {

        EventQueue.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JFrame frame = new JFrame("WorkerTest");
                frame.setContentPane(
                    new LazyImageLoadingCellRendererTest().mainPanel);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLocation(300, 300);
                frame.setMinimumSize(new Dimension(160, 255));
                frame.setVisible(true);
            }
        });
    }

    private static final Random random = new Random(0);

    private static BufferedImage loadAndProcessImage(Product product)
    {
        String id = product.getProductID();
        int w = 100;
        int h = 20;
        BufferedImage image =
            new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 0, w, h);
        g.setColor(Color.BLACK);
        g.drawString(id, 10, 16);
        g.dispose();

        long delay = 500 + random.nextInt(3000);
        try
        {
            System.out.println("Load time of " + delay + " ms for " + id);
            Thread.sleep(delay);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return image;
    }

    class Product
    {
        String productID;

        public Product(String productID)
        {
            this.productID = productID;
        }

        public String getProductID()
        {
            return productID;
        }
    }

}

class LazyImageLoadingCellRenderer<T> extends JLabel
    implements ListCellRenderer<T>
{
    private final JList<?> owner;
    private final Function<? super T, ? extends BufferedImage> imageLookup;
    private final Set<T> pendingImages;
    private final Map<T, BufferedImage> loadedImages;

    public LazyImageLoadingCellRenderer(JList<?> owner,
        Function<? super T, ? extends BufferedImage> imageLookup)
    {
        this.owner = Objects.requireNonNull(
            owner, "The owner may not be null");
        this.imageLookup = Objects.requireNonNull(imageLookup,
            "The imageLookup may not be null");
        this.loadedImages = new ConcurrentHashMap<T, BufferedImage>();
        this.pendingImages =
            Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
        setOpaque(false);
    }

    class ImageLoadingWorker extends SwingWorker<BufferedImage, Void>
    {
        private final T element;

        ImageLoadingWorker(T element)
        {
            this.element = element;
            pendingImages.add(element);
        }

        @Override
        protected BufferedImage doInBackground() throws Exception
        {
            try
            {
                BufferedImage image = imageLookup.apply(element);
                loadedImages.put(element, image);
                pendingImages.remove(element);
                return image;
            }
            catch (Exception e)
            {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        protected void done()
        {
            owner.repaint();
        }
    }

    @Override
    public Component getListCellRendererComponent(JList<? extends T> list,
        T value, int index, boolean isSelected, boolean cellHasFocus)
    {
        BufferedImage image = loadedImages.get(value);
        if (image == null)
        {
            if (!pendingImages.contains(value))
            {
                //System.out.println("Execute for " + value);
                ImageLoadingWorker worker = new ImageLoadingWorker(value);
                worker.execute();
            }
            setText("Loading...");
            setIcon(null);
        }
        else
        {
            setText(null);
            setIcon(new ImageIcon(image));
        }
        return this;
    }
}

注意:

这实际上只是一个显示常规方法的简单示例。当然,可以在许多方面进行改进。尽管实际的加载过程已经提取到Function中(因此使它通常适用于任何类型的图像,而不管其来自何处),但有一个主要警告:将尝试加载所有图像。一个不错的扩展是在此处添加一些智能功能,并确保它仅 加载当前可见其单元格的图像。例如,当您有1000个元素的列表,并且想要查看最后10个元素时,则不必等待990个元素被加载。最后的元素应优先 higher 并首先加载。但是,为此,可能需要稍大的基础结构(主要是:自己的任务队列以及与列表及其滚动窗格的更牢固的连接)。 (我可能会解决这一天,因为它可能是一件很有趣的事情,但是在那之前,上面的示例可以做到这一点...)

答案 1 :(得分:2)

  

我必须从每个getListCellRendererComponent调用中调用SwingWorker来接管图像操作吗?

否,实际上您从不从关键渲染方法中调用后台线程。实际上,这似乎是上面代码的主要问题-您正在从渲染方法中读取图像,从而大大降低了程序的感知响应速度。

  

SwingWorker甚至是解决此问题的正确工具吗?

也许吧,但您在考虑使用它的地方却不是。 SwingWorker不会加快速度,但是通过在后台执行长时间运行的任务,可以避免阻塞Swing事件线程,从而冻结GUI。最好是一次读取图像,如果在程序启动期间未完成的话,也许可以在SwingWorker中读取它们,并将其保存到变量中。如果可以避免,请每次重新读取图像,以使其每次渲染。同样,不要从渲染代码中读取图像,因为这会大大降低程序的响应速度。