【轉】Java SwingWorker(二)

前言:------------------------------
程序難免會使用到線程,在可視化Swing界面中大多數爲客戶端形式體現,其中界面流暢度就成了用戶體驗的影響之一,除了相應代碼整潔減少冗餘性及優化其中的算法和減少內存開銷等方式,將一些處理過程耗時的命令交由一個新的線程去處理,使Swing組件可以繼續接收用戶操作即變相提高流暢性(耗時操作應該儘量不在EDT中進行)。
博主找了幾篇較爲優秀文章可幫助理解其中的奧妙。

以下內容轉自 http://blog.sina.cn/dpool/blog/s/blog_4b6047bc010007th.html?md=gd

————————————————————————————————————

本節簡要介紹SwingWorker的功能。SwingWorker的定義如下:
public abstract class SwingWorker<T,V> extends Object implements RunnableFuture
SwingWorker是抽象類,因此必須繼承它才能執行所需的特定任務。注意該類有兩個類型參數:T及V。T是doInBackground和get方法的返回類型,V是publish和process方法要處理的數據類型。後文將作詳細解釋。 該類實現了java.util.concurrent.RunnableFuture接口。RunnableFuture接口是Runnable和Future兩個接口的簡單封裝。由於SwingWorker實現了Runnable接口,因此SwingWorker有一個run方法。Runnable對象一般作爲線程的一部分執行,當Thread對象啓動時,它激活Runnable對象的run方法。由於SwingWorker實現了Future接口,因此SwingWorker產生類型爲T的結果值並提供同線程交互的方法。SwingWorker實現以下接口方法:
* boolean cancel(boolean mayInterruptIfRunning)
* T get()
* T get(long timeout, TimeUnit unit)
* boolean isCancelled()
* boolean isDone()
SwingWorker實現了所有的接口方法,實際上你僅需要實現以下SwingWorker的抽象方法:
protected T doInBackground() throws Exception
doInBackground方法作爲任務線程的一部分執行,它負責完成線程的基本任務,並以返回值來作爲線程的執行結果。繼承類須覆蓋該方法並確保包含或代理任務線程的基本任務。不要直接調用該方法,應使用任務對象的execute方法來調度執行。
在獲得執行結果後應使用SwingWorker的 get方法獲取doInBackground方法的結果。可以在EDT上調用get方法,但該方法將一直處於阻塞狀態,直到任務線程完成。最好只有在知道結果時才調用get方法,這樣用戶便不用等待。爲防止阻塞,可以使用isDone方法來檢驗doInBackground是否完成。另外調用方法get(long timeout, TimeUnit unit)將會一直阻塞直到任務線程結束或超時。獲取任務結果的最好地方是在done方法內:
protected void done()
在doInBackground方法完成之後,SwingWorker調用done方法。如果任務需要在完成後使用線程結果更新GUI組件或者做些清理工作,可覆蓋done方法來完成它們。這兒是調用get方法的最好地方,因爲此時已知道線程任務完成了,SwingWorker在EDT上激活done方法,因此可以在此方法內安全地和任何GUI組件交互。
沒必要等到線程完成就可以獲得中間結果。中間結果是任務線程在產生最後結果之前就能產生的數據。當任務線程執行時,它可以發佈類型爲V的中間結果,覆蓋process方法來處理中間結果。後文還將提供這些方法的更多詳細信息。當屬性改變時,SwingWorker實例能通知處理器,SwingWorker有兩個重要的屬性:狀態和進程。任務線程有幾種狀態,以下面SwingWorker.StateValue枚舉值來表示:
* PENDING
* STARTED
* DONE
任務線程一創建就處於PENDING狀態,當doInBackground方法開始時,任務線程就進入STARTED狀態,當doInBackground方法完成後,任務線程就處於DONE狀態,隨着線程進入各個階段,SwingWorker超類自動設置這些狀態值。你可以添加處理器,當這些屬性發生變化來接收通知。
最後,任務對象有一個進度屬性,隨着任務進展時,可以將這個屬性從0更新到100標識任務進度,當該屬性發生變化時,任務通知處理器進行處理。
實現簡單的ImageRetriever
當點擊列表所略圖時,事件處理器創建了一個ImageRetriever實例並執行之。ImageRetriever下載選中的圖片並在列表下面展示它。當實現SwingWorker子類,須指定doInBackground和get方法返回值的類型。因爲ImageRetriever並不生成中間結果,它使用特殊類型Void作爲中間類型,ImageRetriever的任務的結果是一圖片,因此使用Icon類型作爲doInBackground和get方法的返回類型,下面代碼顯示了ImageRetriever的大部分實現: public class ImageRetriever extends SwingWorker<Icon, Void> {
private ImageRetriever() {}
public ImageRetriever(JLabel lblImage, String strImageUrl) {
this.strImageUrl = strImageUrl;
this.lblImage = lblImage;
}
@Override
protected Icon doInBackground() throws Exception {
Icon icon = retrieveImage(strImageUrl);
return icon;
}
private Icon retrieveImage(String strImageUrl)
throws MalformedURLException, IOException {
InputStream is = null;
URL imgUrl = null;
imgUrl = new URL(strImageUrl);
is = imgUrl.openStream();
ImageInputStream iis = ImageIO.createImageInputStream(is);
Iterator<ImageReader> it =
ImageIO.getImageReadersBySuffix('jpg');
ImageReader reader = it.next();
reader.setInput(iis); ...
Image image = reader.read(0);
Icon icon = new ImageIcon(image);
return icon;
}
@Override
protected void done() {
Icon icon = null;
String text = null;
try {
icon = get();
} catch (Exception ignore) {
ignore.printStackTrace();
text = 'Image unavailable';
}
lblImage.setIcon(icon);
lblImage.setText(text);
}
private String strImageUrl;
private JLabel lblImage;
} 因爲ImageRetriever類下載圖象並把它以label圖標的方式展現,因此爲了方便在其構造函數中要提供一個JLabel實例和圖象URL。ImageRetriever需要圖象URL來下載圖象,需要一個JLabel實例來展現下載的圖象,如果使用內部類實現任務線程ImageRetriever,由於可以直接訪問這些信息,你甚至不需要在構造函數中提供這些信息(圖象URL以及展現圖象的JLabel實例)。這樣做這些信息不會在ImageRetriever實例之間共享,所以更容易幫助程序實現線程安全。
注意ImageRetriever指定Icon作爲doInBackground和get方法的返回類型,因爲並不產生任何中間數據,所以指定Void類型作爲中間結果類型。
public class ImageRetriever extends SwingWorker<Icon, Void> 在該實現中,doInBackground方法必須遵循類協議,返回一個Icon類型的對象。通過在類定義指定Icon類型,編譯器強制doInBackground和get方法要返回Icon類型的值。不要覆蓋get方法,因爲該方法在SwingWorker中是一個final方法。
doInBackground方法從類構造函數中提供的URL中獲得圖象併產生一個Icon結果: @Override
protected Icon doInBackground() throws Exception {
Icon icon = retrieveImage(strImageUrl);
return icon;
} 當doInBackground方法完成後,SwingWorker在EDT上調用done方法,不要直接調用這個方法,因爲SwingWorker超類會調用這個方法。done方法獲得Icon結果並把它放到標籤label上。在本例中,lblImage引用通過ImageRetriever構造函數傳入進來。
@Override
protected void done() {
...
icon = get();
...
lblImage.setIcon(icon);
...
}
progress屬性值範圍是從0到100,當你在任務實例內處理這些信息時,你可以調用setProgress方法來更新這個屬性。當ImageRetriever通過ImageIO API下載圖象時,它調用setProgress方法來更新其progress屬性。下面的代碼展示如何使用IIOReadProgressListener實例來跟蹤ImageReader對象任務、下載圖象並更新progress屬性:
reader.addIIOReadProgressListener(new IIOReadProgressListener() { ... public void imageProgress(ImageReader source, float percentageDone) { setProgress((int) percentageDone); } public void imageComplete(ImageReader source) { setProgress(100); } }); 當任務屬性發生變化時,它通知處理器對象。在前面例子代碼中,當ImageRetriever類從ImageIO API那兒收到更新信息時調用setProgress方法並使用這些信息來設置自己的進度屬性,作爲結果,屬性變化處理器知道了當前下載了多少圖象數據。
圖7顯示ImageRetriever下載圖象完成後的結果:

