为什么我的下载进度条多次触发同一事件?

时间:2017-07-26 22:16:39

标签: java swing swingworker jprogressbar

我正在练习Swing,我编写了一个下载进度条,当用户按下"开始下载"按钮。下载工作。问题是在我的终端中,我可以看到同一事件(propertyChange)被多次触发,随着每次后续下载次数增加。我已经使用检查点调试了我的代码,但我仍然不确定为什么会发生这种情况。

更具体地说,在我的终端中,我看到了类似

的内容
...100% completed 
...100% completed 
...100% completed 
...100% completed 
...100% completed 
...100% completed 
...100% completed

当我希望看到" ... 100%完成"只有一次。 " ... 100%已完成"显示的内容随着每次下载而累积。我不确定这是否会影响我的下载性能,但我想知道它为什么会发生这种情况。

ProgressBar.java:

package download_progress_bar;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ProgressBar {
    private JFrame frame;
    private JPanel gui;
    private JButton button;
    private JProgressBar progressBar;

    public ProgressBar() {
        customizeFrame();
        createMainPanel();
        createProgressBar();
        createButton();
        addComponentsToFrame();
        frame.setVisible(true);
    }

    private void customizeFrame() {
        // Set the look and feel to the cross-platform look and feel
        try {
            UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
        } catch (Exception e) {
            System.err.println("Unsupported look and feel.");
            e.printStackTrace();
        }

        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
    }

    private void createMainPanel() {
        gui = new JPanel();
        gui.setLayout(new BorderLayout());
    }

    private void createProgressBar() {
        progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true);  // renders a progress string
    }

    private void createButton()  {
        button = new JButton("Start download");
    }

    private void addComponentsToFrame() {
        gui.add(progressBar, BorderLayout.CENTER);
        gui.add(button, BorderLayout.SOUTH);
        frame.add(gui);
        frame.pack();
    }

    // Add passed ActionListener to the button
    void addButtonListener(ActionListener listener) {
        button.addActionListener(listener);
    }

    // Get progress bar
    public JProgressBar getProgressBar() {
        return progressBar;
    }

    // Enable or disable button
    public void turnOnButton(boolean flip) {
        button.setEnabled(flip);
    }
}

Downloader.java:

package download_progress_bar;

import java.net.*;
import java.io.*;
import java.beans.*;

public class Downloader {
    private URL url;
    private int percentCompleted;
    private PropertyChangeSupport pcs;

    public Downloader() {
        pcs = new PropertyChangeSupport(this);
    }

    // Set URL object
    public void setURL(String src) throws MalformedURLException {
        url = new URL(src);
    }

    // Add passed PropertyChangeListener to pcs
    public void addListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }

    public void download() throws IOException {
        // Open connection on URL object
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        // Check response code (always do this first)
        int responseCode = connection.getResponseCode();
        System.out.println("response code: " + responseCode);
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // Open input stream from connection
            BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
            // Open output stream for file writing
            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("cat.jpg"));

            int totalBytesRead = 0;
            //int percentCompleted = 0;
            int i = -1;
            while ((i = in.read()) != -1) {
                out.write(i);
                totalBytesRead++;

                int old = percentCompleted;
                percentCompleted = (int)(((double)totalBytesRead / (double)connection.getContentLength()) * 100.0);
                pcs.firePropertyChange("downloading", old, percentCompleted);

                System.out.println(percentCompleted);  // makes download a bit slower, comment out for speed
            }

            // Close streams
            out.close();
            in.close();
        }
    }
}

Controller.java:

package download_progress_bar;

import java.util.concurrent.ExecutionException;
import javax.swing.*;
import java.awt.event.*;
import java.util.List;
import java.net.*;
import java.io.*;
import java.beans.*;

public class Controller {
    private ProgressBar view;
    private Downloader model;
    private JProgressBar progressBar;
    private SwingWorker<Void, Integer> worker;

    public Controller(ProgressBar theView, Downloader theModel) {
        view = theView;
        model = theModel;
        progressBar = view.getProgressBar();

        // Add button listener to the "Start Download" button
        view.addButtonListener(new ButtonListener());
    }

    class ButtonListener implements ActionListener {
        /**
         * Invoked when user clicks the button.
         */
        public void actionPerformed(ActionEvent evt) {
            view.turnOnButton(false);
            progressBar.setIndeterminate(true);
            // NOTE: Instances of javax.swing.SwingWorker are not reusable, 
            // so we create new instances as needed
            worker = new Worker();
            worker.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (evt.getPropertyName().equals("progress")) {
                        progressBar.setIndeterminate(false);
                        progressBar.setValue(worker.getProgress());
                    }
                }
            });
            worker.execute();
        }
    }

    class Worker extends SwingWorker<Void, Integer> implements PropertyChangeListener {
        /* 
         * Download task. Executed in worker thread.
         */
        @Override
        protected Void doInBackground() throws MalformedURLException {
            model.addListener(this);
            try {
                String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
                model.setURL(src);
                model.download();
            } catch (IOException ex) {
                System.out.println(ex);
                this.cancel(true);
            }   
            return null;
        }

        /*
         * Executed in event dispatching thread
         */
        @Override
        protected void done() {
            try {
                if (!isCancelled()) {
                    get();  // throws an exception if doInBackground throws one
                    System.out.println("File has been downloaded successfully!");
                }
            } catch (InterruptedException x) {
                x.printStackTrace();
                System.out.println("There was an error in downloading the file.");
            } catch (ExecutionException x) {
                x.printStackTrace();
                System.out.println("There was an error in downloading the file.");
            }

            view.turnOnButton(true);
        }

        /**
         * Invoked in the background thread of Downloader.
         */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            this.setProgress((int) evt.getNewValue());
            System.out.println("..." + this.getProgress() + "% completed");
        }
    }
}

