Android 進程和線程

當應用程序組件啟動并且應用程序沒有任何其他組件運行時,Android系統將使用單個執行線程為應用程序啟動一個新的Linux進程。默認情況下, 同一應用程序的所有組件在同一進程和線程中運行(稱為“主”線程)。如果應用程序組件啟動并且已經存在該應用程序的進程(因為存在應用程序的另一個組件),則該組件將在該進程中啟動并使用相同的執行線程。 但是,您可以安排應用程序中的不同組件在單獨的進程中運行,并且可以為任何進程創建其他線程。


進程 Process

默認情況下,同一應用程序的所有組件在同一進程中運行,大多數應用程序不應該更改此操作。 但是,如果您發現需要控制某個組件所屬的進程,則可以在清單文件中進行。
清單條目中每個類型的組件元素<activity>,<service>,<receiver>和<provider>都支持一個android:process屬性,可以指定該組件應該運行的進程。您可以設置此屬性,以便每個組件在其自身的進程中運行,或者某些組件共享進程,而其他組件則不共享進程。您還可以設置android:process,使不同應用程序的組件在同一進程中運行,前提是應用程序共享相同的Linux用戶ID,并使用相同的證書進行簽名。

元素<application>還支持一個android:process屬性,設置一個默認值適用于所有組件。

因此在被殺死的進程中運行的應用程序組件被銷毀。當他們再次工作時,這些組件再次啟動一個進程。

當決定要殺死哪個進程時,Android系統會重新評估相對用戶的重要性。 例如,與可見Activiy的進程相比,它更容易關閉在屏幕上不再可見Activity的進程。因此,是否終止進程的決定取決于在該進程中運行的組件的狀態。

進程生命周期

Android系統嘗試盡可能長時間地維護應用程序進程,但最終需要刪除舊進程來為新的或更重要的進程回收內存。為了確定要保留的進程和要殺死的進程,系統將根據進程中運行的組件和這些組件的狀態將每個進程置于“重要性層次”中。首先消除重要性最低的進程,然后重要性較低的進程,依此類推,以恢復系統資源。

重要性層次結構有五個層次。以下列表按重要性順序列出不同類型的進程(第一個進程最重要,最后被 殺死):

  1. 前臺進程
    用戶正在操作的進程。如果滿足以下任一條件,則將進程視為前臺。通常,在任何給定時間只存在少量前臺進程。 他們只能作為最后的手段被殺死 - 如果記憶如此之低,以至于它們都不能繼續運行。 通常,在這一點上,設備已經達到存儲器分頁狀態,因此需要殺死一些前臺進程來保持用戶界面的響應。
  • 它承載用戶正在交互的Activity(已調用該Activity的onResume()方法)。
  • 它承載與Activity綁定、用戶正在交互的Service。
  • 它承載運行在“前臺”的Service (Service已經調用了startForeground())。
  • 它承載一個正在執行其生命周期回調之一(onCreate(),onStart()或onDestroy())的Service。
  • 它承載正在執行其onReceive()方法的BroadcastReceiver。
  1. 可見進程
    一個沒有任何前臺組件的進程,但仍然可以影響用戶在屏幕上看到的內容。 如果滿足以下條件之一,則認為該進程是可見的:
  • 它承載不在前臺的Activity,但對用戶仍然可見(其onPause()方法已被調用)。 這可能會發生,例如,如果前臺Activity啟動了一個dialog,這允許在其后面看到先前的Activity。
  • 它承載綁定到可見(或前臺)Activity的服務。
    一個可見的進程被認為是非常重要的,不會被殺死,除非這樣做是為了保持所有前臺進程的運行。
  1. 服務進程
    一個正在運行已經以startService()方法啟動但不屬于兩個較高類別的服務的進程。 雖然服務進程并不直接與用戶看到的任何內容相關聯,但它們通常是用戶關心的事情(例如在后臺播放音樂或在網絡上下載數據),因此系統保持運行,除非沒有足夠的內存 保留它們與所有前景和可見進程。
  2. 后臺進程
    持有當前不可見的Activity的進程(Activity的onStop()方法已被調用)。 這些進程對用戶體驗沒有直接影響,系統可以隨時殺死它們,為(前臺,可見或服務進程)回收內存。 通常有許多后臺進程運行,因此它們保留在LRU(最近最少使用的)列表中,以確保用戶最近看到的活動的進程最后被殺死。 如果Activity正確地實現了其生命周期方法,并且保存其當前狀態,那么殺死其進程將不會對用戶體驗產生明顯的影響,因為當用戶導航回Activity時,Activity將恢復其所有可見狀態。
  3. 空進程
    一個不包含任何活動應用程序組件的進程。 保持這種進程的唯一原因是為了緩存目的,以便在下一次組件需要運行時改進啟動時間。 系統通常會殺死這些進程,以平衡進程緩存和底層內核高速緩存之間的整體系統資源。