使用SwingWorker之二X

圖7.SwingWorker線程在任務完成後更新進度條和標籤圖標
ImageRetriever演示了一個SwingWorker的簡單實現,在簡單實現中,只需覆蓋方法doInBackground就行。然而,因爲只有doInBackground方法完成之後,任務最後的結果才能獲得,所以你也應該覆蓋done方法。
SwingWorker在doInBackground方法完成之後激活done方法,所以應該從done方法中獲得任務結果,並且應該使用get方法獲得任務結果。通過在ImageRetriever構造函數提供這些UI組件,保證了能在任務線程的done方法中直接更新這些組件。前面的例子顯示瞭如何使用從Flickr站點獲取的圖象設置標籤label的圖標。參考ImageRetriever的完整實現了解如何從Flickr網站獲得圖像。
使用簡單ImageRetriever
現在已經創建了簡單的SwingWorker實現ImageRetriever,如何使用它呢?首先,要實例化,接着調用它的execute方法。當用戶從JList中選擇一個縮略圖,演示應用程序的MainFrame類就使用ImageRetriever任務線程。當用戶點擊一個縮略圖,列表選擇發生變化,產生一個列表選擇事件,listImageValueChanged方法是事件處理器。listImageValueChanged方法獲得選中的列表項,並生成相應縮略圖片對應的圖象的Flikr服務URL字符串,事件處理器接着調用retrieveImage方法,retrieveImage方法包含生成和使用ImageRetriever任務線程的重要代碼。


