代码之家  ›  专栏  ›  技术社区  ›  brienna

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

  •  2
  • brienna  · 技术社区  · 7 年前

    我正在练习Swing,我编写了一个下载进度条,在用户按下“开始下载”按钮时下载一个图像。下载有效。问题是,在我的终端中,我可以看到相同的事件( propertyChange

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

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

    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();
            }
        }
    }
    

    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);
                }
            });
        }
    }
    

    编辑:我已经更新了代码以反映建议的更改。但即使做出了这些改变,问题仍然存在。我仍然看到多次调用“…100%完成”,每次后续下载调用的数量都在增加。例如,我第一次运行应用程序并按下下载按钮,我会看到

    ...100% completed
    

    我再次按下下载按钮。我懂了

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

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

    2 回复  |  直到 7 年前
        1
  •  5
  •   Community kfsone    4 年前

    由于百分比的计算方式,当还有一些工作要完成时,它可能会百分之百地报告

    在测试期间,我观察到。。。

    //...
    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 摇摆工

    /**
     * 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();
    }
    

    这样做的副作用是,你知道,当 state DONE

    已更新

    好的,在再次检查代码之后,我可以看到您正在添加一个新的 PropertyChangeListener model 每次执行 摇摆工

    /* 
     * 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;
    }
    

    因为 是的实例字段 Controller

    一种解决方案可能是只添加 Downloader 模型 ,但这需要您确保对UI执行的任何更新都正确同步。

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

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

    然后在 s done

    /*
     * Executed in event dispatching thread
     */
    @Override
    protected void done() {
        model.removeListener(this);
    
        2
  •  2
  •   trashgod    7 年前

    如图所示 here here SwingWorker state progress setProgress() 确保“ PropertyChangeListeners 在上异步通知 事件调度线程 “只需添加一个 PropertyChangeListener 转到进度条并呼叫 setProgress() 在您实施 doInBackground() download() 。方便地说,“出于性能目的,所有这些调用都合并为一个调用,只包含最后一个调用参数。”