當應用程式啟動後,系統建立了一個叫做“main”的線程。主線程,也叫UI線程,非常重要,因為它負責分發事件給構件,包括繪製事件。也是這個線程,在這裡才能與Android UI工具包中的組件進行互動。
例如,當你觸控螢幕幕上的一個按鈕時,UI線程會分發一個觸摸事件給構件,然後,構件會設定自己為被按下的狀態,並拋出一個顯示無效的請求給事件隊列。UI線程隊列請求並通知構件繪製自己。
單執行緒模式會導致效能低下,除非你的程式很好地實現。特別是,當所有的操作都在單一的線程中進行,耗時的操作(如網路訪問、資料查詢)會阻塞UI。在耗時操作執行時,沒有任何事件可以分發,包括繪製的事件。從使用者的視覺來看,應用程式被掛起了。更糟糕的是,如果UI線程阻塞超過一定的時間(現在大約是5秒鐘),系統會給使用者呈現一個糟糕的“應用程式無響應”(ANR)對話方塊。
如果你想看這有多糟糕,你可以寫一個簡單的應用程式,在一個Button的OnClickListener函數中調用Thread.sleep(2000)。按鈕在回到它正常狀態之前,保持被按下的狀態2秒鐘。當這種情況發生時,使用者很容易認為應用程式慢。
總之,對於應用程式UI的響應性來說,保證UI線程不被阻塞是至關重要的。如果你有耗時的操作,你應該確保在另外的線程(後台或工作者線程)中執行。
下面有一個例子,點擊事件處理函數中,從網路上下載一個圖片,並顯示到ImageView上:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}
乍一看,這段代碼能很好的解決你的問題,因為它不會阻塞UI線程。遺憾的是,它違背了UI的單執行緒模式:Android UI工具包不是安全執行緒的,必須在UI線程中進行操作。在上面的程式碼片段裡,ImageView是在工作者線程中操作的,因此,這會引發可拍的問題。跟蹤和修正這些Bug可能是困難且耗時的。
Android提供了一些方法,能在其它線程中訪問UI線程。你可能對其中的一些已經很熟悉了,但這裡是一份較為全面的列表:
· Activity.runOnUiThread(Runnable)
· View.post(Runnable)
· View.postDelayed(Runnable, long)
· Handler
你可以使用這些類和方法中的任一來修正上面的例子代碼:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}
不幸的是,這些類和方法可能會使你的代碼變得更加複雜並難以閱讀。特別是,當你實現一個複雜的操作,而在這個操作中,需要頻繁地更新UI。
為瞭解決這個問題,Android 1.5和它之後的平台提供了一個通用的類——AsyncTask,其簡化了長時間運行任務的建立過程,而這些任務還能做到與UI進行互動。
在Android 1.0和1.1上,也有與AsyncTask類似的東西,叫做UserTask。它提供了相同的API,而你需要做的只是拷貝其中的代碼。
AsyncTask的目的是協助你管理線程。我們之前的例子可以很容易用AsyncTask進行改寫:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
如你所見,AsyncTask必須繼承使用。此外,還必須記住的是AsyncTask的執行個體必須在UI線程中建立並只能執行一次。你可以閱讀AsyncTask的文檔來全面瞭解如何使用,但這裡,只是簡要地描述它以及它的工作過程:
· 你可以使用泛型來指定參數、進度值和最終結果的類型
· doInBackground() 方法自動在工作者線程中執行
· onPreExecute(),onPostExecute()和onProgressUpdate()在主線程中調用
· doInBackground()中返回的值會發送給onPostExecute()方法
· 你可以在doInBackground()中隨時調用publishProgress()來執行onProgressUpdate()
· 你可以任何時候從任何線程中取消任務
除了官方的文檔,你還可以參考幾個複雜例子的原始碼,如Shelves(ShelvesActivity.java和AddBookActivity.java)和Photostream(LoginActivity,PhotostreamActivity.java和ViewPhotoActivity.java)。我們強烈地建議你閱讀Shelves的原始碼,來瞭解配置變更時任務的儲存以及Activity銷毀時如何正確地取消任務。
不管你是否使用AsyncTask,在單執行緒模式中始終要記住兩條法則:
1. 不要阻塞UI線程
2. 確保只在UI線程中訪問Android UI工具包