private void listImagesValueChanged(ListSelectionEvent evt) { 

...

 ImageInfo info = (ImageInfo) 

listImages.getSelectedValue(); 

String id = info.getId(); 

String server = info.getServer(); 

String secret = info.getSecret(); // No need to search an invalid thumbnail image 

if (id == null || server == null || secret == null) {

 return; 

String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id, secret); retrieveImage(strImageUrl);

 ...

 } 

private void retrieveImage(String imageUrl) { 

// SwingWorker objects can't be reused, so create a new one as needed. 

ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);

 progressSelectedImage.setValue(0);

 /* Listen for changes in the 'progress' property.  You can reuse the listener even though the worker thread .will be a new SwingWorker. */ 

imgRetriever.addPropertyChangeListener(listenerSelectedImage); 

progressSelectedImage.setIndeterminate(true);

  // Tell the worker thread to begin with this asynchronous method.

 imgRetriever.execute(); 

 //This event thread continues immediately here without blocking. */ 

} 注意每次下載新的圖像,retrieveImage方法都生成新的ImageRetriever實例。SwingWorker實例不可複用,每次執行任務必鬚生成新的實例。
正確使用SwingWorker的方法是實例化,如果需要獲知線程發生的狀態變化通知,則要添加屬性變化處理器,最後執行。execute方法是異步執行,它立即返回到調用者。在execute方法執行後,EDT立即繼續執行。圖6顯示這兩條線程之間的交互。
前面所講的retrieveImage方法生成一個ImageRetriever,並向該實例提供一個JLabel引用,任務線程使用它顯示圖象,retrieveImage方法和EDT不需要再訪問任務線程。這些代碼生成線程、執行、並根據需要繼續處理UI事件。
注意retrieveImage方法在執行線程之前往ImageRetriever實例添加屬性改變處理器,MainFrame類包含進度條跟蹤當前圖像下載的狀態,事件處理器會接收到所有SwingWorker線程事件的通知,其中一個事件是進度事件。
下面的事件處理器類響應進度事件,更新進度條。本文程序有兩個進度條,一個跟蹤搜索以及下載縮略圖進度,另一個跟蹤下載大圖片的進度。程序使用相同的ProgressListener類,不同實例,來跟蹤任務進度,因此需要在ProgressListener構造函數中傳要更新的進度條,下面是ProgressListener的代碼:
/** * ProgressListener listens to 'progress' property * changes in the SwingWorkers that search and load * images. */

 class ProgressListener implements PropertyChangeListener {

 // Prevent creation without providing a progress bar. private 

ProgressListener() {} 

ProgressListener(JProgressBar progressBar) { 

this.progressBar = progressBar; 

this.progressBar.setValue(0); 

}

 public void propertyChange(PropertyChangeEvent evt) { 

String strPropertyName = evt.getPropertyName(); 

if ('progress'.equals(strPropertyName)) { 

progressBar.setIndeterminate(false); 

int progress = (Integer)evt.getNewValue(); 

progressBar.setValue(progress); 

private JProgressBar progressBar; 

ImageRetriever類的使用是簡單的,給定一個圖像URL,它負責下載該圖像,向事件處理器提供進度信息來更新MainFrame類的進度條,你不需要知道關於創建和使用任務線程任何信息。只要一點努力,就可以從SwingWorker子類獲取更多效果。下一節將顯示如何實現並使用更復雜的任務子類。
(待續)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章