Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android資訊 >> Android的進程與線程使用總結

Android的進程與線程使用總結

編輯:Android資訊

當一個Android應用程序組件啟動時候,如果此時這個程序的其他組件沒有正在運行,那麼系統會為這個程序以單一線程的形式啟動一個新的Linux 進程。默認情況下,同一應用程序下的所有組件都運行再相同的進程和線程(一般稱為程序的“主”線程)中。如果一個應用組件啟動但這個應用的進程已經存在了(因為這個應用的其他組件已經在之前啟動了),那麼這個組件將會在這個進程中啟動,同時在這個應用的主線程裡面執行。然而,你也可以讓你的應用裡面的組件運行在不同的進程裡面,也可以為任何進程添加額外的線程。

這片文章討論了Android程序裡面的進程和線程如何運作的。

進程

默認情況下,同一程序的所有組件都運行在相同的進程裡面,大多數的應用都是這樣的。然而,如果你發現你需要讓你的程序裡面的某個組件運行在特定的進程裡面,你可以在manifest 文件裡面設置。

manifest 文件裡面為每一個組件元素—<activity><service><receiver>, 和<provider>—提供了 android:process 屬性。通過設置這個屬性你可以讓組件運行在特定的進程中。你可以設置成每個組件運行在自己的進程中,也可以讓一些組件共享一個進程而其他的不這樣。你還可以設置成不同應用的組件運行在同一個進程裡面—這樣可以讓這些應用共享相同的Linux user ID同時被相同的證書所認證。

<application> 元素也支持 android:process 屬性,設置這個屬性可以讓這個應用裡面的所有組件都默認繼承這個屬性。

Android 可能在系統剩余內存較少,而其他直接服務用戶的進程又要申請內存的時候shut down 一個進程, 這時這個進程裡面的組件也會依次被kill掉。當這些組件有新的任務到達時,他們對應的進程又會被啟動。

在決定哪些進程需要被kill的時候,Android系統會權衡這些進程跟用戶相關的重要性。比如,相對於那些承載這可見的activities的進程,系統會更容易的kill掉那些承載不再可見activities的進程。決定是否終結一個進程取決於這個進程裡面的組件運行的狀態。下面我們會討論kill進程時所用到的一些規則。

進程的生命周期

作為一個多任務的系統,Android 當然系統能夠盡可能長的保留一個應用進程。但是由於新的或者更重要的進程需要更多的內存,系統不得不逐漸終結老的進程來獲取內存。為了聲明哪些進程需要保留,哪些需要kill,系統根據這些進程裡面的組件以及這些組件的狀態為每個進程生成了一個“重要性層級” 。處於最低重要性層級的進程將會第一時間被清楚,接著時重要性高一點,然後依此類推,根據系統需要來終結進程。

