Android - Processes and Threads
原文:http://developer.android.com/guide/components/processes-and-threads.html
當一個 APP 元件啟動,而且沒有其他 APP 元件在執行,Android 系統會開啟一個新的 Single thread Linux process 給這個 APP 執行。
基本上,所有同一個 APP 的元件是在同一個 Thread 和 Process(叫做 “main” thread)上執行。如果一個 APP 元件啟動,而這個 APP 已經有 Thread 了(其他元件還在這個APP上執行),這時元件會啟動在同一個 Thread 上執行。
你也可以安排你 APP 上的不同的元件在獨立的 Process 上執行,你可以為任何 Process 建立額外的 Thread。
這個文件將討論 Process 和 Thread 在 Android 應用程式中如何運作。
Processes
如果你需要自己控制 Process,可以從 manifest 檔案來控制。
manifest 裡面有各種 元件的標籤 <activity>
, <service>
, <receiver>
, <provider>
,用 android:process
屬性可以更動元件執行的 Process。
你可以幫每個元件設定這個屬性讓他們執行在自己的 Process 中,你也可以設定 android:process
讓不同 APP 的元件執行在同一個 Process,讓他們共享同一個 Linux user ID 並且用同一個憑證註冊。
<application>
也支援 android:process
屬性,用來設定所有元件的預設值。
Android 決定在某些時候關閉 Process,當記憶體不足以供應其他使用者正在使用的 Process 需求時,APP 元件執行的 Process 會被殺掉。當重新執行這些元件時,會有新的 Process 給他們執行。
要決定殺掉某個 Process,Android 系統會衡量他們對於使用者的重要性,例如:會先殺掉用來服務某個不再顯示畫面的 Activity 會比看的到畫面的 Activity 容易被殺掉。
決定是否終止 Process 是由裡面執行的元件來決定,決定刪除哪個 Process 的規則會在下面討論。
Process lifecycle
Android 系統會試著讓 APP Process 盡可能的存活更久,但是最後還是會需要刪除舊的 Process 來回收記憶體給新的、更重要的 Process 使用。要決定哪個 Process 要留著,哪個 Process 要殺掉,系統基於元件的狀態給每個元件執行的 Process 一個 importance hierarchy
(重要性等級),低重要性的 Process 會先被刪掉,再來是第二低重要性的…等,來回收系統資源。
重要性有五個等級,以下介紹不同種類的 Process,依照重要性排列(第一個是最重要的,會最後殺掉):
Foreground process
(前景)
使用者正在操作的 Process,如果 Process 符合以下狀況就是 Foreground process:- 與使用者互動的 Activity(Activity 的 onResume()被呼叫)
- 與上面那種 Activity 結合的 Service
- 在前景執行的 Service-服務被 startForeground()呼叫
- 執行一個 Lifecycle Callbacks(onCreate(), onStart() 或 onDestory() )的 Service
- 正在執行 onReceive() 的 BroadcastReceiver
Visible process
(可見)
一個 Process 雖然沒有前景元件,但還是可以對使用者看到的畫面有作用。如果 Process 符合下列狀況就可以是 Visible process:- 執行的 Activity 雖然不在前景,但是使用者依然可以看到他( onPause() 方法被呼叫),舉例來說,如果一個前景 Activity 啟動一個對話框,還是能允許原本的 Activity 留在背後。
- 與上面那種 Activity 結合的 Service
Service process
(服務)
執行 Service 的 Process(呼叫 startService() 方法執行 Service),且不屬於上面兩種 Process 之一。雖然服務 Process 不會直接被使用者看到,他們通常做一些使用者關心的事情(像是在背景播放音樂或是從網路上下載資料),所以系統會讓他們保持執行,除非記憶體不足維持上面兩種 Process。Background process
(背景)
Process 保持一個現在使用者看不到的 Activity(Activity onStop() 方法被呼叫)。這種 Process 不會直接影響使用者經驗,系統可能會在任何時候殺掉他們來回收記憶體給前三種 Process 使用。
通常有許多背景 Process 在執行,所以他們被存在一個 LRU(least recently used 最近很少用)清單來刪除這些 Process 中使用者久沒用的 Process。
如果 Activity 正確的實現他的生命週期方法,且將目前狀態儲存下來,刪除這個 Process 將不會影響使用者經驗,因為當使用者回到這個 Activity 時,他會回覆所有狀態。更多關於儲存和回復狀態的文件請看Activities。Empty process
(空的)
Process 中沒有任何執行的元件,保留這種 Process 唯一的原因是為了作為快取,用來增加下次啟動這個元件的速度,系統通常會殺掉種 Process 來平衡整個系統資源。
Android 會盡可能的將 Process 的重要性等級評的較高,如果一個 Process 提供一個服務而且是可見的,他的 Process 評分會是可見 Process 而不是服務 Process。
Thread
當應用程式啟動,系統會建立一個 Thread 來給應用程式執行,稱為 “main”。這個 Thread 非常重要因為他負責指派事件給合適的 UI widgets 包含 drawing 事件。他也是應用程式從 Android UI toolkit(android.widget 和 android.view packages 的元件)和元件交流的 Thread。因此 main thread 有時候會呼叫 UI thread。
系統不會為每個元件的實例建立獨立的 Thread,所有的元件在同一個 Process 中執行,實做在 UI thread 中,這個 Thread 指派系統呼叫每個元件。因此回應 system callbacks 的方法總是在 UI thread 中執行(像是 onKeyDown() 這種回應使用者的動作或是生命週期 callback 方法)。
舉例來說,當一個使用者觸碰畫面上的按鈕,APP 的 UI thread 指派 touch event 給 widget,讓他將自己設定為按下的狀態並告訴 event queue 將請求作廢,UI thread 將請求移除以後再通知 widget 讓他重新繪製。
當你的 APP 密集的進行影響 UI 的工作,這個 Single thread 的模型將會變的效能很差,除非你正確的實現你的應用程式。
具體來說,如果每一件事都發生在 UI thread,像是從網路訪問請求資料庫這種長時間的操作,將會讓你目前的 UI 卡住。當這個 Thread 被卡住,就沒辦法指派事件,包含 drawing event,在使用者的觀點,應用程式看起來就像當掉了。更糟的是如果 UI thread 被卡住超過幾秒鐘(目前大約是5秒)使用者會看到這個應用程式沒有回應(ANR)的視窗問使用者要不要關掉 APP,使用者通常會決定關掉你的 APP 並且很生氣的把他移除。
另外 Android UI toolkit 並不是 Thread-safe。所以你不能在 Worker thread 操作 UI ,你必須在 UI thread 進行所有對 UI 的操作,因此對 Android 的 Single thread 模型有兩個簡單的規則:
- 不要讓你的 UI thread 卡住
- 不要從 UI thread 以外的地方存取 Android UI toolkit
Worker threads
因為 Single thread 模型像上面說的那樣,不讓 UI 卡住的去回應應用程式 UI 非常重要,如果你要進行一些不會即時反應的操作,你應該要確保讓這些事在別的 Thread 中執行(background
或 worker
thread)。
例如,下面有一些 click listener 的程式碼,要在一個獨立的 Thread 中下載一個影像並讓他顯示在 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();
}
乍看之下看起來可以順利執行,因為他建立一個新的 Thread 來負責網路操作,然而這違反了 Single thread 模型第二條規則:不要從 UI thread 以外的地方存取 Android UI toolkit。
這裡簡單的從 Worker thread 取代 UI thread 去修改 ImageView,這會造成未定義或是意外的行為的結果,如果出問題DEBUG的時候可能會難以追蹤又耗時。
要修正這個問題,Android 提出幾個方法來從其他 Thread 存取 UI thread:
例如你可以用 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();
}
這樣就變成 Thread-safe 了:網路操作在其他 Thread 結束,同時 ImageView 由 UI thread 操作。
然而如果有一組複雜的操作,這類的程式碼會很複雜而且難以維持,要處理更多與 Worker thread 複雜的互動,你可以考慮在你的 Worker thread 使用 Handler 來處理和 UI thread 之間的訊息傳遞。
或許有更好的解決方法,就是繼承 AsyncTask 這個類別,他可以簡單的處理 需要和 UI 互動的 Worker thread 任務。
Using AsyncTask
AsyncTask 允許你在 UI 做非同步的工作,他把會造成 Blocking 的操作放在 Worker thread,之後在發佈結果到 UI thread,這樣就不需要自己去掌握 thread 和 Handle。
要使用這個,你必須繼承 AsyncTask 並在子類別實現 doInBackground()
Callback 方法,這會在 Background thread 執行。要更新你的 UI,你需要實現 onPostExecute()
,這將會傳遞 doInBackground()
的結果並在 UI thread 執行,所以你可以安全的更新你的 UI,然後你就可以呼叫 execute()
在 UI thread 執行任務。
舉例來說,如果你用 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);
}
}
這樣你的程式碼就變的安全又簡單了,因為會把工作分別在 Worker thread 和 UI thread 上完成。
如果要瞭解如何使用這個類別,你可以閱讀 AsyncTask 的參考資料,這裡只會對他如何運作做一個簡單的概述:
- 參數的型別都可以自訂
- doInBackground() 方法會自動執行在 Worker thread
- onPreExecute(), onPostExecute() 和 onProgressUpdate() 都會在 UI thread 被呼叫
- doInBackground() 的回傳直會送給 onPostExecute()
- 你可以在 doInBackground() 中的任何地方呼叫 publishProgress() 來讓 onProgressUpdate() 在 UI thread 執行
- 你可以在任何時候、任何 Thread 取消任務
注意:另一個可能會遇到的問題是由於執行期的配置改變(像是使用者改變了畫面的方向),你使用的 Worker thread 在 Activity 可能會意外的重新啟動,可能會破壞你的 Worker thread。
要知道如何在重新啟動時保持留的任務,還有當 Activity 被消除時如何正確的取消工作,請看 Shelves 範例程式的原始碼。
Thread-safe methods
在某些情況下,你實現的方法可能會在不同的 Thread 中被呼叫,所以必須要寫成 Thread-safe 才行。
主要是讓方法可以被遠端呼叫,像是 Bound Services 中的方法。
TODO