當某個應用組件啓動且該應用沒有運行其餘任何組件時,Android 系統會使用單個執行線程爲應用啓動新的 Linux 進程。默認狀況下,同一應用的全部組件在相同的進程和線程(稱爲「主」線程)中運行。 若是某個應用組件啓動且該應用已存在進程(由於存在該應用的其餘組件),則該組件會在此進程內啓動並使用相同的執行線程。 可是,您能夠安排應用中的其餘組件在單獨的進程中運行,併爲任何進程建立額外的線程。android
本文介紹進程和線程在 Android 應用中的工做方式。數據庫
默認狀況下,同一應用的全部組件均在相同的進程中運行,且大多數應用都不會改變這一點。 可是,若是您發現須要控制某個組件所屬的進程,則可在清單文件中執行此操做。編程
各種組件元素的清單文件條目—activity、service、receiver 和 provider—均支持 android:process 屬性,此屬性能夠指定該組件應在哪一個進程運行。您能夠設置此屬性,使每一個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其餘組件則不共享。 此外,您還能夠設置 android:process,使不一樣應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 並使用相同的證書進行簽署。緩存
此外,application 元素還支持 android:process 屬性,以設置適用於全部組件的默認值。安全
若是內存不足,而其餘爲用戶提供更緊急服務的進程又須要內存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷燬。 當這些組件須要再次運行時,系統將爲它們重啓進程。bash
決定終止哪一個進程時,Android 系統將權衡它們對用戶的相對重要程度。例如,相對於託管可見 Activity 的進程而言,它更有可能關閉託管屏幕上再也不可見的 Activity 的進程。 所以,是否終止某個進程的決定取決於該進程中所運行組件的狀態。 下面,咱們介紹決定終止進程所用的規則。網絡
Android 系統將盡可能長時間地保持應用進程,但爲了新建進程或運行更重要的進程,最終須要移除舊進程來回收內存。 爲了肯定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每一個進程放入「重要性層次結構」中。 必要時,系統會首先消除重要性最低的進程,而後是重要性略遜的進程,依此類推,以回收系統資源。app
如下列表按照重要程度列出了各種進程(第一個進程最重要,將是最後一個被終止的進程):異步
用戶當前操做所必需的進程。若是一個進程知足如下任一條件,即視爲前臺進程: 託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法) 託管某個 Service,後者綁定到用戶正在交互的 Activity 託管正在「前臺」運行的 Service(服務已調用 startForeground()) 託管正執行一個生命週期回調的 Service(onCreate()、onStart() 或 onDestroy()) 託管正執行其 onReceive() 方法的 BroadcastReceiver 一般,在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們。 此時,設備每每已達到內存分頁狀態,所以須要終止一些前臺進程來確保用戶界面正常響應。ide
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 若是一個進程知足如下任一條件,即視爲可見進程: 託管不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,若是前臺 Activity 啓動了一個對話框,容許在其後顯示上一 Activity,則有可能會發生這種狀況。 託管綁定到可見(或前臺)Activity 的 Service。 可見進程被視爲是極其重要的進程,除非爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。
正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的進程。儘管服務進程與用戶所見內容沒有直接關聯,可是它們一般在執行一些用戶關心的操做(例如,在後臺播放音樂或從網絡下載數據)。所以,除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會讓服務進程保持運行狀態。
包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 一般會有不少後臺進程在運行,所以它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。若是某個 Activity 正確實現了生命週期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,由於當用戶導航回該 Activity 時,Activity 會恢復其全部可見狀態。 有關保存和恢復狀態的信息,請參閱 Activity文檔。
不含任何活動應用組件的進程。保留這種進程的的惟一目的是用做緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使整體系統資源在進程緩存和底層內核緩存之間保持平衡,系統每每會終止這些進程。 根據進程中當前活動組件的重要程度,Android 會將進程評定爲它可能達到的最高級別。例如,若是某進程託管着服務和可見 Activity,則會將此進程評定爲可見進程,而不是服務進程。
此外,一個進程的級別可能會因其餘進程對它的依賴而有所提升,即服務於另外一進程的進程其級別永遠不會低於其所服務的進程。 例如,若是進程 A 中的內容提供程序爲進程 B 中的客戶端提供服務,或者若是進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視爲至少與進程 B 一樣重要。
因爲運行服務的進程其級別高於託管後臺 Activity 的進程,所以啓動長時間運行操做的 Activity 最好爲該操做啓動服務,而不是簡單地建立工做線程,當操做有可能比 Activity 更加持久時尤要如此。例如,正在將圖片上傳到網站的 Activity 應該啓動服務來執行上傳,這樣一來,即便用戶退出 Activity,仍可在後臺繼續執行上傳操做。使用服務能夠保證,不管 Activity 發生什麼狀況,該操做至少具有「服務進程」優先級。 同理,廣播接收器也應使用服務,而不是簡單地將耗時冗長的操做放入線程中。
應用啓動時,系統會爲應用建立一個名爲「主線程」的執行線程。 此線程很是重要,由於它負責將事件分派給相應的用戶界面小部件,其中包括繪圖事件。 此外,它也是應用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程。所以,主線程有時也稱爲 UI 線程。
系統不會爲每一個組件實例建立單獨的線程。運行於同一進程的全部組件均在 UI 線程中實例化,而且對每一個組件的系統調用均由該線程進行分派。 所以,響應系統回調的方法(例如,報告用戶操做的 onKeyDown() 或生命週期回調方法)始終在進程的 UI 線程中運行。
例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給小部件,而小部件反過來又設置其按下狀態,並將失效請求發佈到事件隊列中。 UI 線程從隊列中取消該請求並通知小部件應該重繪自身。
在應用執行繁重的任務以響應用戶交互時,除非正確實現應用,不然這種單線程模式可能會致使性能低下。 具體地講,若是 UI 線程須要處理全部任務,則執行耗時很長的操做(例如,網絡訪問或數據庫查詢)將會阻塞整個 UI。 一旦線程被阻塞,將沒法分派任何事件,包括繪圖事件。 從用戶的角度來看,應用顯示爲掛起。 更糟糕的是,若是 UI 線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘),用戶就會看到一個讓人厭煩的「應用無響應」(ANR) 對話框。若是引發用戶不滿,他們可能就會決定退出並卸載此應用。
此外,Android UI 工具包並不是線程安全工具包。所以,您不得經過工做線程操縱 UI,而只能經過 UI 線程操縱用戶界面。 所以,Android 的單線程模式必須遵照兩條規則:
不要在 UI 線程以外訪問 Android UI 工具包
根據上述單線程模式,要保證應用 UI 的響應能力,關鍵是不能阻塞 UI 線程。 若是執行的操做不能很快完成,則應確保它們在單獨的線程(「後臺」或「工做」線程)中運行。
例如,如下代碼演示了一個點擊偵聽器從單獨的線程下載圖像並將其顯示在 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();
}
複製代碼
乍看起來,這段代碼彷佛運行良好,由於它建立了一個新線程來處理網絡操做。 可是,它違反了單線程模式的第二條規則:不要在 UI 線程以外訪問 Android UI 工具包 — 此示例從工做線程(而不是 UI 線程)修改了 ImageView。 這可能致使出現不明確、不可預見的行爲,但要跟蹤此行爲困難而又費時。
爲解決此問題,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();
}
複製代碼
如今,上述實現屬於線程安全型:在單獨的線程中完成網絡操做,而在 UI 線程中操縱 ImageView。
可是,隨着操做日趨複雜,這類代碼也會變得複雜且難以維護。 要經過工做線程處理更復雜的交互,能夠考慮在工做線程中使用 Handler 處理來自 UI 線程的消息。固然,最好的解決方案或許是擴展 AsyncTask 類,此類簡化了與 UI 進行交互所需執行的工做線程任務。
AsyncTask 容許對用戶界面執行異步操做。 它會先阻塞工做線程中的操做,而後在 UI 線程中發佈結果,而無需您親自處理線程和/或處理程序。
要使用它,必須建立 AsyncTask 的子類並實現 doInBackground() 回調方法,該方法將在後臺線程池中運行。 要更新 UI,應該實現 onPostExecute() 以傳遞 doInBackground() 返回的結果並在 UI 線程中運行,以便您安全地更新 UI。 稍後,您能夠經過從 UI 線程調用 execute() 來運行任務。
例如,您能夠經過如下方式使用 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);
}
}
複製代碼
如今 UI 是安全的,代碼也獲得簡化,由於任務分解成了兩部分:一部分應在工做線程內完成,另外一部分應在 UI 線程內完成。
下面簡要概述了 AsyncTask 的工做方法,但要全面瞭解如何使用此類,您應閱讀 AsyncTask 參考文檔:
可使用泛型指定參數類型、進度值和任務最終值 方法 doInBackground() 會在工做線程上自動執行 onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調用 doInBackground() 返回的值將發送到 onPostExecute() 您能夠隨時在 doInBackground() 中調用publishProgress(),以在 UI 線程中執行 onProgressUpdate() 您能夠隨時取消任何線程中的任務 注意:使用工做線程時可能會遇到另外一個問題,即:運行時配置變動(例如,用戶更改了屏幕方向)致使 Activity 意外重啓,這可能會銷燬工做線程。 要了解如何在這種重啓狀況下堅持執行任務,以及如何在 Activity 被銷燬時正確地取消任務,請參閱書架示例應用的源代碼。
在某些狀況下,您實現的方法可能會從多個線程調用,所以編寫這些方法時必須確保其知足線程安全的要求。
這一點主要適用於能夠遠程調用的方法,如綁定服務中的方法。若是對 IBinder 中所實現方法的調用源自運行 IBinder 的同一進程,則該方法在調用方的線程中執行。可是,若是調用源自其餘進程,則該方法將在從線程池選擇的某個線程中執行(而不是在進程的 UI 線程中執行),線程池由系統在與 IBinder 相同的進程中維護。 例如,即便服務的 onBind() 方法將從服務進程的 UI 線程調用,在 onBind() 返回的對象中實現的方法(例如,實現 RPC 方法的子類)仍會從線程池中的線程調用。 因爲一個服務能夠有多個客戶端,所以可能會有多個池線程在同一時間使用同一 IBinder 方法。所以,IBinder 方法必須實現爲線程安全方法。
一樣,內容提供程序也可接收來自其餘進程的數據請求。儘管 ContentResolver 和 ContentProvider 類隱藏瞭如何管理進程間通訊的細節,但響應這些請求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)將從內容提供程序所在進程的線程池中調用,而不是從進程的 UI 線程調用。 因爲這些方法可能會同時從任意數量的線程調用,所以它們也必須實現爲線程安全方法。
Android 利用遠程過程調用 (RPC) 提供了一種進程間通訊 (IPC) 機制,經過這種機制,由 Activity 或其餘應用組件調用的方法將(在其餘進程中)遠程執行,而全部結果將返回給調用方。 這就要求把方法調用及其數據分解至操做系統能夠識別的程度,並將其從本地進程和地址空間傳輸至遠程進程和地址空間,而後在遠程進程中從新組裝並執行該調用。 而後,返回值將沿相反方向傳輸回來。 Android 提供了執行這些 IPC 事務所需的所有代碼,所以您只需集中精力定義和實現 RPC 編程接口便可。
要執行 IPC,必須使用 bindService() 將應用綁定到服務上。