編寫:AllenZheng1991 - 原文:http://developer.android.com/training/multiple-threads/communicate-ui.html
在前面的課程中你學習瞭如何在一個被ThreadPoolExecutor管理的線程中開啟一個任務。最後這一節課將會向你展示如何從執行的任務中發送數據給運行在UI線程中的對象。這個功能允許你的任務可以做後臺工作,然後把得到的結果數據轉移給UI元素使用,例如位圖數據。
任何一個APP都有自己特定的一個線程用來運行UI對象,比如View對象,這個線程我們稱之為UI線程。只有運行在UI線程中的對象能訪問運行在其它線程中的對象。因為你的任務執行的線程來自一個線程池而不是執行在UI線程,所以他們不能訪問UI對象。為了把數據從一個後臺線程轉移到UI線程,需要使用一個運行在UI線程裡的Handler。
##在UI線程中定義一個Handler
Handler屬於Android系統的線程管理框架的一部分。一個Handler對象用於接收消息和執行處理消息的代碼。一般情況下,如果你為一個新線程創建了一個Handler,你還需要創建一個Handler,讓它與一個已經存在的線程關聯,用於這兩個線程之間的通信。如果你把一個Handler關聯到UI線程,處理消息的代碼就會在UI線程中執行。
你可以在一個用於創建你的線程池的類的構造方法中實例化一個Handler對象,並把它定義為全局變量,然後通過使用Handler (Looper) 這一構造方法實例化它,用於關聯到UI線程。Handler(Looper)這一構造方法需要傳入了一個Looper對象,它是Android系統的線程管理框架中的另一部分。當你在一個特定的Looper實例的基礎上去實例化一個Handler時,這個Handler與Looper運行在同一個線程裡。例如:
private PhotoManager() {
...
// Defines a Handler object that's attached to the UI thread
mHandler = new Handler(Looper.getMainLooper()) {
...
在這個Handler裡需要重寫handleMessage()方法。當這個Handler接收到由另外一個線程管理的Handler發送過來的新消息時,Android系統會自動調用這個方法,而所有線程對應的Handler都會收到相同信息。例如:
/*
* handleMessage() defines the operations to perform when
* the Handler receives a new Message to process.
*/
@Override
public void handleMessage(Message inputMessage) {
// Gets the image task from the incoming Message object.
PhotoTask photoTask = (PhotoTask) inputMessage.obj;
...
}
...
}
}
下一部分將向你展示如何用Handler轉移數據。
##把數據從一個任務中轉移到UI線程
為了從一個運行在後臺線程的任務對象中轉移數據到UI線程中的一個對象,首先需要存儲任務對象中的數據和UI對象的引用;接下來傳遞任務對象和狀態碼給實例化Handler的那個對象。在這個對象裡,發送一個包含任務對象和狀態的Message給Handler也運行在UI線程中,所以它可以把數據轉移到UI線程。
###在任務對象中存儲數據
比如這裡有一個Runnable,它運行在一個編碼了一個Bitmap且存儲這個Bitmap到父類PhotoTask對象裡的後臺線程。這個Runnable同樣也存儲了狀態碼DECODE_STATE_COMPLETED。
// A class that decodes photo files into Bitmaps
class PhotoDecodeRunnable implements Runnable {
...
PhotoDecodeRunnable(PhotoTask downloadTask) {
mPhotoTask = downloadTask;
}
...
// Gets the downloaded byte array
byte[] imageBuffer = mPhotoTask.getByteBuffer();
...
// Runs the code for this task
public void run() {
...
// Tries to decode the image buffer
returnBitmap = BitmapFactory.decodeByteArray(
imageBuffer,
0,
imageBuffer.length,
bitmapOptions
);
...
// Sets the ImageView Bitmap
mPhotoTask.setImage(returnBitmap);
// Reports a status of "completed"
mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);
...
}
...
}
...
PhotoTask類還包含一個用於給ImageView顯示Bitmap的handler。雖然Bitmap和ImageViewImageView的引用在同一個對象中,但你不能把這個Bitmap分配給ImageView去顯示,因為它們並沒有運行在UI線程中。
這時,下一步應該發送這個狀態給PhotoTask
對象。
###發送狀態取決於對象層次
PhotoTask是下一個層次更高的對象,它包含將要展示數據的編碼數據和View對象的引用。它會收到一個來自PhotoDecodeRunnable的狀態碼,並把這個狀態碼單獨傳遞到一個包含線程池和Handler實例的對象:
public class PhotoTask {
...
// Gets a handle to the object that creates the thread pools
sPhotoManager = PhotoManager.getInstance();
...
public void handleDecodeState(int state) {
int outState;
// Converts the decode state to the overall state.
switch(state) {
case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
outState = PhotoManager.TASK_COMPLETE;
break;
...
}
...
// Calls the generalized state method
handleState(outState);
}
...
// Passes the state to PhotoManager
void handleState(int state) {
/*
* Passes a handle to this task and the
* current state to the class that created
* the thread pools
*/
sPhotoManager.handleState(this, state);
}
...
}
###轉移數據到UI 從PhotoTask對象那裡,PhotoManager對象收到了一個狀態碼和一個PhotoTask對象的handler。因為狀態碼是TASK_COMPLETE,所以創建一個Message應該包含狀態和任務對象,然後把它發送給Handler:
public class PhotoManager {
...
// Handle status messages from tasks
public void handleState(PhotoTask photoTask, int state) {
switch (state) {
...
// The task finished downloading and decoding the image
case TASK_COMPLETE:
/*
* Creates a message for the Handler
* with the state and the task object
*/
Message completeMessage =
mHandler.obtainMessage(state, photoTask);
completeMessage.sendToTarget();
break;
...
}
...
}
最終,Handler.handleMessage()會檢查每個傳入進來的Message,如果狀態碼是TASK_COMPLETE,這時任務就完成了,而傳入的Message裡的PhotoTask對象裡同時包含一個Bitmap和一個ImageView。因為Handler.handleMessage()運行在UI線程裡,所以它能安全地轉移Bitmap數據給ImageView:
private PhotoManager() {
...
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message inputMessage) {
// Gets the task from the incoming Message object.
PhotoTask photoTask = (PhotoTask) inputMessage.obj;
// Gets the ImageView for this task
PhotoView localView = photoTask.getPhotoView();
...
switch (inputMessage.what) {
...
// The decoding is done
case TASK_COMPLETE:
/*
* Moves the Bitmap from the task
* to the View
*/
localView.setImageBitmap(photoTask.getImage());
break;
...
default:
/*
* Pass along other messages from the UI
*/
super.handleMessage(inputMessage);
}
...
}
...
}
...
}
...
}