線程
應用程式啟動時,Android系統會給應用程式建立一個叫做“main”的執行線程。這個線程很重要,因為它負責給適當的使用者介面視窗調度事件,包括描畫事件。它也是你的應用程式與Android UI工具中的組件互動的線程(這些組件來自android.widget和android.view包)。如,“main”線程有時也叫UI線程。
系統不會給每個組件的執行個體建立一個單獨的線程。運行在同一個進程的所有的組件都被執行個體化在UI線程,並且系統會調用每個由線程調度的組件。因此響應系統回調的方法(如報告使用者動作的onKeyDown()方法或一個生命週期的回調方法)始終運行在這個進程的UI線程中。
例如,當使用者觸控螢幕幕上的一個按鈕時,你的應用程式的UI線程會調度對應視窗的touch事件,它會依次設定按鈕的按下狀態,並且把一個有效請求發送給這個事件隊列。UI線程會讓請求出隊並通知對應的視窗應該重繪自己。
當你的應用程式要在響應使用者的互動中執行密集的工作時,除非你正確的實現了應用程式,否則這種單線程模式會降低執行效率。尤其是把每件正在發生的事情都放在UI線程中,如執行像網路訪問、資料庫查詢這樣的長時操作將會使整個UI被阻塞。線程被阻塞時,任何事件都不能被調度,包括描畫事件。從使用者的角度,應用程式似乎被掛起了。甚至更糟,如果UI線程被阻塞幾秒鐘(大約是5秒鐘),使用者就會看到臭名昭著的“應用程式沒有響應”(ANR)對話方塊。那麼使用者就可能決定退出你的應用程式,並且如果他們感覺不好,還會卸載這個應用程式。
另外,Android UI 工具集不是安全執行緒的。因此你不能由一個背景工作執行緒來控制你的UI---你必須用UI線程來對你使用者介面進行所有的控制。這樣對於Android單線程模式,有以下兩個簡單的規則:
1. 不要阻塞UI線程;
2. 不要從UI線程的外部存取Android UI工具集。
背景工作執行緒
因為以上介紹的單線程模式的緣故,你的應用程式UI的響應不阻塞UI線程是至關重要的。如果你執行的不是瞬時操作,就應該用一個單獨的線程(後台或背景工作執行緒)來工作。
例如,一下click監聽器的代碼就是從一個單獨的線程中下載圖片,並在一個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();
}
初看以上這段代碼,似乎能夠很好的工作,因為它為處理網路操作建立了一個新的線程。但是,它違反了單線程模式的第二個規則:不從外部UI線程訪問Android UI工具集---這個樣本在背景工作執行緒中修改了ImageView對象而不是在UI線程中。這樣會導致未知的和不確定的異常行為,這對於尋找問題是困難和費時的。
要修正這個問題,Android提供了以下幾個方法用於從其他線程訪問UI線程:
1. Activity.runOnUiThread(Runnable )
2. View.post(Runnable )
3. View.postDelayed(Runnable, long)
例如,你能夠通過View.post(Runnable)方法來修改以上代碼:
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();
}
現在的這個實現是安全執行緒的,因為網路操作是在一個單獨的線程中完成的,而對ImageView對象的操作則在UI線程中完成。
但是操作的複雜性增加了,這類型的代碼使代碼的維護變的複雜和困難了。要處理跟背景工作執行緒更複雜的互動,你可能考慮在你的背景工作執行緒中使用Handler對象,來處理由UI線程發送的訊息。繼承AsyncTask類可能是最好的解決方案,它簡化了背景工作執行緒執行需要跟UI互動的任務。
使用AnsyncTask類
AsyncTask類允許你在使用者介面上執行非同步工作。它在背景工作執行緒中執行一個阻塞操作,然後在UI線程上發布執行結果,不要求你自己來處理線程和/或處理器。
要使用這個類,必須繼承AsyncTask類並且實現doInBackground()回調方法,它在背景線程池中運行。要更新UI,你應該實現onPostExecute()方法,它提供了來自doInBackground()方法的結果,並且在UI線程中運行,因此你能安全的更新你的UI。然後你能夠通過execute()方法從UI線程中運行任務。
例如,你能夠使用AsyncTask類的方法來實現前面的例子:
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);
}
}
現在的UI是安全的並且代碼也簡化了,因為把它把工作分為在背景工作執行緒中執行的部分和在UI線程中執行的部分。
要完全理解這個類應該讀AsyncTask參考,但是以下可以快速探索一下這個類的工作方式:
1. 你能夠指定常用類型的參數,如進度值、任務結束的值;
2. doInBackground()方法在背景工作執行緒上自動的執行;
3. onPreExecute()、onPostExecute()、和onProgressUpdate()方法都在UI線程上調用;
4. 由doInBackground()方法傳回值被發送給onPostExecute()方法
5. 你能夠在任何時候調用doInBackground()方法中的publishProgress()方法在UI線程上執行onProgressUpdate()方法;
6. 你能夠在任何時候取消來自任何線程的任務。
警告:當你使用背景工作執行緒的Activity由於運行時配置的改變而導致異常重啟(如使用者改變了螢幕的方向)時,你可能遇到另外一個問題:可能銷毀你的背景工作執行緒。要瞭解在Activity重啟期間如何保持你的背景工作和在Activity被銷毀時如何正確的取消任務等資訊,請看Shelves應用程式範例的原始碼。(http://code.google.com/p/shelves/)
安全執行緒的方法
在某些情況中,你實現的方法可能可能被多個線程調用,因此,必須要寫安全執行緒的方法。
安全執行緒主要是針對能夠被遠程調用的方法---如綁定類型Service中的方法。當IBinder實作類別中的一個方法調用源於與IBinder對象運行那個進程時,這個方法就會在調用者的線程中執行。但是,當調用源於另一個進程時,就會從系統維護的與IBinder對象相同的進程那個線程池中選擇執行這個方法的線程(不會在進程的UI線程中執行)。如,一個Service的onBind()方法將會被這個Service進程的主線程調用,由onBind()方法返回的對象中實現的方法(如實現RPC方法的一個子類)將會從線程池中調用。因為一個服務能夠有多個用戶端,多個線程能夠同時調用同一個IBinder對象方法,因此IBinder方法必須實現安全執行緒。
類似地,一個內容提供器能夠接受源自其他進程的資料申請。儘管ContentResolver類和ContentProvider類隱藏了如何管理進程處理序間通訊的細節,但是響應那些請求的ContentProvider方法(query()、insert()、delete()、update()和getType())是從內容提供器進程中的線程池調用的,而不是進程的UI線程。因為這些方法可能同時被多個線程調用,因此它們也必須實現安全執行緒。
處理序間通訊
Android使用遠端程序呼叫(RPCs)給處理序間通訊(IPC)提供了一種機制,在這種機制中一個被Activity或其他應用程式組件調用的方法,不是在本地執行,而是在遠程(其他進程)執行,它會給調用者返回一個結果。這就必需把一個方法分解為作業系統層面上能夠理解的調用和資料,並且把它從本地進程和地址空間中傳遞給遠程進程和它的地址空間,然後再在調用的地方重新組裝和執行。然後把傳回值反向傳輸回來。Android提供了所有的執行這些IPC傳輸的代碼,因此你能夠關注RPC編程介面的定義和實現。
要執行IPC,你的應用程式必須使用bindService()方法綁定一個Sevice。