詳解Android進程和線程
寫在前面的話
一個Android應用就是一個Linux進程,每個應用在各自的進程中運行,互不干擾,比較安全。
一個應用對應一個主線程,就是通常所說的UI線程,android遵守的就是單線程模型,所以說Ui操作不是線程安全的并且這些操作必須在UI線程中執(zhí)行。
本文是對官方文檔的翻譯,原文鏈接:https://developer.android.com/guide/components/processes-and-threads.html
概述
當某個應用組件啟動且該應用沒有運行其他任何組件時,Android 系統(tǒng)會使用單個執(zhí)行線程為應用啟動新的 Linux 進程。默認情況下,同一應用的所有組件在相同的進程和線程(稱為“主”線程)中運行。 如果某個應用組件啟動且該應用已存在進程(因為存在該應用的其他組件),則該組件會在此進程內(nèi)啟動并使用相同的執(zhí)行線程。 但是,您可以安排應用中的其他組件在單獨的進程中運行,并為任何進程創(chuàng)建額外的線程。
本文檔介紹進程和線程在 Android 應用中的工作方式。
一、進程
默認情況下,同一應用的所有組件均在相同的進程中運行,且大多數(shù)應用都不會改變這一點。 但是,如果您發(fā)現(xiàn)需要控制某個組件所屬的進程,則可在清單文件中執(zhí)行此操作。
各類組件元素的清單文件條目—、、 和 —均支持 android:process 屬性,此屬性可以指定該組件應在哪個進程運行。您可以設置此屬性,使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。 此外,您還可以設置 android:process,使不同應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 并使用相同的證書進行簽署。
此外, 元素還支持 android:process 屬性,以設置適用于所有組件的默認值。
如果內(nèi)存不足,而其他為用戶提供更緊急服務的進程又需要內(nèi)存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷毀。 當這些組件需要再次運行時,系統(tǒng)將為它們重啟進程。
決定終止哪個進程時,Android 系統(tǒng)將權衡它們對用戶的相對重要程度。例如,相對于托管可見 Activity 的進程而言,它更有可能關閉托管屏幕上不再可見的 Activity 進程。 因此,是否終止某個進程的決定取決于該進程中所運行組件的狀態(tài)。 下面,我們介紹決定終止進程所用的規(guī)則。
進程生命周期
Android 系統(tǒng)將盡量長時間地保持應用進程,但為了新建進程或運行更重要的進程,最終需要清除舊進程來回收內(nèi)存。 為了確定保留或終止哪些進程,系統(tǒng)會根據(jù)進程中正在運行的組件以及這些組件的狀態(tài),將每個進程放入“重要性層次結構”中。 必要時,系統(tǒng)會首先消除重要性最低的進程,然后是重要性略遜的進程,依此類推,以回收系統(tǒng)資源。
重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類進程(第一個進程最重要,將是最后一個被終止的進程):
1、前臺進程
用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視為前臺進程:
托管用戶正在交互的 Activity(已調(diào)用 Activity 的 onResume() 方法)
托管某個 Service,后者綁定到用戶正在交互的 Activity
托管正在“前臺”運行的 Service(服務已調(diào)用 startForeground())
托管正執(zhí)行一個生命周期回調(diào)的 Service(onCreate()、onStart() 或 onDestroy())
托管正執(zhí)行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前臺進程都為數(shù)不多。只有在內(nèi)在不足以支持它們同時繼續(xù)運行這一萬不得已的情況下,系統(tǒng)才會終止它們。 此時,設備往往已達到內(nèi)存分頁狀態(tài),因此需要終止一些前臺進程來確保用戶界面正常響應。
2、可見進程
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內(nèi)容的進程。 如果一個進程滿足以下任一條件,即視為可見進程:
托管不在前臺、但仍對用戶可見的 Activity(已調(diào)用其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話框,允許在其后顯示上一 Activity,則有可能會發(fā)生這種情況
托管綁定到可見(或前臺)Activity 的 Service
可見進程被視為是極其重要的進程,除非為了維持所有前臺進程同時運行而必須終止,否則系統(tǒng)不會終止這些進程。
3、服務進程
正在運行已使用 startService() 方法啟動的服務且不屬于上述兩個更高類別進程的進程。盡管服務進程與用戶所見內(nèi)容沒有直接關聯(lián),但是它們通常在執(zhí)行一些用戶關心的操作(例如,在后臺播放音樂或從網(wǎng)絡下載數(shù)據(jù))。因此,除非內(nèi)存不足以維持所有前臺進程和可見進程同時運行,否則系統(tǒng)會讓服務進程保持運行狀態(tài)。
4、后臺進程
包含目前對用戶不可見的 Activity 的進程(已調(diào)用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統(tǒng)可能隨時終止它們,以回收內(nèi)存供前臺進程、可見進程或服務進程使用。 通常會有很多后臺進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最后一個被終止。如果某個 Activity 正確實現(xiàn)了生命周期方法,并保存了其當前狀態(tài),則終止其進程不會對用戶體驗產(chǎn)生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態(tài)。 有關保存和恢復狀態(tài)的信息,請參閱Activity文檔。
5、空進程
不含任何活動應用組件的進程。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間。 為使總體系統(tǒng)資源在進程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進程。
根據(jù)進程中當前活動組件的重要程度,Android 會將進程評定為它可能達到的最高級別。例如,如果某進程托管著服務和可見 Activity,則會將此進程評定為可見進程,而不是服務進程。
此外,一個進程的級別可能會因其他進程對它的依賴而有所提高,即服務于另一進程的進程其級別永遠不會低于其所服務的進程。 例如,如果進程 A 中的內(nèi)容提供程序為進程 B 中的客戶端提供服務,或者如果進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視為至少與進程 B 同樣重要。
由于運行服務的進程其級別高于托管后臺 Activity 的進程,因此啟動長時間運行操作的 Activity 最好為該操作啟動服務,而不是簡單地創(chuàng)建工作線程,當操作有可能比 Activity 更加持久時尤要如此。例如,正在將圖片上傳到網(wǎng)站的 Activity 應該啟動服務來執(zhí)行上傳,這樣一來,即使用戶退出 Activity,仍可在后臺繼續(xù)執(zhí)行上傳操作。使用服務可以保證,無論 Activity 發(fā)生什么情況,該操作至少具備“服務進程”優(yōu)先級。 同理,廣播接收器也應使用服務,而不是簡單地將耗時冗長的操作放入線程中。
二、線程
應用啟動時,系統(tǒng)會為應用創(chuàng)建一個名為“主線程”的執(zhí)行線程。 此線程非常重要,因為它負責將事件分派給相應的用戶界面小工具,其中包括繪圖事件。 此外,它也是應用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程。因此,主線程有時也稱為 UI 線程。
系統(tǒng)絕對不會為每個組件實例創(chuàng)建單獨的線程。運行于同一進程的所有組件均在 UI 線程中實例化,并且對每個組件的系統(tǒng)調(diào)用均由該線程進行分派。因此,響應系統(tǒng)回調(diào)的方法(例如,報告用戶操作的 onKeyDown() 或生命周期回調(diào)方法)始終在進程的 UI 線程中運行。
例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給小工具,而小工具反過來又設置其按下狀態(tài),并將無效請求發(fā)布到事件隊列中。UI 線程從隊列中取消該請求并通知小工具應該重繪自身。
在應用執(zhí)行繁重的任務以響應用戶交互時,除非正確實施應用,否則這種單線程模式可能會導致性能低下。 特別地,如果 UI 線程需要處理所有任務,則執(zhí)行耗時很長的操作(例如,網(wǎng)絡訪問或數(shù)據(jù)庫查詢)將會阻塞整個 UI。一旦線程被阻塞,將無法分派任何事件,包括繪圖事件。從用戶的角度來看,應用顯示為掛起。 更糟糕的是,如果 UI 線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘),用戶就會看到一個讓人厭煩的“應用無響應”(ANR) 對話框。如果引起用戶不滿,他們可能就會決定退出并卸載此應用。
此外,Android UI 工具包并非線程安全工具包。因此,您不得通過工作線程操縱 UI,而只能通過 UI 線程操縱用戶界面。因此,Android 的單線程模式必須遵守兩條規(guī)則:
1、不要阻塞 UI 線程
2、不要在 UI 線程之外訪問 Android UI 工具包
工作線程
根據(jù)上述單線程模式,要保證應用 UI 的響應能力,關鍵是不能阻塞 UI 線程。如果執(zhí)行的操作不能很快完成,則應確保它們在單獨的線程(“后臺”或“工作”線程)中運行。
例如,以下代碼演示了一個點擊偵聽器從單獨的線程下載圖像并將其顯示在 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(); }
乍看起來,這段代碼似乎運行良好,因為它創(chuàng)建了一個新線程來處理網(wǎng)絡操作。 但是,它違反了單線程模式的第二條規(guī)則:不要在 UI 線程之外訪問 Android UI 工具包—此示例從工作線程(而不是 UI 線程)修改了 ImageView。這可能導致出現(xiàn)不明確、不可預見的行為,但要跟蹤此行為困難而又費時。
為解決此問題,Android 提供了幾種途徑來從其他線程訪問 UI 線程。以下列出了幾種有用的方法:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
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(); }
現(xiàn)在,上述實現(xiàn)屬于線程安全型:在單獨的線程中完成網(wǎng)絡操作,而在 UI 線程中操縱 ImageView。
但是,隨著操作日趨復雜,這類代碼也會變得復雜且難以維護。 要通過工作線程處理更復雜的交互,可以考慮在工作線程中使用 Handler 處理來自 UI 線程的消息。當然,最好的解決方案或許是擴展 AsyncTask 類,此類簡化了與 UI 進行交互所需執(zhí)行的工作線程任務。
使用 AsyncTask
AsyncTask 允許對用戶界面執(zhí)行異步操作。它會先阻塞工作線程中的操作,然后在 UI 線程中發(fā)布結果,而無需您親自處理線程和/或處理程序。
要使用它,必須創(chuàng)建 AsyncTask 子類并實現(xiàn) doInBackground() 回調(diào)方法,該方法將在后臺線程池中運行。要更新 UI,必須實現(xiàn) onPostExecute() 以傳遞 doInBackground() 返回的結果并在 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 是安全的,代碼也得到簡化,因為任務分解成了兩部分:一部分應在工作線程內(nèi)完成,另一部分應在 UI 線程內(nèi)完成。
下面簡要概述了 AsyncTask 的工作方法,但要全面了解如何使用此類,您應閱讀 AsyncTask 參考文檔:
可以使用泛型指定參數(shù)類型、進度值和任務最終值
方法 doInBackground() 會在工作線程上自動執(zhí)行
onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調(diào)用
doInBackground() 返回的值將發(fā)送到 onPostExecute()
您可以隨時在 doInBackground() 中調(diào)用publishProgress(),以在 UI 線程中執(zhí)行 onProgressUpdate()
您可以隨時取消任何線程中的任務
注意:使用工作線程時可能會遇到另一個問題,即:運行時配置變更(例如,用戶更改了屏幕方向)導致 Activity 意外重啟,這可能會銷毀工作線程。 要了解如何在這種重啟情況下堅持執(zhí)行任務,以及如何在 Activity 被銷毀時正確地取消任務,請參閱書架示例應用的源代碼。
線程安全方法
在某些情況下,您實現(xiàn)的方法可能會從多個線程調(diào)用,因此編寫這些方法時必須確保其滿足線程安全的要求。
這一點主要適用于可以遠程調(diào)用的方法,如綁定服務中的方法。如果對 IBinder 中所實現(xiàn)方法的調(diào)用源自運行 IBinder 的同一進程,則該方法在調(diào)用方的線程中執(zhí)行。但是,如果調(diào)用源自其他進程,則該方法將在從線程池選擇的某個線程中執(zhí)行(而不是在進程的 UI 線程中執(zhí)行),線程池由系統(tǒng)在與 IBinder 相同的進程中維護。例如,即使服務的 onBind() 方法將從服務進程的 UI 線程調(diào)用,在 onBind() 返回的對象中實現(xiàn)的方法(例如,實現(xiàn) RPC 方法的子類)仍會從線程池中的線程調(diào)用。由于一個服務可以有多個客戶端,因此可能會有多個池線程在同一時間使用同一 IBinder 方法。因此,IBinder 方法必須實現(xiàn)為線程安全方法。
同樣,內(nèi)容提供程序也可接收來自其他進程的數(shù)據(jù)請求。盡管 ContentResolver 和 ContentProvider 類隱藏了如何管理進程間通信的細節(jié),但響應這些請求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)將從內(nèi)容提供程序所在進程的線程池中調(diào)用,而不是從進程的 UI 線程調(diào)用。由于這些方法可能會同時從任意數(shù)量的線程調(diào)用,因此它們也必須實現(xiàn)為線程安全方法。
進程間通信
Android 利用遠程過程調(diào)用 (RPC) 提供了一種進程間通信 (IPC) 機制,通過這種機制,由 Activity 或其他應用組件調(diào)用的方法將(在其他進程中)遠程執(zhí)行,而所有結果將返回給調(diào)用方。這就要求把方法調(diào)用及其數(shù)據(jù)分解至操作系統(tǒng)可以識別的程度,并將其從本地進程和地址空間傳輸至遠程進程和地址空間,然后在遠程進程中重新組裝并執(zhí)行該調(diào)用。 然后,返回值將沿相反方向傳輸回來。 Android 提供了執(zhí)行這些 IPC 事務所需的全部代碼,因此您只需集中精力定義和實現(xiàn) RPC 編程接口即可。
要執(zhí)行 IPC,必須使用 bindService() 將應用綁定到服務上。如需了解詳細信息,請參閱服務開發(fā)者指南。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android編程實現(xiàn)在自定義對話框中獲取EditText中數(shù)據(jù)的方法
這篇文章主要介紹了Android編程實現(xiàn)在自定義對話框中獲取EditText中數(shù)據(jù)的方法,結合實例形式分析了Android對話框數(shù)據(jù)傳遞相關操作技巧,需要的朋友可以參考下2018-01-01android自定義按鈕示例(重寫imagebutton控件實現(xiàn)圖片按鈕)
由于項目這種類型的圖片按鈕比較多,所以重寫了ImageButton類,現(xiàn)在把代碼分享給大家,需要的朋友可以參考下2014-03-03Android中實現(xiàn)監(jiān)聽ScrollView滑動事件
這篇文章主要介紹了Android中實現(xiàn)監(jiān)聽ScrollView滑動事件,本文用重寫ScrollView類的方法實現(xiàn)了一些擴展功能,需要的朋友可以參考下2015-05-05Android使用自定義控件HorizontalScrollView打造史上最簡單的側滑菜單
側滑菜單一般都會自定義ViewGroup,然后隱藏菜單欄,當手指滑動時,通過Scroller或者不斷的改變leftMargin等實現(xiàn);多少都有點復雜,完成以后還需要對滑動沖突等進行處理,今天給大家?guī)硪粋€簡單的實現(xiàn),史上最簡單有點夸張,但是的確是我目前遇到過的最簡單的一種實現(xiàn)2016-02-02Android UI設計系列之自定義Dialog實現(xiàn)各種風格的對話框效果(7)
這篇文章主要介紹了Android UI設計系列之自定義Dialog實現(xiàn)各種風格的對話框效果,具有一定的實用性和參考價值,感興趣的小伙伴們可以參考一下2016-06-06