【问题标题】:Java Swing Worker for Progress Bar - UI remains unresponsive for a long time进度条的 Java Swing Worker - UI 长时间无响应
【发布时间】:2015-10-27 16:57:47
【问题描述】:

我在 Windows 上使用 Java、Swing(窗口构建器)开发了一个应用程序。
单击按钮后,我的应用程序将转到另一个类(FileManager.java 文件)以计算输入文件夹中的文件总数(同时progressBar 将处于不确定模式)。一旦知道文件的数量,就会设置progressBar 最大值。

然后我调用convertToXLS(fileMgr) 来读取每个文件(1 kb)的内容,并在读取每个文件时更新progressBar

这里是它的代码:

public class xmlToXL {
            public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                xmlToXL window = new xmlToXL();
                window.frame.setVisible(true);
            }
        });
        private void initialize() {
            ...... some UI code ........
        btnConvertXmlTo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {    
                try {
                    preConvertToXLS();
                    Task task = new Task(folderPath.getText());
                    task.execute();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }// end of actionPerformed method
        }); // end of action listened

}//end of initialize

    public void preConvertToXLS() {    //method to set few UI properties
        btnConvertXmlTo.setEnabled(false);
        progressBar.setVisible(true);
        progressBar.setStringPainted(true);
        progressBar.setIndeterminate(true);
        progressBar.setString("Calculating Total number of files...");
        progressBar.setForeground(new Color(0, 102, 0));
    }

    ParserUtils parUtils = new ParserUtils(); //class to parse XML files (in another .java file)

    private void convertToXLS(FileManager fileMgr) {
        try {
            int i=1;
            parUtils.reset();
            progressBar.setValue(0);
            List<File> files = fileMgr.getFiles();
            for(File file : files) {
                progressBar.setString("Reading " + i+ " of " + fileMgr.getSize()+ " files");
                parUtils.parseFileUsingDOM(file); // This will read content of the input file 
                progressBar.setValue(i++);
            }
            btnConvertXmlTo.setEnabled(true);


        } catch (Exception e) {

        } 
    }

    class Task extends SwingWorker<Void, Void> {
        private FileManager fileMgr;

        public Task(String srcPath) {
            this.fileMgr = new FileManager(new File(srcPath));

        }

        /*
         * Main task. Executed in background thread.
         */
        @Override
        public Void doInBackground() {
            try {
                progressBar.setIndeterminate(true);
                fileMgr.readFiles();
                progressBar.setIndeterminate(false);
                progressBar.setMaximum(fileMgr.getSize());
                convertToXLS(fileMgr);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        /*
         * Executed in event dispatching thread
         */
        @Override
        public void done() {
            Toolkit.getDefaultToolkit().beep();
            try {
            progressBar.setString("FileRead Successful");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }//end of task class
}//end of My class

fileMgr.readFiles(); 之后,我的 UI 变得无响应。它需要一两分钟,有时是三分钟,然后执行convertToXLS(fileMgr)

FileManager.java

import XMLParsing.DetermineEncoding;

public class FileManager {

    public HashMap<String, ArrayList<String>> dirFiles = null;
    public ArrayList<String> dirNames = null;
    public int numberOfFiles;
    private File src;
    private List<File> files;

    public FileManager(File src) {
        this.src = src;
        dirNames = new ArrayList<String>();
        dirFiles = new HashMap<String, ArrayList<String>>();
        numberOfFiles = 0;
        files = new ArrayList<File>();
    }



    public int getSize() {
        return numberOfFiles;
    }

    public ArrayList<String> getDirectories(){
        return dirNames;
    }

    public List<File> getFiles() {
        Iterator it = dirFiles.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry) it.next();
            String folderName = (pair.getKey()).toString();
            ArrayList<String> FileNames = (ArrayList<String>) pair.getValue();
            if (FileNames != null) {
                for (String fileName : FileNames) {
                    if(replaceSelected(fileName)) {
                        File fXmlFile = new File(fileName);
                        files.add(fXmlFile);
                    }
                    else {
                    }
                }
            }
        }
        return files;
    }

    public void readFiles() throws IOException {
        readFiles(src);
    }

    private void readFiles(File folder) throws IOException {
        if (folder.isDirectory()) {
            ArrayList<String> fileNames = new ArrayList<String>();
            for (final File file : folder.listFiles()) {
                if (file.isDirectory()) {
                    readFiles(file);
                } else {
                    String fileName = (file.getPath()).toString();
                    if(fileName.toLowerCase().endsWith(".xml")) {
                        fileNames.add(file.getPath());
                        numberOfFiles = numberOfFiles + 1;
                        System.out.println(".");
                        if(!dirNames.contains(file.getParentFile().getName()))
                                dirNames.add(file.getParentFile().getName());
                    }
                }
            }
            dirFiles.put(folder.getName(), fileNames);
        }
    }

    private boolean replaceSelected(String filePath) {
        String line;
        String input = "";
        try {
            DetermineEncoding DE = new DetermineEncoding();
            String encoding = DE.getFileEncoding(filePath);
            InputStreamReader file = new InputStreamReader(new FileInputStream(
                    filePath), encoding);
            BufferedReader br = new BufferedReader(file);
            while ((line = br.readLine()) != null) {
                input += line.toString() + " ";
            }
            file.close();
            Writer out = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(filePath), "UTF-8"));
            out.append(input.trim());
            out.flush();
            out.close();
        } catch (Exception e) {
            return false;
        }
        return true;
    }

}