基于目前在該進程中活躍的組件的重要性,Android可以在最高級別進行排名。例如,如果進程保持Service和可見Activity,則該進程將被排列為可見進程,而不是服務進程。

此外,由于其他進程依賴于進程的排名可能會增加,因此,服務于另一進程的進程絕對不能低于其所服務的進程。例如,如果進程A中的內容提供商正在為進程B中的客戶端服務,或者如果進程A中的服務綁定到進程B中的組件,則進程A總是被認為至少與進程B一樣重要。

由于運行Service的進程的排名高于具有后臺Activity的進程,所以啟動長時間運行的操作的Activity可能會很好地為該操作啟動Service,而不是簡單地創建一個工作線程,特別是如果操作可能超出Activity。例如,將圖片上傳到網站的Activity應啟動Service以執行上傳,以便即使用戶離開Activity,上傳也可以在后臺繼續。使用服務保證操作至少具有“服務進程”的優先級,不管Acitivity發生了什么。這是BroadcastReceiver應該采用服務的原因,而不是簡單地在線程中耗費時間的操作。


線程 Thread

當應用程序啟動時,系統會為應用程序創建一個名為“main”的執行線程。這個線程非常重要,因為它負責將事件發送到適當的用戶界面小部件,包括繪圖事件。它也是您的應用程序與Android UI工具包(來自android.widget和android.view包的組件)進行交互的線程。因此,主線程有時也稱為UI線程。

系統不會為組件的每個實例創建一個單獨的線程。在同一進程中運行的所有組件都在UI線程中實例化,并從該線程調度對每個組件的系統調用。因此,響應系統回調(例如onKeyDown()來報告用戶操作或生命周期回調方法)的方法總是在進程的UI線程中運行。

例如,當用戶觸摸屏幕上的按鈕時,您的應用程序的UI線程將觸摸事件調度到窗口小部件,該窗口小部件又設置其按下的狀態,并向事件隊列發送無效請求。 UI線程對請求進行排隊,并通知小部件它應該重繪自己。

當您的應用程序執行緊急工作以響應用戶交互時,此單線程模型可能會導致性能下降,除非您正確實施應用程序。具體來說,如果在UI線程中發生了一切,執行諸如網絡訪問或數據庫查詢之類的長時間操作會阻止整個UI。當線程被阻止時,不會調度任何事件,包括繪制事件。從用戶的角度來看,應用程序似乎掛起。更糟糕的是,如果UI線程被阻塞超過幾秒鐘(目前約5秒),用戶將會看到臭名昭著的“應用程序無響應”(ANR)對話框。

另外,Andoid UI工具包不是線程安全的。所以,你不能從一個工作線程操縱你的UI - 你必須從UI線程對所有的用戶界面進行操作。因此,Android的單線程模型只有兩個規則:

  • 不要阻塞UI線程
  • 不要從UI線程外部更新UI

工作線程

