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

bri*_*akh 1 java swing swingworker jprogressbar

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

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

...100% completed 
...100% completed 
...100% completed 
...100% completed 
...100% completed 
...100% completed 
...100% completed
Run Code Online (Sandbox Code Playgroud)

当我希望看到"...... 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

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

...100% completed
Run Code Online (Sandbox Code Playgroud)

我再次按下载按钮.我知道了

...100% completed
...100% completed
Run Code Online (Sandbox Code Playgroud)

我再次按下载按钮......

...100% completed
...100% completed
...100% completed
Run Code Online (Sandbox Code Playgroud)

等等.为什么会这样?

Mad*_*mer 5

有可能的是,由于计算百分比的方式,当仍有一些工作需要完成时它会报告100%

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

//...
98
...
99
99
...
100
Run Code Online (Sandbox Code Playgroud)

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

我注意到你的下载代码中存在一些问题/奇怪之处,主要是因为你完全忽略了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();
    }
}
Run Code Online (Sandbox Code Playgroud)

对我来说,我会略微改变代码,而不是......

@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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

这将意味着您将需要一个连接PropertyChangeListenerSwingWorker

/**
 * 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();
}
Run Code Online (Sandbox Code Playgroud)

副作用这是,你知道有手段也被通知时SwingWorkerstate变化来检查时看到它是DONE

更新

好了,去了的代码,再以后,我可以看到你添加一个新的PropertyChangeListenermodel,你执行了EVERY TIMESwingWorker

/* 
 * 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;
}
Run Code Online (Sandbox Code Playgroud)

因为它model是一个实例字段Controller,所以它具有累积效应.

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

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

public class Downloader {
    //...        
    public void removeListener(PropertyChangeListener listener) {
        pcs.removePropertyChangeListener(listener);
    }
Run Code Online (Sandbox Code Playgroud)

然后在SwingWorkers done方法中,删除监听器...

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