在這個重要性層級裡面有5個等級。下面的列表按照重要性排序展示了不同類型的進程(第一種進程是最重要的,因此將會在最後被kill):

  1. Foreground 進程 一個正在和用戶進行交互的進程。 如果一個進程處於下面的狀態之一,那麼我們可以把這個進程稱為 foreground 進程:
    • 進程包含了一個與用戶交互的 Activity  (這個 Activity的 onResume() 方法被調用)。
    • 進程包含了一個綁定了與用戶交互的activity的 Service 。
    • 進程包含了一個運行在”in the foreground”狀態的 Service —這個 service 調用了 startForeground()方法。
    • 進程包含了一個正在運行的它的生命周期回調函數 (onCreate()onStart(), oronDestroy())的 Service 。
    • 進程包含了一個正在運行 onReceive() 方法的 BroadcastReceiver 。

    一般說來,任何時候,系統中只存在少數的 foreground 進程。 只有在系統內存特別緊張以至於都無法繼續運行下去的時候,系統才會通過kill這些進程來緩解內存壓力。在這樣的時候系統必須kill一些 (Generally, at that point, the device has reached a memory paging state,這句如何翻譯較好呢)foreground 進程來保證 用戶的交互有響應。

  2. Visible 進程 一個進程沒有任何 foreground 組件, 但是它還能影響屏幕上的顯示。 如果一個進程處於下面的狀態之一,那麼我們可以把這個進程稱為 visible 進程:
    • 進程包含了一個沒有在foreground 狀態的 Activity ,但是它仍然被用戶可見 (它的 onPause() 方法已經被調用)。這種情況是有可能出現的,比如,一個 foreground activity 啟動了一個 dialog,這樣就會讓之前的 activity 在dialog的後面部分可見。
    • 進程包含了一個綁定在一個visible(或者foreground)activity的 Service 。

    一個 visible 進程在系統中是相當重要的,只有在為了讓所有的foreground 進程正常運行時才會考慮去kill visible 進程。

  3. Service 進程 一個包含著已經以 startService() 方法啟動的 Service 的進程,同時還沒有進入上面兩種更高級別的種類。盡管 service 進程沒有與任何用戶所看到的直接關聯,但是它們經常被用來做用戶在意的事情(比如在後台播放音樂或者下載網絡數據),所以系統也只會在為了保證所有的foreground and visible 進程正常運行時kill掉 service 進程。
  4. Background 進程 一個包含了已不可見的activity的 進程 (這個 activity 的 onStop() 已經被調用)。這樣的進程不會直接影響用戶的體驗,系統也可以為了foreground 、visible 或者 service 進程隨時kill掉它們。一般說來,系統中有許多的 background 進程在運行,所以將它們保持在一個LRU (least recently used)列表中可以確保用戶最近看到的activity 所屬的進程將會在最後被kill。如果一個 activity 正確的實現了它的生命周期回調函數,保存了自己的當前狀態,那麼kill這個activity所在的進程是不會對用戶在視覺上的體驗有影響的,因為當用戶回退到這個 activity時,它的所有的可視狀態將會被恢復。查看 Activities 可以獲取更多如果保存和恢復狀態的文檔。
  5. Empty 進程 一個不包含任何活動的應用組件的進程。 這種進程存在的唯一理由就是緩存。為了提高一個組件的啟動的時間需要讓組件在這種進程裡運行。為了平衡進程緩存和相關內核緩存的系統資源,系統需要kill這些進程。

Android是根據進程中組件的重要性盡可能高的來評級的。比如,如果一個進程包含來一個 service 和一個可見 activity,那麼這個進程將會被評為 visible 進程,而不是 service 進程。

另外,一個進程的評級可能會因為其他依附在它上面的進程而被提升—一個服務其他進程的進程永遠不會比它正在服務的進程評級低的。比如,如果進程A中的一個 content provider 正在為進程B中的客戶端服務,或者如果進程A中的一個 service 綁定到進程B中的一個組件,進程A的評級會被系統認為至少比進程B要高。

因為進程裡面運行著一個 service 的評級要比一個包含background activities的進程要高,所以當一個 activity 啟動長時操作時,最好啟動一個 service 來做這個操作,而不是簡單的創建一個worker線程—特別是當這個長時操作可能會拖垮這個activity。比如,一個需要上傳圖片到一個網站的activity 應當開啟一個來執行這個上傳操作。這樣的話,即使用戶離開來這個activity也能保證上傳動作在後台繼續。使用 service 可以保證操作至少處於”service process” 這個優先級,無論這個activity發生了什麼。這也是為什麼 broadcast receivers 應該使用 services 而不是簡單的將耗時的操作放到線程裡面。

線程

當一個應用啟動的時候,系統會為它創建一個線程,稱為“主線程”。這個線程很重要因為它負責處理調度事件到相關的 user interface widgets,包括繪制事件。你的應用也是在這個線程裡面與來自Android UI toolkit (包括來自 android.widget 和 android.view 包的組件)的組件進行交互。因此,這個主線程有時候也被稱為 UI 線程。

