[這篇文章是我對dev Guide中Processes and Threads的翻譯和總結, 也加上了一些個人理解]
android中的進程
預設情況下, 同一個application中的所有component運行在同一個linux進程下. 啟動一個component A時, 如果已存在處於運行狀態中的component B, 且A和B屬於同一個application, 那麼component A將在component B所在的進程下運行. 否則將為A建立一個新的linux進程.
開發人員也可以為application中的component指定不同的運行進程. manifest.xml檔案中的<activity>, <service>, <receiver>, <provider>標籤都支援android:process屬性, 通過這個屬性, 可以為component指定啟動並執行進程. <application>標籤也支援設定android:process屬性, 用於為application下的所有component指定預設的運行進程.
進程優先順序
當系統的記憶體不足時, android系統將根據進程優先順序選擇殺死一些不太重要的進程. 進程優先順序從高到低分別為:
1. 前台進程. 以下的進程為前台進程:
a. 進程中包含處於前台的正與使用者互動的activity;
b. 進程中包含與前台activity綁定的service;
c. 進程中包含調用了startForeground()方法的service;
d. 進程中包含正在執行onCreate(), onStart(), 或onDestroy()方法的service;
e. 進程中包含正在執行onReceive()方法的BroadcastReceiver.
系統中前台進程的數量很少, 前台進程幾乎不會被殺死. 只有當記憶體低到無法保證所有的前台進程同時運行時才會選擇殺死某個前台進程.
2. 可視進程. 以下進程為可視進程:
a. 進程中包含未處於前台但仍然可見的activity(調用了activity的onPause()方法, 但沒有調用onStop()方法). 典型的情況是運行activity時彈出對話方塊, 此時的activity雖然不是前台activity, 但其仍然可見.
b. 進程中包含與可見activity綁定的service.
可視進程不會被系統殺死, 除非為了保證前台進程的運行而不得已為之.
3. 服務進程. 進程中包含已啟動的service.
4. 後台進程. 進程中包含不可見的activity(onStop()方法調用後的activity). 後台進程不會直接影響使用者體驗, 為了保證前台進程/可視進程/服務進程的運行, 系統隨時都有可能殺死一個後台進程. 一個正確的實現了生命週期方法的activity處於後台時被系統殺死, 可以在使用者重新啟動它時恢複之前的運行狀態.
5. 空進程. 不包含任何處於活動狀態的進程是一個空進程. 系統經常殺死空進程, 這不會造成任何影響. 空進程存在的唯一理由是為了緩衝一些啟動資料, 以便下次可以更快的啟動.
進程優先順序的額外說明
1. 系統會賦予進程儘可能高的優先順序. 例如一個進程既包含已啟動的service, 也包含前台activity, 則這個進程會被視為前台進程.
2. 由於組件之間的依賴性, 進程的優先順序有可能被提高. 假如進程A服務於進程B, 則進程A的優先順序不能低於進程B. 比如, 進程A的ContentProvider組件正在服務於進程B的某個組件, 或者進程A的service組件和進程B的某個組件綁定等, 這些情況下, 進程A的優先順序都不會低於進程B(如果按照優先順序規則, 進程A的優先順序確實低於進程B, 則系統會選擇提高進程A的優先順序到和進程B相同).
3. 由於服務進程的優先順序高於後台進程, 因此如果activity需要執行耗時操作, 最好還是啟動一個service來完成. 當然, 在activity中啟動子線程完成耗時操作也可以, 但是這樣做的缺點在於, 一旦activity不再可見, activity所在的進程成為後台進程, 而記憶體不足時後台進程隨時都有可能被系統殺死(但是啟動service完成耗時操作會帶來資料互動的問題, 比如耗時操作需要即時更新UI控制項的狀態的話, service就不是一個好的選擇). 基於同樣的考慮, 在BroadcastReceiver中也不應該執行耗時操作,
而應該啟動service來完成(當然, BroadcastReceiver的生命週期過於短暫, 也決定了不能在其中執行耗時操作).
android中的線程
系統不會為進程中的每一個組件啟動一個新的線程, 進程中的所有組件都在UI線程中執行個體化. 關於android中的多線程機制, 請參考我的另一篇博文http://coolxing.iteye.com/blog/1208371
永遠要記得:
1. 不要阻塞UI線程. 如果在UI線程中執行阻塞或者耗時操作會導致UI線程無法響應使用者請求.
2. 不能在非UI線程(也稱為背景工作執行緒)中更新UI, 這是因為android的UI控制項都是線程不安全的.
由上所述, 開發人員經常會啟動背景工作執行緒完成耗時操作或阻塞操作, 如果需要在背景工作執行緒的執行期間更新UI狀態, 則應該通知UI線程來進行.
線程間通訊
請看下面的代碼:
Java代碼
- public void onClick(View v) {
- new Thread(new Runnable() {
- public void run() {
- Bitmap b = loadImageFromNetwork("http://example.com/image.png");
- mImageView.setImageBitmap(b);
- }
- }).start();
- }
上面的代碼是錯誤的, mImageView.setImageBitmap(b)違反了第二條準則--不能在背景工作執行緒中更新UI.
線程間通訊可以解決背景工作執行緒如何通知UI線程更新控制項的問題. android提供了3種線程間通訊的方案:
1. 調用以下方法:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
如果在背景工作執行緒中調用了這3個方法, 那麼方法中Runnable參數封裝的操作會在UI線程中執行.
使用這種方式可以修正例子中的錯誤之處:
Java代碼
- 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() {
- // run方法會在UI線程中執行
- public void run() {
- mImageView.setImageBitmap(bitmap);
- }
- });
- }
- }).start();
- }
2. Handler機制. Handler機制允許開發人員在背景工作執行緒中調用與UI線程綁定的handler對象的sendMessage()方法向UI線程的訊息佇列發送一條訊息, UI線程會在適當的時候從訊息佇列中取出訊息並完成處理.
3. 使用AsyncTask類. 建立一個AsyncTask類的子類, 並根據需要選擇覆寫onPreExecute(), doInBackground(), onProgressUpdate(), onPostExecute()方法.AsyncTask類的具體使用方法請參看文檔, 以下是一些大概的說明:
a. AsyncTask類是一個泛型類, 存在3個泛型參數. 第一個參數指定execute方法的參數類型, 第二個參數指定onProgressUpdate()方法的參數類型, 第三個參數指定 doInBackground()方法的傳回值類型以及onPostExecute()方法的參數類型.
b. 執行流程: 在UI線程中調用AsyncTask類的execute方法(只有該步驟是由程式員控制的)-->系統調用onPreExecute(), 這個方法在UI線程中執行-->系統調用doInBackground()方法, 這個方法在背景工作執行緒中執行-->在doInBackground()方法中每調用一次publishProgress()方法, 就會在UI線程中執行一次onProgressUpdate()方法-->doInBackground()方法執行完成後, 系統將調用 onPostExecute()方法,
並將doInBackground()方法的傳回值傳遞給 onPostExecute()方法的形參. onPostExecute()方法在UI線程中執行.
採用這種方式也可以修正例子中的錯誤之處:
Java代碼
- 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);
- }
- }