Main.java:

package download_progress_bar;

import javax.swing.SwingUtilities;

/**
 * Runs the download progress bar application.
 */
public class Main {
    public static void main(String[] args) {
        // Schedule a job for the event-dispatching thread:
        // creating and showing this application's GUI.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // Create view
                ProgressBar view = new ProgressBar();
                // NOTE: Should model/controller be created outside invokeLater?
                // Create model
                Downloader model = new Downloader();
                // Create controller
                Controller controller = new Controller(view, model);
            }
        });
    }
}

编辑:我已更新我的代码以反映建议的更改。但即使在进行了更改之后,问题仍然存在。我仍然看到多次调用&#34; ... 100%已完成&#34;,随后的每次下载,调用次数都会增加。例如,我运行应用程序并第一次按下载按钮,我将看到

...100% completed

我再次按下载按钮。我明白了

...100% completed
...100% completed

我再次按下载按钮...

...100% completed
...100% completed
...100% completed

等等。为什么会这样?

2 个答案:

答案 0 :(得分:5)

可能的是,由于计算百分比的方式,当还有更多工作需要完成时,它将报告100%

在我的测试过程中,我观察到......

//...
98
...
99
99
...
100

因此在代码完成之前重复了很多值。

我注意到下载代码中存在一些问题/奇怪之处,主要是因为您完全忽略了percentCompleted属性,所以我将其更改为更像......

public void download() throws IOException {
    // Open connection on URL object
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    // Check response code (always do this first)
    int responseCode = connection.getResponseCode();
    System.out.println("response code: " + responseCode);
    if (responseCode == HttpURLConnection.HTTP_OK) {
        // Open input stream from connection
        BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
        // Open output stream for file writing
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("cat.jpg"));

        int totalBytesRead = 0;
        //int percentCompleted = 0;
        int i = -1;
        while ((i = in.read()) != -1) {
            out.write(i);
            totalBytesRead++;

            int old = percentCompleted;
            percentCompleted = (int) (((double) totalBytesRead / (double) connection.getContentLength()) * 100.0);
            pcs.firePropertyChange("downloading", old, percentCompleted);

            System.out.println(percentCompleted);  // makes download a bit slower, comment out for speed
        }

        // Close streams
        out.close();
        in.close();
    }
}

对我来说,我稍微改变了代码,而不是......

@Override
protected void process(List<Integer> chunks) {
    int percentCompleted = chunks.get(chunks.size() - 1); // only interested in the last value reported each time
    progressBar.setValue(percentCompleted);

    if (percentCompleted > 0) {
        progressBar.setIndeterminate(false);
        progressBar.setString(null);
    }
    System.out.println("..." + percentCompleted + "% completed");
}

/**
 * Invoked when a progress property of "downloading" is received.
 */
@Override
public void propertyChange(PropertyChangeEvent evt) {
    if (evt.getPropertyName().equals("downloading")) {
        publish((Integer) evt.getNewValue());
    }
}

您应该利用SwingWorker内置进度支持,例如......

/**
 * Invoked when a progress property of "downloading" is received.
 */
@Override
public void propertyChange(PropertyChangeEvent evt) {
    setProgress((int)evt.getNewValue());
}

这意味着您需要将PropertyChangeListener附加到SwingWorker

/**
 * Invoked when user clicks the button.
 */
public void actionPerformed(ActionEvent evt) {
    view.turnOnButton(false);
    progressBar.setIndeterminate(true);
    // NOTE: Instances of javax.swing.SwingWorker are not reusable, 
    // so we create new instances as needed
    worker = new Worker();
    worker.addPropertyChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("progress".equals(evt.getPropertyName())) {
                progressBar.setIndeterminate(false);
                progressBar.setValue(worker.getProgress());
            }
        }
    });
    worker.execute();
}

对此的副作用是,您知道有一种方法可以在SwingWorker state更改时收到通知,以检查DONE < / p>

更新

好的,在重新审核代码之后,我再次看到您在执行PropertyChangeListener

时每次向model添加新的SwingWorker
/* 
 * Download task. Executed in worker thread.
 */
@Override
protected Void doInBackground() throws MalformedURLException, InterruptedException {
    model.addListener(this); // Add another listener...
    try {
        String src = "https://lh3.googleusercontent.com/l6JAkhvfxbP61_FWN92j4ulDMXJNH3HT1DR6xrE7MtwW-2AxpZl_WLnBzTpWhCuYkbHihgBQ=s640-h400-e365";
        model.setURL(src);
        model.download();
    } catch (IOException ex) {
        System.out.println(ex);
        this.cancel(true);
    }
    return null;
}

因为modelController的实例字段,所以会产生累积效应。

一种解决方案可能是将Downloader添加为model的监听器,但这需要您确保对UI执行的任何更新都能正确同步。

更好,更通用的解决方案是在工作人员完成后添加支持以删除侦听器

public class Downloader {
    //...        
    public void removeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }

然后在SwingWorker s done方法中删除侦听器...

/*
 * Executed in event dispatching thread
 */
@Override
protected void done() {
    model.removeListener(this);

答案 1 :(得分:2)

herehere所示,SwingWorker维护了两个绑定属性:stateprogress。调用setProgress()可确保在事件调度线程上异步通知&#34; PropertyChangeListeners。&#34;只需在进度条中添加PropertyChangeListener,然后在setProgress()的实施中调用doInBackground(),或调用download()所调用的方法。方便地,&#34;出于性能目的,所有这些调用仅与最后一个调用参数合并为一个调用。&#34;