DetermineEncoding.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.mozilla.universalchardet.UniversalDetector;

public class DetermineEncoding {

    public DetermineEncoding() {
        // TODO Auto-generated constructor stub
    }

    public String getFileEncoding(String fileName) throws IOException {
        byte[] buf = new byte[4096];
        java.io.FileInputStream fis = new FileInputStream(fileName);
        UniversalDetector detector = new UniversalDetector(null);
        int nread;
        while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
          detector.handleData(buf, 0, nread);
        }
        detector.dataEnd();
        String encoding = detector.getDetectedCharset();
        if (encoding != null) {
          return encoding;
        } else {
          return "";
        }


    }

}

请帮我找出问题。

【问题讨论】:

  • 您不应该在事件调度线程的上下文之外修改 UI 组件的状态,在 doInBackground 方法内调用 progressBar.setIndeterminate(true); 是个坏主意
  • 好的,但是那是我的progressBar要更新的时候。我该怎么做 ?感谢您的回复
  • 看看这个example
  • 嗨,改了,还是不行。谢谢..
  • 考虑提供一个runnable example 来证明您的问题。这不是代码转储,而是您正在做的事情的一个例子,它突出了您遇到的问题。这将减少混乱并获得更好的响应

标签: java swing progress-bar swingworker


【解决方案1】:

基本问题是感知问题之一。您“认为” UI 没有响应,而实际上它只是在等待。

当您调用readFiles 时,它会检查您之前扫描的所有文件,读取它们然后再次将它们写出,而进度条处于“确定”模式,因此它不会显示任何内容.

您需要某种方式让FileManager 向您的工作人员提供有关其进度的更新,但工作人员需要通过许多其他方法,这些方法也必须提供进度通知。

这似乎暗示需要某种Observer Pattern,当发生变化时,程序的其他部分可以通知工作人员。

我们还需要以允许用户安全更新 UI 的方式完成所有这些操作

让我们从观察者开始...

public interface ProgressListener {
    public void progressChanged(double progress);
    public void setStatus(String text);
}

很简单,当状态发生变化时它会通知你,让正在收听的人在他们认为合适的时候进行更新。

基本进度值在 0-1 之间,这意味着侦听器实际上并不关心你有多少值,它只关心你的进度,这消除了尝试更新进度条关于它的最大值的需要相反,只需关注更新 0-100 之间的进度条的需要

现在我们需要在 API 的其余部分为它腾出空间

