2010-05-12 56 views
2

我正在使用依賴於嵌入式H2數據庫的swing應用程序。因爲我不想將數據庫與應用程序捆綁在一起(數據庫經常更新,我希望應用程序的新用戶以最近的副本開始),所以我已經實現了一個解決方案,它將數據庫的壓縮副本第一次啓動應用程序並提取它。由於提取過程可能很慢,因此我添加了一個ProgressMonitorInputStream以顯示提取過程的進度 - 不幸的是,當提取開始時,進度對話框顯示,但根本沒有更新。似乎事件正在進入事件調度線程。下面是方法:在Swing中使用ProgressMonitorInputStream監視壓縮文件解壓縮時未更新UI

public static String extractDbFromArchive(String pathToArchive) { 
    if (SwingUtilities.isEventDispatchThread()) { 
     System.out.println("Invoking on event dispatch thread"); 
    } 

    // Get the current path, where the database will be extracted 
    String currentPath = System.getProperty("user.home") + File.separator + ".spellbook" + File.separator; 
    LOGGER.info("Current path: " + currentPath); 

    try { 
     //Open the archive 
     FileInputStream archiveFileStream = new FileInputStream(pathToArchive); 
     // Read two bytes from the stream before it used by CBZip2InputStream 

     for (int i = 0; i < 2; i++) { 
      archiveFileStream.read(); 
     } 

     // Open the gzip file and open the output file 
     CBZip2InputStream bz2 = new CBZip2InputStream(new ProgressMonitorInputStream(
           null, 
           "Decompressing " + pathToArchive, 
           archiveFileStream)); 
     FileOutputStream out = new FileOutputStream(ARCHIVED_DB_NAME); 

     LOGGER.info("Decompressing the tar file..."); 
     // Transfer bytes from the compressed file to the output file 
     byte[] buffer = new byte[1024]; 
     int len; 
     while ((len = bz2.read(buffer)) > 0) { 
      out.write(buffer, 0, len); 
     } 

     // Close the file and stream 
     bz2.close(); 
     out.close(); 
    } catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    } catch (IOException ex) { 
     ex.printStackTrace(); 
    } 

    try { 
     TarInputStream tarInputStream = null; 
     TarEntry tarEntry; 
     tarInputStream = new TarInputStream(new ProgressMonitorInputStream(
           null, 
           "Extracting " + ARCHIVED_DB_NAME, 
           new FileInputStream(ARCHIVED_DB_NAME))); 

     tarEntry = tarInputStream.getNextEntry(); 

     byte[] buf1 = new byte[1024]; 

     LOGGER.info("Extracting tar file"); 

     while (tarEntry != null) { 
      //For each entry to be extracted 
      String entryName = currentPath + tarEntry.getName(); 
      entryName = entryName.replace('/', File.separatorChar); 
      entryName = entryName.replace('\\', File.separatorChar); 

      LOGGER.info("Extracting entry: " + entryName); 
      FileOutputStream fileOutputStream; 
      File newFile = new File(entryName); 
      if (tarEntry.isDirectory()) { 
       if (!newFile.mkdirs()) { 
        break; 
       } 
       tarEntry = tarInputStream.getNextEntry(); 
       continue; 
      } 

      fileOutputStream = new FileOutputStream(entryName); 
      int n; 
      while ((n = tarInputStream.read(buf1, 0, 1024)) > -1) { 
       fileOutputStream.write(buf1, 0, n); 
      } 

      fileOutputStream.close(); 
      tarEntry = tarInputStream.getNextEntry(); 

     } 
     tarInputStream.close(); 
    } catch (Exception e) { 
    } 

    currentPath += "db" + File.separator + DB_FILE_NAME; 

    if (!currentPath.isEmpty()) { 
     LOGGER.info("DB placed in : " + currentPath); 
    } 

    return currentPath; 
} 

這種方法獲取事件分派線程(SwingUtilities.isEventDispatchThread()返回true),使UI組件應更新上調用。我還沒有將它作爲SwingWorker實現,因爲在我可以繼續進行程序的初始化之前,我需要等待提取。在應用程序的主JFrame可見之前調用此方法。我不會不會基於SwingWorker +屬性的解決方案更改偵聽器 - 我認爲ProgressMonitorInputStream正是我需要的,但我想我沒有做正確的事情。我正在使用Sun JDK 1.6.18。任何幫助將不勝感激。

回答

4

當您在EDT上運行提取過程時,它將阻止GUI的所有更新,甚至是進度監視器。這正是SwingWorker將有助於的情況。

實際上,您正在通過霸佔EDT來阻止繪畫,以便進行數據庫提取。任何GUI更新請求(例如調用repaint())都將排隊,但實際上重新繪製這些更新永遠不會運行,因爲EDT已經很忙。

它可能工作的唯一方法是如果您將處理卸載到另一個線程。 SwingWorker更容易做到這一點。

+0

我認爲你在這裏說的是什麼,但我認爲Swing工程師可能記住輸入流通常是循環讀取的。如果循環完全佔用了EDT,那意味着這個ProgressMonitorInputStream總是沒用...... – 2010-05-12 09:45:11

+1

我明白你在說什麼。我也無法真正看到用例,因爲如果你在EDT以外的線程上使用它,它會在底層的JProgressBar上調用'setValue'(除此之外),這本身就是打破了Swing的規則。但是它並沒有改變現實,即如果你阻止了EDT,你的GUI不會更新。你不應該在美國東部時間做I/O。另外,我在PMIS上搜索的每個示例都使用主線程或者swing工作人員從流中進行實際讀取。 – Ash 2010-05-12 10:38:31

+0

我的問題是,我必須等待SwingWorker線程完成,然後才能使用init序列繼續。如果我在美國東部時間召集加入,情況將會和現在一樣糟糕。大多數情況下,當我使用監視器時,我有一些用戶按下的按鈕 - 這會啓動一個工作者線程,禁用一些UI元素,以便用戶強制等待線程完成。但是由於這種提取發生在沒有UI元素可見的地方,我不能這樣玩,我也不會引入不必要的對話來緩解這種情況。 – 2010-05-12 13:13:11

2

@Ash正確約爲SwingWorker。準備好後,您可以使用done()啓用其他GUI功能。在doInBackground()方法完成後,將在「事件調度線程上執行done()方法」。

+0

我知道這一點。我從昨天開始並沒有進行Swing開發:-)正如我在之前的評論中寫到的,我需要等待swing工作人員完成才能恢復EDT,因爲在我創建實體的問題中顯示的那段代碼之後經理使用下載的數據庫。而且沒有gui的功能,我可以禁用,這樣我就可以完成它們。我看到它的方式 - 如果在當前設置中沒有辦法同步 - 我不得不重做我的初始化邏輯只是爲了一個進度條... – 2010-05-12 15:05:34

+0

由於用戶必須等待初始化完成,顯示進度單獨的「飛濺」窗口。完成後,用您的GUI替換該窗口。 – trashgod 2010-05-12 19:12:05

+0

我從一開始就這樣做。我只是想在外面看到一些東西,因爲它們實際上並不是應用程序初始化的一部分,但實際上它是它的前奏。不管 - 我通過重構我的pre-init代碼來解決這個問題。感謝您的幫助。從我+1。 – 2010-05-12 19:31:11