線程
應(yīng)用啟動時佳簸,系統(tǒng)會為應(yīng)用創(chuàng)建一個名為“主線程”的執(zhí)行線程滔韵。 此線程非常重要逻谦,因為它負(fù)責(zé)將事件分派給相應(yīng)的用戶界面小部件,其中包括繪圖事件奏属。 此外跨跨,它也是應(yīng)用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程。因此,主線程有時也稱為 UI 線程勇婴。
系統(tǒng)不會為每個組件實例創(chuàng)建單獨的線程忱嘹。運行于同一進程的所有組件均在 UI 線程中實例化,并且對每個組件的系統(tǒng)調(diào)用均由該線程進行分派耕渴。 因此颁股,響應(yīng)系統(tǒng)回調(diào)的方法(例如实柠,報告用戶操作的 onKeyDown() 或生命周期回調(diào)方法)始終在進程的 UI 線程中運行云矫。
例如抱冷,當(dāng)用戶觸摸屏幕上的按鈕時,應(yīng)用的 UI 線程會將觸摸事件分派給小部件添诉,而小部件反過來又設(shè)置其按下狀態(tài)屁桑,并將失效請求發(fā)布到事件隊列中。 UI 線程從隊列中取消該請求并通知小部件應(yīng)該重繪自身栏赴。
在應(yīng)用執(zhí)行繁重的任務(wù)以響應(yīng)用戶交互時蘑斧,除非正確實現(xiàn)應(yīng)用,否則這種單線程模式可能會導(dǎo)致性能低下须眷。 具體地講竖瘾,如果 UI 線程需要處理所有任務(wù),則執(zhí)行耗時很長的操作(例如花颗,網(wǎng)絡(luò)訪問或數(shù)據(jù)庫查詢)將會阻塞整個 UI捕传。 一旦線程被阻塞,將無法分派任何事件扩劝,包括繪圖事件庸论。 從用戶的角度來看,應(yīng)用顯示為掛起今野。 更糟糕的是葡公,如果 UI 線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘)罐农,用戶就會看到一個讓人厭煩的“應(yīng)用無響應(yīng)”(ANR) 對話框条霜。如果引起用戶不滿,他們可能就會決定退出并卸載此應(yīng)用涵亏。
此外宰睡,Android UI 工具包并非線程安全工具包。因此气筋,您不得通過工作線程操縱 UI拆内,而只能通過 UI 線程操縱用戶界面。 因此宠默,Android 的單線程模式必須遵守兩條規(guī)則:
- 不要阻塞 UI 線程
- 不要在 UI 線程之外訪問 Android UI 工具包
工作線程
根據(jù)上述單線程模式麸恍,要保證應(yīng)用 UI 的響應(yīng)能力,關(guān)鍵是不能阻塞 UI 線程。 如果執(zhí)行的操作不能很快完成抹沪,則應(yīng)確保它們在單獨的線程(“后臺”或“工作”線程)中運行刻肄。
例如,以下代碼演示了一個點擊偵聽器從單獨的線程下載圖像并將其顯示在 ImageView中:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();}
乍看起來融欧,這段代碼似乎運行良好敏弃,因為它創(chuàng)建了一個新線程來處理網(wǎng)絡(luò)操作。 但是噪馏,它違反了單線程模式的第二條規(guī)則:不要在 UI 線程之外訪問 Android UI 工具包 — 此示例從工作線程(而不是 UI 線程)修改了 ImageView麦到。 這可能導(dǎo)致出現(xiàn)不明確、不可預(yù)見的行為欠肾,但要跟蹤此行為困難而又費時瓶颠。
為解決此問題,Android 提供了幾種途徑來從其他線程訪問 UI 線程刺桃。 以下列出了幾種有用的方法:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
例如步清,您可以通過使用 View.post(Runnable) 方法修復(fù)上述代碼:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap =
loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();}
現(xiàn)在,上述實現(xiàn)屬于線程安全型:在單獨的線程中完成網(wǎng)絡(luò)操作虏肾,而在 UI 線程中操縱 ImageView廓啊。
但是,隨著操作日趨復(fù)雜封豪,這類代碼也會變得復(fù)雜且難以維護谴轮。 要通過工作線程處理更復(fù)雜的交互,可以考慮在工作線程中使用 Handler 處理來自 UI 線程的消息吹埠。當(dāng)然第步,最好的解決方案或許是擴展 AsyncTask 類,此類簡化了與 UI 進行交互所需執(zhí)行的工作線程任務(wù)缘琅。
使用 AsyncTask
AsyncTask 允許對用戶界面執(zhí)行異步操作粘都。 它會先阻塞工作線程中的操作,然后在 UI 線程中發(fā)布結(jié)果刷袍,而無需您親自處理線程和/或處理程序翩隧。
要使用它,必須創(chuàng)建 AsyncTask 的子類并實現(xiàn) doInBackground() 回調(diào)方法呻纹,該方法將在后臺線程池中運行堆生。 要更新 UI,應(yīng)該實現(xiàn) onPostExecute() 以傳遞 doInBackground() 返回的結(jié)果并在 UI 線程中運行雷酪,以便您安全地更新 UI淑仆。 稍后,您可以通過從 UI 線程調(diào)用 execute() 來運行任務(wù)哥力。
例如蔗怠,您可以通過以下方式使用 AsyncTask 來實現(xiàn)上述示例:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
現(xiàn)在 UI 是安全的,代碼也得到簡化,因為任務(wù)分解成了兩部分:一部分應(yīng)在工作線程內(nèi)完成寞射,另一部分應(yīng)在 UI 線程內(nèi)完成最住。
下面簡要概述了 AsyncTask 的工作方法,但要全面了解如何使用此類怠惶,您應(yīng)閱讀 AsyncTask 參考文檔:
可以使用泛型指定參數(shù)類型涨缚、進度值和任務(wù)最終值方法 doInBackground() 會在工作線程上自動執(zhí)行
onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調(diào)用doInBackground() 返回的值將發(fā)送到 onPostExecute()您可以隨時在 doInBackground() 中調(diào)用publishProgress()策治,以在 UI 線程中執(zhí)行 onProgressUpdate()您可以隨時取消任何線程中的任務(wù)脓魏。
注意:使用工作線程時可能會遇到另一個問題,即:運行時配置變更(例如通惫,用戶更改了屏幕方向)導(dǎo)致 Activity 意外重啟茂翔,這可能會銷毀工作線程。 要了解如何在這種重啟情況下堅持執(zhí)行任務(wù)履腋,以及如何在 Activity 被銷毀時正確地取消任務(wù)珊燎,請參閱書架示例應(yīng)用的源代碼。