由于上述單線程模型,對應用程序的UI的響應性至關重要,您不會阻塞UI線程。如果您的操作執行不是即時的,那么您應該確保在單獨的線程(“后臺”或“工作”線程)中執行操作。

例如,下面是一個點擊監聽器的一些代碼,它從一個單獨的線程中下載一個映像并將其顯示在ImageView:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://img.blog.csdn.net/20170427142128030");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

起初,這似乎工作正常,因為它創建一個新的線程來處理網絡操作。但是,它違反了單線程模型的第二條規則:不要從UI線程外面更新UI - 這個示例ImageView從工作線程而不是UI線程修改。這可能導致未定義和意外的行為,這可能是困難和耗時的追蹤。

要解決這個問題,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://img.blog.csdn.net/20170427142128030");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

現在這個實現是線程安全的:網絡操作是從一個單獨的線程完成的,而ImageView從UI線程被操縱。

然而,隨著操作的復雜性的增長,這種代碼可能會變得復雜和難以維護。要處理與工作線程的更復雜的交互,您可以考慮Handler在工作線程中使用一個來處理從UI線程傳遞的消息。也許最好的解決方案是擴展AsyncTask類,這簡化了需要與UI交互的工作線程任務的執行。

線程安全的方法

在某些情況下,可以從多個線程調用實現的方法,因此必須將其編寫為線程安全的。

這主要適用于可以遠程調用的方法,例如綁定服務中的方法。當在IBinder實現的方法的調用源于IBinder正在運行的相同進程時 ,該方法在調用者的線程中執行。然而,當呼叫源于另一個進程時,該方法在從系統維護在與IBinder相同的進程(不在進程的UI線程中執行)的線程池中選擇的線程中執行。例如,雖然服務的 onBind()方法將從服務進程的UI線程調用,但是將onBind()返回的對象(例如,實現RPC方法的子類)中實現的方法將從線程池中調用。因為服務可以有多個客戶端,所以多個池線程可以同時使用相同的IBinder方法。 因此IBinder方法必須實現為線程安全。

類似地,Content Provider可以接收源自其他進程的數據請求。雖然ContentResolver和ContentProvider 類隱藏的進程間通信是如何管理的細節,ContentProvider會響應這些請求---(query(),insert(),delete(),update(),和getType())被調用來自內容提供商進程中的線程池,而不是該進程的UI線程。因為這些方法可能會同時從任意數量的線程調用,所以它們也必須被實現為線程安全的。


進程間通信

Android提供了使用遠程進程調用(RPC)的進程間通信(IPC)的機制,其中一個方法由Activity或其他應用程序組件調用,但是遠程執行(在另一個進程中),任何結果返回給調用者。這需要將方法調用及其數據分解為操作系統可以理解的級別,將其從本地進程和地址空間發送到遠程進程和地址空間,然后重新組合并重新啟動該呼叫。然后返回值以相反方向傳輸。Android提供執行這些IPC事務的所有代碼,因此您可以專注于定義和實現RPC編程接口。

要執行IPC,您的應用程序必須綁定到使用的服務bindService()。有關詳細信息,可以參考 [Android 進程間通信——Service、Messenger]

歡迎大家關注、評論、點贊
你們的支持是我堅持的動力。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 進程和線程 快速查看 默認情況下,每個應用程序運行在各自的進程中,應用程序中的所有組件也都運行在其中。activi...
    Czppp閱讀 343評論 0 0
  • Android 進程和線程 當一個應用程序組件啟動和應用程序沒有任何其他組件在運行時,Android系統開始一個新...
    ProZoom閱讀 494評論 0 1
  • Table of Contents generated with DocToc 【Android 進程和線程】 ...
    Rtia閱讀 395評論 0 1
  • Android進程模型 在安裝Android應用程序的時候,Android會為每個程序分配一個Linux用戶ID,...
    Yeung_Yeung閱讀 218評論 0 0
  • 轉自:https://developer.android.com/guide/components/process...
    畫峰閱讀 218評論 0 0