Anthony
Anthony

Reputation: 87

Running the program in GUI is slow

I'm creating a program to scrape a website of images and place those image file names and properties on a list, the problem is, whenever I try to fetch the data from the URL using GUI, the program takes about 20-30 seconds to display the information on my table model, but when I run it without the GUI (just the console and simple system out println) it takes only 2-4 seconds, even faster at some point. here is my GUI code:

public class ImageDownloader extends JFrame {

private JPanel contentPane;
private JTextField urlTextField;
private JButton btnCheck;
private JButton btnDownload;
private JButton btnDownloadAll;
private JTable table;

private String imgUrl;
private String url;

Document document;
Elements media;

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                ImageDownloader frame = new ImageDownloader();
                frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

public ImageDownloader() {
    setTitle("Image Downloader");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setBounds(100, 100, 565, 300);
    contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    contentPane.setLayout(new BorderLayout(0, 0));
    setContentPane(contentPane);

    JPanel panel = new JPanel();
    FlowLayout flowLayout = (FlowLayout) panel.getLayout();
    flowLayout.setAlignment(FlowLayout.LEFT);
    contentPane.add(panel, BorderLayout.NORTH);

    JLabel lblWebsiteUrl = new JLabel("Website URL:");
    panel.add(lblWebsiteUrl);

    urlTextField = new JTextField();
    panel.add(urlTextField);
    urlTextField.setColumns(30);

    btnCheck = new JButton("Check");
    btnCheck.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            List<Images> images = new ArrayList<>();

            url = urlTextField.getText();

            if(url.isEmpty()) {
                JOptionPane.showMessageDialog(ImageDownloader.this, "Please enter a website URL", "Input Error", JOptionPane.ERROR_MESSAGE);
            } else {

                try {
                    document = Jsoup.connect(urlTextField.getText()).userAgent("Mozilla").timeout(10 * 1000).get();

                    media = document.select("[src]");

                    for(Element src : media) {
                        if(src.tagName().equals("img")) {
                            imgUrl = src.attr("abs:src");
                            URL url = new URL(imgUrl);
                            long size = url.openConnection().getContentLengthLong();
                            images.add(new Images(src.tagName(), src.attr("abs:src"), src.attr("width"), src.attr("height"), size));
                        }
                    }

                    ImageDownloaderTableModel tableModel = new ImageDownloaderTableModel(images);

                    table.setModel(tableModel);
                } catch (IOException e1) {
                    JOptionPane.showMessageDialog(ImageDownloader.this, "Error loading website, The site that you are trying to reach is either down or does not exist..", "Error Loading", JOptionPane.ERROR_MESSAGE);
                    e1.printStackTrace();
                }
            }
        }
    });
    panel.add(btnCheck);

    JPanel panel_1 = new JPanel();
    contentPane.add(panel_1, BorderLayout.SOUTH);

    btnDownloadAll = new JButton("Download All");
    btnDownloadAll.addActionListener(new ActionListener() {

        public void actionPerformed(ActionEvent arg0) {
            try {
                media = document.select("img");

                for(Element src : media) {
                    String strImgUrl = src.attr("abs:src");
                    downloadImage(strImgUrl);
                }
            } catch(Exception ex) {
                ex.printStackTrace();
            }
        }           
    });
    panel_1.add(btnDownloadAll);

    btnDownload = new JButton("Download");
    panel_1.add(btnDownload);

    JScrollPane scrollPane = new JScrollPane();
    contentPane.add(scrollPane, BorderLayout.CENTER);

    table = new JTable();
    scrollPane.setViewportView(table);
}

public static void downloadImage(String imgUrl) {
    String strImgUrl = imgUrl.substring(imgUrl.lastIndexOf("/") + 1);

    try {
        URL urlImage = new URL(imgUrl);
        InputStream in = urlImage.openStream();

        byte[] buffer = new byte[4096];
        int n = -1;

        OutputStream os = new FileOutputStream(strImgUrl);

        while((n = in.read(buffer)) != -1) {
            os.write(buffer, 0, n);
        }

        os.close();

        System.out.println("Saved..");
    } catch(IOException ex) {
        ex.printStackTrace();
    }
}
}

Upvotes: 0

Views: 821

Answers (2)

TT.
TT.

Reputation: 16137

The documentation of the SwingWorker class has an excellent recap of your situation:

Time-consuming tasks should not be run on the Event Dispatch Thread. Otherwise the application becomes unresponsive.

In your case downloading one or more files would be that time-consuming task, and you are downloading from the Event Dispatch Thread (aka EDT).

The SwingWorker class provides a solution to your problem:

SwingWorker is designed for situations where you need to have a long running task run in a background thread and provide updates to the UI either when done, or while processing.


I would also argue that action listeners (or actions) shouldn't contain long pieces of code. It is better to wrap that in a method and call the method from the action listener (action).

And in cases where you are opening a modal dialog (you're doing that with JOptionPane.showMessageDialog) I would invokeLater that method so that all pending UI messages get handled before you open that new dialog. That is general advice that will side-step other UI related problems (focus related problems come to mind).

If invokeLater is called from the event dispatching thread -- for example, from a JButton's ActionListener -- the doRun.run() will still be deferred until all pending events have been processed.

Upvotes: 3

Kanishka Ganguly
Kanishka Ganguly

Reputation: 1298

Well, the answer is quite simple and obvious. When you run your app in GUI mode, there is an extra thread for visualization and graphics and all those components you have like the JPanel or the JButton. These take up extra processing for the program and there are context-switches between your networking thread and your main GUI thread.

A console print, on the other hand, does not require the heavy graphics processing and can thus run much faster.

Also, AFAIK, networking is a blocking operation, i.e. everything else must wait before the network call is complete. Which is why the context shifts from the GUI thread to the network thread and back again.

Upvotes: 2

Related Questions