瞭解 Android 的進程和線程

前言:本文所寫的是博主的我的看法,若有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文連接demo連接java

當某個應用組件啓動且未運行其餘組件時, Android 系統會使用單個執行線程爲應用啓動新的 Linux 進程。默認狀況下,同一應用的全部組件在相同的進程和線程(主線程)中運行。若是某個應用組件啓動且應用已存在進程(存在其餘組件),則該組件會在此進程內啓動並使用相同的執行線程,可是你能夠安排應用的其餘組件在單獨的進程中運行,併爲任何進程建立額外的線程。android

進程

默認狀況下,同一應用的全部組件均在相同的進程中運行,且大多數的應該都是如此。可是,若是須要控制某個組件所屬的進程,則能夠在清單文件中執行此操做。git

各種組件元素的清單文件條目 <activity><service><receiver><provider> 均支持 android:process 屬性,該屬性能夠指定組件應該運行在哪一個進程。能夠設置此屬性,使每一個組件在各自的進程中運行,或者使一些組件共享一個進程,而其餘組件則不共享。此外,還能夠設置 android:process ,應用共享具備相同的 Linux 用戶 ID 和相同的證書籤署,使不一樣應用的組件在相同的進程中運行。github

此外,<application> 元素還支持 android:process 屬性,能夠設置適用於全部組件的默認值。數據庫

若是內存不足,而其餘爲用戶提供更緊急服務的進程又須要內存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷燬。當這些組件須要再次運行時,系統將爲它們重啓進程。編程

決定終止哪一個進程時,Android 系統將權衡它們對用戶的相對重要程度。例如,相對於託管可見 Activity 的進程而言,它更有可能關閉託管屏幕上再也不可見的 Activity 進程。所以,是否終止某個進程取決於該進程所運行組件的狀態。緩存

進程的生命週期

Android 系統將盡可能長時間的保持應用進程,但爲了新建進程或者運行更重要的進程,最終須要移除舊的進程來回收內存。爲了肯定保留或終止哪些進程,系統會根據正在運行的組件及這些組件的狀態,將每一個進程按重要性層次結構進行劃分,必要時,系統會首先清除重要性最低的進程,而後是重要性略遜的進程,以此類推,來回收系統資源。安全

重要性層次結構一共有5級。下面的列表按照重要程度列出了各種進程(第一個進程最重要,會是最後被終止的進程):網絡

  1. 前臺進程app

    用戶當前操做所必需的進程。若是一個進程知足如下任意一個條件,即視爲前臺進程:

    • 託管於用戶正在交互的 Activity (已調用 Activity 的 onResume() 方法)
    • 託管於某個 Service,後者綁定到用戶正在交互的 Activity
    • 託管於正在前臺運行的 Service (服務已調用 startForeground())
    • 託管於正在執行一個生命週期回調的 Service (onCreate()、onStart() 或 onDestroy())
    • 託管於正在執行 onReceive() 方法的 BroadcastReceiver

    一般在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行,不得已的狀況下,系統纔會終止它們。此時,,設備每每已達到內存分頁狀態,須要終止一些前臺進程來確保用戶界面正常響應。

  2. 可見進程

    沒有任何前臺組件、但會影響用戶在屏幕上所見內容的進程。若是一個進程知足如下任一條件,即視爲可見進程:

    • 託管不在前臺、但仍對客戶可見的 Activity (已調用其 onPause()方法)。例如,若是前臺 Activity 啓動一個對話框,容許在其後顯示一個 Activity ,則有可能會發生這種狀況。
    • 託管綁定到可見(或前臺) Activity 的 Service
      可見進程被視爲極其重要的進程,除非是爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。
  3. 服務進程

    正在運行已使用 startService() 方法啓動的服務不屬於上面的兩個更高級別的進程。儘管服務進程與用戶縮減內容沒有直接關聯,但他們一般執行一些用戶關心的操做(例如,在後臺播放音樂或從網絡下載數據)。所以除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會會讓服務進程保持運行狀態。

  4. 後臺進程

    包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統隨時能夠終止它們,以回收內存供前臺進程、可見進程或服務進程使用。一般會有不少後臺進程在運行,他們都會保存在LRU(最近最少使用)列表中,確保包含用戶最近查看的 Activity 的進程會是被最後終止的一個。若是某個 Activity 正確的實現了生命週期方法,而且保存了其當前狀態,則終止進程不會對用戶體驗產生明顯影響,由於當用戶導航回該 Activity 時,Activity 會恢復其全部可見的狀態。能夠參考淺談Android Activity的生命週期

  5. 空進程
    不含任何活動應用組件的進程。 保留這種進程的惟一目的是用做緩存,以縮短下次在其運行組件所需的啓動時間。爲使整體系統資源在進程緩存和底層內了緩存之間保持平衡,系統每每會終止這些進程。

根據進程中當前活動組件的重要程度, 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 的單線程模式必須遵照兩條規則:

  1. 不要阻塞 UI 線程
  2. 不要在 UI 線程以外訪問 Android UI 工具包

工做線程

在單線程模式下,要保證應用 UI 的響應能力,關鍵是不能阻塞 UI 線程。若是執行的操做不能很快完成,則應該確保他們在單獨的線程(後臺或者工做線程)中運行。

例如,下面演示了一個點擊監聽器從單獨的線程下載圖片並將其顯示在 ImageView 中:

@Override
   public void onClick(View v) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               final Bitmap bitmap = loadImageFromNetwork(url);
               showImage.setImageBitmap(bitmap);
           }
       }).start();
   }複製代碼

這段代碼看起來彷佛運行良好,由於它建立了一個新的線程來處理網絡操做。可是它違背了單線程模式的第二條規則:不要在 UI 線程以外訪問 Android UI 工具包,此示例從工做線程(而不是 UI )線程修改了 ImageView 。這個可能致使出現不明確,不可預見的行爲,可是要跟蹤這個行爲既困難又費時。

未解決此問題,Android 提供了幾種途徑從其餘線程訪問 UI 線程。如下列出幾種有用的方法:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

例如你能夠經過使用 Activity.runOnUiThread(Runnable) 方法修復上面的代碼:

@Override
   public void onClick(View v) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               final Bitmap bitmap = loadImageFromNetwork(url);
               MainActivity.this.runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       showImage.setImageBitmap(bitmap);
                   }
               });

           }
       }).start();
   }複製代碼

上面的實現屬於線程安全型:在單獨的線程中完成網絡操做,而在 UI 線程中操做 ImageView。

可是隨着操做的複雜,這類代碼變得難以維護,要經過工做線程實現更復雜的交互,能夠考慮在工做線程中使用 Handler 處理來自 UI 線程的消息。固然更好的解決方案是擴展 AsyncTask 類,此類簡化了與 UI 線程進行交互所須要執行的工做線程任務。

使用 AsyncTask

AsyncTask容許用戶對用戶界面執行異步操做。他會先阻塞工做線程中的操做,而後在 UI 線程中發佈結果,而你無需親自處理線程和處理程序。

要使用它,必須建立 AsyncTask 的子類,並實現 odInBrackground() 回調方法,該方法將在後臺線程池中運行,須要更新 UI 則要實現 onPostExecute(),傳遞 odInBrackground() 返回的結果並在 UI 線程中運行,使之更安全地更新 UI,能夠經過從 UI 線程中調用 execute() 來運行任務。

例如,下面使用 AsyncTask 來實現上述的示例:

Activity 裏調用 execute() 方法

@Override
            public void onClick(View v) {
                new DownloadImageTask(showImage).execute(url);
            }複製代碼

繼承 AsyncTask 實現異步加載

public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {

    private ImageView mImageView;
    public DownloadImageTask() {
    }

    public DownloadImageTask(ImageView imageView) {
        mImageView = imageView;
    }
    @Override
    protected Bitmap doInBackground(String... params) {
        return DownImageUtil.getInstance().loadImageFromNetwork(params[0]);
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mImageView != null) {
            mImageView.setImageBitmap(bitmap);
        }
    }
}複製代碼

如今 UI 是安全的,代碼也獲得簡化,任務分解成了兩部分:一部分在工做線程內完成,另外一部分在 UI 線程內完成。

下面簡單的介紹 AsyncTask 的工做方法:

  • 可使用泛型指定參數類型、進度值和任務最終值
  • 方法 doInBackground() 會在工做線程上自動執行
  • onPreExecute() 、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調用
  • doInBackground() 返回值將發送到 onPostExecute()
  • 能夠隨時在 doInBackground() 中調用 publishProgress() ,以在 UI 線程中執行 onProgressUpdate()
  • 能夠隨時取消任何線程中的任務

注意:使用工做線程時有可能遇到另外一個問題,即:運行時配置變動(例如,用戶更改了屏幕方向),致使 Activity 意外重啓,這可能會銷燬工做線程。

線程安全方法

在某些狀況下,你實現的方法可能會從多個線程調用,所以在編寫這些方法時必須確保其知足線程安全的要求。

這一點主要適用於能夠遠程調用的方法,如綁定服務中的方法。若是對 IBinder 中所實現方法的調用源自運行 IBinder 的同一進程,則該方法在調用方的線程中執行。可是,若是調用源自其餘進程,則該方法將從線程池中選擇某個線程中執行(而不是在進程的 UI 線程中執行),線程池由系統與 IBinder 相同的進程中維護。例如,服務的 onBind() 方法從服務的 UI 線程中調用,在 onBind() 返回的對象中實現的方法仍會從線程池中的線程調用。因爲一個服務能夠有多個客戶端,所以可能會有多個線程池同一時間使用同一個 IBinder 方法。因此 IBinder 方法實現必須爲線程安全方法。

一樣,內容提供程序也能夠接受來自其餘進程的數據請求。儘管 ContentResolver 和 ContentProvider 類隱藏瞭如何管理進程間通訊的細節,但響應這些請求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)將從內容提供程序所在進程的線程池中調用,而不是從進程的 UI 線程調用。因爲這些方法同時從任意數量的線程調用,所以它們必須實現爲線程安全方法。

進程間通訊

Android 利用遠程過程調用(RPC)提供了一種進程間通訊(IPC)機制,經過這種機制,由 Activity 或其餘應用組件調用方法將(在其餘進程中)遠程執行,而全部結果將返回給調用方。這要求把方法調用及其數據分解至操做系統能夠識別的程度,並將其從本地進程和地址空間傳輸至遠程進程和地址空間,而後在遠程進程中從新組裝並執行該調用。而後返回值將沿相反方向傳輸回來。Android 提供了執行這些 IPC 事務所需的所有代碼,所以只須要集中精力定義和實現 RPC 編程接口便可。

須要執行 IPC,必須使用 bindService() 將應用綁定到服務上,想了解詳細的信息,能夠參考
淺談 Android Service

相關文章
相關標籤/搜索