默認情況下, 同一個application中的所有component運行在同一個linux進程下. 啟動一個component A時, 如果已存在處于運行狀態中的component B, 且A和B屬于同一個application, 那么component A將在component B所在的進程下運行. 否則將為A創建一個新的linux進程.
開發者也可以為application中的component指定不同的運行進程. manifest.xml文件中的, , , 標簽都支持android:PRocess屬性, 通過這個屬性, 可以為component指定運行的進程. 標簽也支持設定android:process屬性, 用于為application下的所有component指定默認的運行進程.
當系統的內存不足時, android系統將根據進程優先級選擇殺死一些不太重要的進程. 進程優先級從高到低分別為:
前臺進程. 以下的進程為前臺進程:a. 進程中包含處于前臺的正與用戶交互的activity;
b. 進程中包含與前臺activity綁定的service;
c. 進程中包含調用了startForeground()方法的service;
d. 進程中包含正在執行onCreate(), onStart(), 或onDestroy()方法的service;
e. 進程中包含正在執行onReceive()方法的BroadcastReceiver.
系統中前臺進程的數量很少, 前臺進程幾乎不會被殺死. 只有當內存低到無法保證所有的前臺進程同時運行時才會選擇殺死某個前臺進程.
可視進程. 以下進程為可視進程:a. 進程中包含未處于前臺但仍然可見的activity(調用了activity的onPause()方法, 但沒有調用onStop()方法). 典型的情況是運行activity時彈出對話框, 此時的activity雖然不是前臺activity, 但其仍然可見.
b. 進程中包含與可見activity綁定的service.
可視進程不會被系統殺死, 除非為了保證前臺進程的運行而不得已為之.
服務進程. 進程中包含已啟動的service.
后臺進程. 進程中包含不可見的activity(onStop()方法調用后的activity). 后臺進程不會直接影響用戶體驗, 為了保證前臺進程/可視進程/服務進程的運行, 系統隨時都有可能殺死一個后臺進程. 一個正確的實現了生命周期方法的activity處于后臺時被系統殺死, 可以在用戶重新啟動它時恢復之前的運行狀態.
空進程. 不包含任何處于活動狀態的進程是一個空進程. 系統經常殺死空進程, 這不會造成任何影響. 空進程存在的唯一理由是為了緩存一些啟動數據, 以便下次可以更快的啟動.
系統會賦予進程盡可能高的優先級. 例如一個進程既包含已啟動的service, 也包含前臺activity, 則這個進程會被視為前臺進程.
由于組件之間的依賴性, 進程的優先級有可能被提高. 假如進程A服務于進程B, 則進程A的優先級不能低于進程B. 比如, 進程A的ContentProvider組件正在服務于進程B的某個組件, 或者進程A的service組件和進程B的某個組件綁定等, 這些情況下, 進程A的優先級都不會低于進程B(如果按照優先級規則, 進程A的優先級確實低于進程B, 則系統會選擇提高進程A的優先級到和進程B相同).
由于服務進程的優先級高于后臺進程, 因此如果activity需要執行耗時操作, 最好還是啟動一個service來完成. 當然, 在activity中啟動子線程完成耗時操作也可以, 但是這樣做的缺點在于, 一旦activity不再可見, activity所在的進程成為后臺進程, 而內存不足時后臺進程隨時都有可能被系統殺死(但是啟動service完成耗時操作會帶來數據交互的問題, 比如耗時操作需要實時更新UI控件的狀態的話, service就不是一個好的選擇). 基于同樣的考慮, 在BroadcastReceiver中也不應該執行耗時操作, 而應該啟動service來完成(當然, BroadcastReceiver的生命周期過于短暫, 也決定了不能在其中執行耗時操作).
系統不會為進程中的每一個組件啟動一個新的線程, 進程中的所有組件都在UI線程中實例化.
永遠要記得:
不要阻塞UI線程. 如果在UI線程中執行阻塞或者耗時操作會導致UI線程無法響應用戶請求.
不能在非UI線程(也稱為工作線程)中更新UI, 這是因為android的UI控件都是線程不安全的.
由上所述, 開發者經常會啟動工作線程完成耗時操作或阻塞操作, 如果需要在工作線程的執行期間更新UI狀態, 則應該通知UI線程來進行.
請看下面的代碼:
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種線程間通信的方案:
調用以下方法:Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
如果在工作線程中調用了這3個方法, 那么方法中Runnable參數封裝的操作會在UI線程中執行.
使用這種方式可以修正例子中的錯誤之處:
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(); }Handler機制. Handler機制允許開發者在工作線程中調用與UI線程綁定的handler對象的sendMessage()方法向UI線程的消息隊列發送一條消息, UI線程會在適當的時候從消息隊列中取出消息并完成處理.
使用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線程中執行.
采用這種方式也可以修正例子中的錯誤之處:
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); } }耗時操作使用多線程, 耗時操作放在UI線程中會導致用戶的操作無法得到響應.
阻塞操作使用多線程, 理由同上.
多核CUP的設備使用多線程, 可以有效提高CPU的利用率.
并行操作使用多線程.
Looper類用來創建消息隊列. 每個線程最多只能有一個消息隊列, android中UI線程默認具有消息隊列, 但非UI線程在默認情況下是不具備消息隊列的. 如果需要在非UI線程中開啟消息隊列, 需要調用Looper.prepare()方法, 在該方法的執行過程中會創建一個Looper對象, 而Looper的構造函數中會創建一個MessageQueue instance(Looper的構造函數是私有的, 在Looper類之外無法創建其對象). 此后再為該線程綁定一個Handler instance, 然后調用Looper.loop()方法, 就可以不斷的從消息隊列中取出消息和處理消息了. Looper.myLoop()方法可以得到線程的Looper對象, 如果為null, 說明此時該線程尚未開啟消息隊列.
新聞熱點
疑難解答