系統沒有為每個組件創建一個單獨的線程。同一進程裡面的所有組件都是在UI 線程裡面被實例化的,系統對每個組件的調用都是用過這個線程進行調度的。所以,響應系統調用的方法(比如 onKeyDown() 方法是用來捕捉用戶動作或者一個生命周期回調函數)都運行在進程的UI 線程裡面。

比如,當用戶點擊屏幕上的按鈕,你的應用的UI 線程會將這個點擊事件傳給 widget,接著這個widget設置它的按壓狀態,然後發送一個失效的請求到事件隊列。這個UI 線程對請求進行出隊操作,然後處理(通知這個widget重新繪制自己)。

當你的應用與用戶交互對響應速度的要求比較高時,這個單線程模型可能會產生糟糕的效果(除非你很好的實現了你的應用)。特別是,當應用中所有的事情都發生在UI 線程裡面,那些訪問網絡數據和數據庫查詢等長時操作都會阻塞整個UI線程。當整個線程被阻塞時,所有事件都不能被傳遞,包括繪制事件。這在用戶看來,這個應用假死了。甚至更糟糕的是,如果UI 線程被阻塞幾秒(當前是5秒)以上,系統將會彈出臭名昭著的 “application not responding” (ANR) 對話框。這時用戶可能選擇退出你的應用甚至卸載。

另外,Android的UI 線程不是線程安全的。所以你不能在一個worker 線程操作你的UI—你必須在UI線程上對你的UI進行操作。這有兩條簡單的關於Android單線程模型的規則:

  1. 不要阻塞 UI 線程
  2. 不要在非UI線程裡訪問 Android UI toolkit

Worker 線程

由於上面對單一線程模型的描述,保證應用界面的及時響應同時UI線程不被阻塞變得很重要。如果你不能讓應用裡面的操作短時被執行玩,那麼你應該確保把這些操作放到獨立的線程裡(“background” or “worker” 線程)。

比如,下面這段代碼在一個額外的線程裡面下載圖片並在一個 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 toolkit—這個例子在一個worker線程修改了 ImageView 。這會導致不可預期的結果,而且還難以調試。

為了修復這個問題,Android提供了幾個方法從非UI線程訪問Android UI toolkit 。詳見下面的這個列表:

  • 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 進行操作。

然而,隨著操作復雜性的增長,代碼會變得越來越復雜,越來越難維護。為了用worker 線程處理更加復雜的交互,你可以考慮在worker線程中使用Handler ,用它來處理UI線程中的消息。也許最好的方案就是繼承 AsyncTask 類,這個類簡化了需要同UI進行交互的worker線程任務的執行。

使用 AsyncTask

AsyncTask 能讓你在UI上進行異步操作。它在一個worker線程裡進行一些阻塞操作然後把結果交給UI主線程,在這個過程中不需要你對線程或者handler進行處理。

使用它,你必須繼承 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是安全的了,代碼也更加簡單了,因為AsyncTask把worker線程裡做的事和UI線程裡要做的事分開了。

你應該閱讀一下 AsyncTask 的參考文檔以便更好的使用它。下面就是一個對 AsyncTask 如何作用的快速的總覽:

  • 你可以具體設置參數的類型,進度值,任務的終值,使用的范型
  •  doInBackground() 方法自動在 worker 線程執行
  • onPreExecute()onPostExecute(), 和 onProgressUpdate() 方法都是在UI線程被調用
  •  doInBackground() 的返回值會被送往 onPostExecute()方法
  • 你可以隨時在 doInBackground()方法裡面調用 publishProgress() 方法來執行UI 線程裡面的onProgressUpdate() 方法
  • 你可以從任何線程取消這個任務

注意: 你在使用worker線程的時候可能會碰到的另一個問題就是因為runtime configuration change (比如用戶改變了屏幕的方向)導致你的activity不可預期的重啟,這可能會kill掉你的worker線程。為了解決這個問題你可以參考 Shelves 這個項目。

線程安全的方法

在某些情況下,你實現的方法可能會被多個線程所調用,因此你必須把它寫出線程安全的。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved