Android 的進程與線程總結(jié)
本文翻譯自Android官方文檔
當一個Android應用程序組件啟動時候,如果此時這個程序的其他組件沒有正在運行,那么系統(tǒng)會為這個程序以單一線程的形式啟動一個新的Linux 進程。 默認情況下,同一應用程序下的所有組件都運行再相同的進程和線程(一般稱為程序的“主”線程)中。如果一個應用組件啟動但這個應用的進程已經(jīng)存在了(因為這個應用的其他組件已經(jīng)在之前啟動了),那么這個組件將會在這個進程中啟動,同時在這個應用的主線程里面執(zhí)行。然而,你也可以讓你的應用里面的組件運行在 不同的進程里面,也可以為任何進程添加額外的線程。
這片文章討論了Android程序里面的進程和線程如何運作的。
進程
默認情況下,同一程序的所有組件都運行在相同的進程里面,大多數(shù)的應用都是這樣的。然而,如果你發(fā)現(xiàn)你需要讓你的程序里面的某個組件運行在特定的進程里面,你可以在manifest 文件里面設(shè)置。
manifest 文件里面為每一個組件元素—<activity>, <service>, <receiver>, 和<provider>—提供了 android:process 屬 性。通過設(shè)置這個屬性你可以讓組件運行在特定的進程中。你可以設(shè)置成每個組件運行在自己的進程中,也可以讓一些組件共享一個進程而其他的不這樣。你還可以 設(shè)置成不同應用的組件運行在同一個進程里面—這樣可以讓這些應用共享相同的Linux user ID同時被相同的證書所認證。
<application> 元素也支持 android:process 屬性,設(shè)置這個屬性可以讓這個應用里面的所有組件都默認繼承這個屬性。
Android 可能在系統(tǒng)剩余內(nèi)存較少,而其他直接服務用戶的進程又要申請內(nèi)存的時候shut down 一個進程, 這時這個進程里面的組件也會依次被kill掉。當這些組件有新的任務到達時,他們對應的進程又會被啟動。
在決定哪些進程需要被kill的時候,Android系統(tǒng)會權(quán)衡這些進程跟用戶相關(guān)的重要性。比如,相對于那些承載這可見的activities的 進程,系統(tǒng)會更容易的kill掉那些承載不再可見activities的進程。決定是否終結(jié)一個進程取決于這個進程里面的組件運行的狀態(tài)。下面我們會討論 kill進程時所用到的一些規(guī)則。
進程的生命周期
作為一個多任務的系統(tǒng),Android 當然系統(tǒng)能夠盡可能長的保留一個應用進程。但是由于新的或者更重要的進程需要更多的內(nèi)存,系統(tǒng)不得不逐漸終結(jié)老的進程來獲取內(nèi)存。為了聲明哪些進程需要保 留,哪些需要kill,系統(tǒng)根據(jù)這些進程里面的組件以及這些組件的狀態(tài)為每個進程生成了一個“重要性層級” 。處于***重要性層級的進程將會***時間被清楚,接著時重要性高一點,然后依此類推,根據(jù)系統(tǒng)需要來終結(jié)進程。
在這個重要性層級里面有5個等級。下面的列表按照重要性排序展示了不同類型的進程(***種進程是最重要的,因此將會在***被kill):
- Foreground 進程 一個正在和用戶進行交互的進程。 如果一個進程處于下面的狀態(tài)之一,那么我們可以把這個進程稱為 foreground 進程:
- 進程包含了一個與用戶交互的 Activity (這個 Activity的 onResume() 方法被調(diào)用)。
- 進程包含了一個綁定了與用戶交互的activity的 Service 。
- 進程包含了一個運行在”in the foreground”狀態(tài)的 Service —這個 service 調(diào)用了 startForeground()方法。
- 進程包含了一個正在運行的它的生命周期回調(diào)函數(shù) (onCreate(), onStart(), oronDestroy())的 Service 。
- 進程包含了一個正在運行 onReceive() 方法的 BroadcastReceiver 。
一般說來,任何時候,系統(tǒng)中只存在少數(shù)的 foreground 進程。 只有在系統(tǒng)內(nèi)存特別緊張以至于都無法繼續(xù)運行下去的時候,系統(tǒng)才會通過kill這些進程來緩解內(nèi)存壓力。在這樣的時候系統(tǒng)必須kill一些 (Generally, at that point, the device has reached a memory paging state,這句如何翻譯較好呢)foreground 進程來保證 用戶的交互有響應。
- Visible 進程 一個進程沒有任何 foreground 組件, 但是它還能影響屏幕上的顯示。 如果一個進程處于下面的狀態(tài)之一,那么我們可以把這個進程稱為 visible 進程:
- 進程包含了一個沒有在foreground 狀態(tài)的 Activity ,但是它仍然被用戶可見 (它的 onPause() 方法已經(jīng)被調(diào)用)。這種情況是有可能出現(xiàn)的,比如,一個 foreground activity 啟動了一個 dialog,這樣就會讓之前的 activity 在dialog的后面部分可見。
- 進程包含了一個綁定在一個visible(或者foreground)activity的 Service 。
一個 visible 進程在系統(tǒng)中是相當重要的,只有在為了讓所有的foreground 進程正常運行時才會考慮去kill visible 進程。
- Service 進程 一個包含著已經(jīng)以 startService() 方法啟動的 Service 的 進程,同時還沒有進入上面兩種更高級別的種類。盡管 service 進程沒有與任何用戶所看到的直接關(guān)聯(lián),但是它們經(jīng)常被用來做用戶在意的事情(比如在后臺播放音樂或者下載網(wǎng)絡數(shù)據(jù)),所以系統(tǒng)也只會在為了保證所有的 foreground and visible 進程正常運行時kill掉 service 進程。
- Background 進程 一個包含了已不可見的activity的 進程 (這個 activity 的 onStop() 已 經(jīng)被調(diào)用)。這樣的進程不會直接影響用戶的體驗,系統(tǒng)也可以為了foreground 、visible 或者 service 進程隨時kill掉它們。一般說來,系統(tǒng)中有許多的 background 進程在運行,所以將它們保持在一個LRU (least recently used)列表中可以確保用戶最近看到的activity 所屬的進程將會在***被kill。如果一個 activity 正確的實現(xiàn)了它的生命周期回調(diào)函數(shù),保存了自己的當前狀態(tài),那么kill這個activity所在的進程是不會對用戶在視覺上的體驗有影響的,因為當用戶 回退到這個 activity時,它的所有的可視狀態(tài)將會被恢復。查看 Activities 可以獲取更多如果保存和恢復狀態(tài)的文檔。
- Empty 進程 一個不包含任何活動的應用組件的進程。 這種進程存在的唯一理由就是緩存。為了提高一個組件的啟動的時間需要讓組件在這種進程里運行。為了平衡進程緩存和相關(guān)內(nèi)核緩存的系統(tǒng)資源,系統(tǒng)需要kill這些進程。
Android是根據(jù)進程中組件的重要性盡可能高的來評級的。比如,如果一個進程包含來一個 service 和一個可見 activity,那么這個進程將會被評為 visible 進程,而不是 service 進程。
另外,一個進程的評級可能會因為其他依附在它上面的進程而被提升—一個服務其他進程的進程永遠不會比它正在服務的進程評級低的。比如,如果進程A中 的一個 content provider 正在為進程B中的客戶端服務,或者如果進程A中的一個 service 綁定到進程B中的一個組件,進程A的評級會被系統(tǒng)認為至少比進程B要高。
因為進程里面運行著一個 service 的評級要比一個包含background activities的進程要高,所以當一個 activity 啟動長時操作時,***啟動一個 service 來 做這個操作,而不是簡單的創(chuàng)建一個worker線程—特別是當這個長時操作可能會拖垮這個activity。比如,一個需要上傳圖片到一個網(wǎng)站的 activity 應當開啟一個來執(zhí)行這個上傳操作。這樣的話,即使用戶離開來這個activity也能保證上傳動作在后臺繼續(xù)。使用 service 可以保證操作至少處于”service process” 這個優(yōu)先級,無論這個activity發(fā)生了什么。這也是為什么 broadcast receivers 應該使用 services 而不是簡單的將耗時的操作放到線程里面。
線程
當一個應用啟動的時候,系統(tǒng)會為它創(chuàng)建一個線程,稱為“主線程”。這個線程很重要因為它負責處理調(diào)度事件到相關(guān)的 user interface widgets,包括繪制事件。你的應用也是在這個線程里面與來自Android UI toolkit (包括來自 android.widget 和 android.view 包的組件)的組件進行交互。因此,這個主線程有時候也被稱為 UI 線程。
系統(tǒng)沒有為每個組件創(chuàng)建一個單獨的線程。同一進程里面的所有組件都是在UI 線程里面被實例化的,系統(tǒng)對每個組件的調(diào)用都是用過這個線程進行調(diào)度的。所以,響應系統(tǒng)調(diào)用的方法(比如 onKeyDown() 方法是用來捕捉用戶動作或者一個生命周期回調(diào)函數(shù))都運行在進程的UI 線程里面。
比如,當用戶點擊屏幕上的按鈕,你的應用的UI 線程會將這個點擊事件傳給 widget,接著這個widget設(shè)置它的按壓狀態(tài),然后發(fā)送一個失效的請求到事件隊列。這個UI 線程對請求進行出隊操作,然后處理(通知這個widget重新繪制自己)。
當你的應用與用戶交互對響應速度的要求比較高時,這個單線程模型可能會產(chǎn)生糟糕的效果(除非你很好的實現(xiàn)了你的應用)。特別是,當應用中所有的事情 都發(fā)生在UI 線程里面,那些訪問網(wǎng)絡數(shù)據(jù)和數(shù)據(jù)庫查詢等長時操作都會阻塞整個UI線程。當整個線程被阻塞時,所有事件都不能被傳遞,包括繪制事件。這在用戶看來,這個 應用假死了。甚至更糟糕的是,如果UI 線程被阻塞幾秒(當前是5秒)以上,系統(tǒng)將會彈出臭名昭著的 “application not responding” (ANR) 對話框。這時用戶可能選擇退出你的應用甚至卸載。
另外,Android的UI 線程不是線程安全的。所以你不能在一個worker 線程操作你的UI—你必須在UI線程上對你的UI進行操作。這有兩條簡單的關(guān)于Android單線程模型的規(guī)則:
- 不要阻塞 UI 線程
- 不要在非UI線程里訪問 Android UI toolkit
Worker 線程
由于上面對單一線程模型的描述,保證應用界面的及時響應同時UI線程不被阻塞變得很重要。如果你不能讓應用里面的操作短時被執(zhí)行玩,那么你應該確保把這些操作放到獨立的線程里(“background” or “worker” 線程)。
比如,下面這段代碼在一個額外的線程里面下載圖片并在一個 ImageView顯示:
- new Thread(new Runnable(){
- public void run(){
- Bitmap b = loadImageFromNetwork("http://example.com/image.png");
- mImageView.setImageBitmap(b);
- }
- }).start();}
起先這段代碼看起來不錯,因為它創(chuàng)建一個新的線程來處理網(wǎng)絡操作。然而,它違反來單一線程模型的第二條規(guī)則: 不在非UI線程里訪問 Android UI toolkit—這個例子在一個worker線程修改了 ImageView 。這會導致不可預期的結(jié)果,而且還難以調(diào)試。
為了修復這個問題,Android提供了幾個方法從非UI線程訪問Android UI toolkit 。詳見下面的這個列表:
那么,你可以使用 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();}
現(xiàn)在這個方案的線程安全的:這個網(wǎng)絡操作在獨立線程中完成后,UI線程便會對ImageView 進行操作。
然而,隨著操作復雜性的增長,代碼會變得越來越復雜,越來越難維護。為了用worker 線程處理更加復雜的交互,你可以考慮在worker線程中使用Handler ,用它來處理UI線程中的消息。也許***的方案就是繼承 AsyncTask 類,這個類簡化了需要同UI進行交互的worker線程任務的執(zhí)行。
使用 AsyncTask
AsyncTask 能讓你在UI上進行異步操作。它在一個worker線程里進行一些阻塞操作然后把結(jié)果交給UI主線程,在這個過程中不需要你對線程或者handler進行處理。
使用它,你必須繼承 AsyncTask 并實現(xiàn) doInBackground() 回調(diào)方法,這個方法運行在一個后臺線程池里面。如果你需要更新UI,那么你應該實現(xiàn)onPostExecute(),這個方法從 doInBackground() 取出結(jié)果,然后在 UI 線程里面運行,所以你可以安全的更新你的UI。你可以通過在UI線程調(diào)用 execute()方法來運行這個任務。
比如,你可以通過使用 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是安全的了,代碼也更加簡單了,因為AsyncTask把worker線程里做的事和UI線程里要做的事分開了。
你應該閱讀一下 AsyncTask
的參考文檔以便更好的使用它。下面就是一個對 AsyncTask
如何作用的快速的總覽:
- 你可以具體設(shè)置參數(shù)的類型,進度值,任務的終值,使用的范型
-
doInBackground()
方法自動在 worker 線程執(zhí)行 onPreExecute()
,onPostExecute()
, 和onProgressUpdate()
方法都是在UI線程被調(diào)用-
doInBackground()
的返回值會被送往onPostExecute()方法
- 你可以隨時在
doInBackground()
方法里面調(diào)用publishProgress()
方法來執(zhí)行UI 線程里面的onProgressUpdate()
方法 - 你可以從任何線程取消這個任務
注意: 你在使用worker線程的時候可能會碰到的另一個問題就是因為runtime configuration change (比如用戶改變了屏幕的方向)導致你的activity不可預期的重啟,這可能會kill掉你的worker線程。為了解決這個問題你可以參考 Shelves 這個項目。
線程安全的方法
在某些情況下,你實現(xiàn)的方法可能會被多個線程所調(diào)用,因此你必須把它寫出線程安全的。
文地址:Android的進程與線程