private void convertToXLS(FileManager fileMgr, ProgressListener listener) {
    try {
        int i = 1;
        listener.progressChanged(0d);
        List<File> files = fileMgr.getFiles(listener);
        for (File file : files) {
            listener.setStatus("Reading " + i + " of " + fileMgr.getSize() + " files");
            parUtils.parseFileUsingDOM(file); // This will read content of the input file 
            listener.progressChanged(i / (double) files.size());
        }
        btnConvertXmlTo.setEnabled(true);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

还有FileManager#getFiles....

public List<File> getFiles(ProgressListener listener) {
    Iterator it = dirFiles.entrySet().iterator();
    int count = dirFiles.size();
    for (Map.Entry<String, ArrayList<String>> entry : dirFiles.entrySet()){
        count += entry.getValue() == null ? 0 : entry.getValue().size();
    }
    int index = 0;
    listener.setStatus("Processing files...");
    while (it.hasNext()) {
        Map.Entry pair = (Map.Entry) it.next();
        String folderName = (pair.getKey()).toString();
        ArrayList<String> FileNames = (ArrayList<String>) pair.getValue();
        if (FileNames != null) {
            for (String fileName : FileNames) {
                if (replaceSelected(fileName)) {
                    File fXmlFile = new File(fileName);
                    files.add(fXmlFile);
                } else {
                }
                index++;
                listener.progressChanged(index / (double)count);
            }
        }
    }
    return files;
}

接下来,我们需要更新Task 以利用它的进度支持,我们还需要允许更改进度条的状态。

这我们可以通过publish/process方法,从后台线程发送消息到EDT。我们也可以稍微“作弊”并使用它发送消息以更改进度条的indeterminate 状态(仅供参考:您也可以使用属性更改侦听器支持来执行此操作,这可能是一种更简洁的方法)

class Task extends SwingWorker<Void, String> {

    protected   static final String INDETERMINATE_ON = "indeterminate.on";
    protected   static final String INDETERMINATE_OFF = "indeterminate.off";

    private FileManager fileMgr;

    public Task(String srcPath) {
        this.fileMgr = new FileManager(new File(srcPath));

    }

    @Override
    protected void process(List<String> chunks) {
        for (String text : chunks) {
            if (INDETERMINATE_OFF.equals(text)) {
                progressBar.setIndeterminate(false);
            } else if (INDETERMINATE_ON.equals(text)) {
                progressBar.setIndeterminate(true);
            } else {
                progressBar.setString(text);
            }
        }
    }

    /*
         * Main task. Executed in background thread.
     */
    @Override
    public Void doInBackground() {
        try {
            publish(INDETERMINATE_ON);
            fileMgr.readFiles();
            publish(INDETERMINATE_OFF);
            convertToXLS(fileMgr, new ProgressListener() {
                @Override
                public void progressChanged(double progress) {
                    setProgress((int) (progress * 100d));
                }

                @Override
                public void setStatus(String text) {
                    publish(text);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*
         * Executed in event dispatching thread
     */
    @Override
    public void done() {
        Toolkit.getDefaultToolkit().beep();
        try {
            progressBar.setString("FileRead Successful");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}//end of task class

最后,我们需要在创建Task的时候添加一个PropertyChangeListener,这样我们就可以得到progress的更新和更新进度条...

task.addPropertyChangeListener(new PropertyChangeListener() {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        String name = evt.getPropertyName();
        switch (name) {
            case "progress":
                int value = (int) evt.getNewValue();
                progressBar.setValue(value);
                break;
        }
    }
});

简单:P

【讨论】:

    【解决方案2】:

    代码看起来不错。我似乎唯一无法检查的是 FileManager。使用 FileReader,它在一个单独的线程中运行,允许同时进行用户操作。所以我猜肯定是 FileManager 引起了问题。

    【讨论】:

    • 你能帮我理解在哪里添加这部分代码吗?此外,Task 扩展了 SwingWorker 类(只是确保你没有错过)
    • 所以你说 FileManager 正在进入一个新线程?那我该怎么办呢?我完全不知道如何处理这个问题。非常感谢任何帮助。
    • 可以添加文件管理器代码吗?也许您可以尝试使用 FileReader 读取一个文件,看看是否可行。
    • 要尝试的另一件事是将文件传递给您的任务,然后从任务中创建并运行 FileManager。 FileManager 是在 UI 线程中创建的,只有读取调用是在 Task 中完成的。这可能是问题所在。
    • 代码不是“好”,它违反了 Swing 的单线程规则,从 EDT 的上下文之外修改 UI:P
    猜你喜欢
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多