Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android之進程和線程簡單分析

Android之進程和線程簡單分析

編輯:關於Android編程

當某個應用組件啟動且該應用沒有運行其他任何組件時,Android 系統會使用單個執行線程為應用啟動新的 Linux 進程。(即啟動一個進程)。

默認情況下,同一應用的所有組件在相同的進程和線程(稱為“主”線程)中運行。 如果某個應用組件啟動且該應用已存在進程(因為存在該應用的其他組件),則該組件會在此進程內啟動並使用相同的執行線程。您也可以為新組件啟動子線程 。

但是,您可以安排應用中的其他組件在單獨的進程中運行,並為任何進程創建額外的線程.

進程


默認情況下,同一應用的所有組件均在相同的進程中運行,且大多數應用都不會改變這一點。 但是,如果您發現需要控制某個組件所屬的進程,則可在清單文件中執行此操作。

各類組件元素的清單文件條目—、—均支持 android:process 屬性,此屬性可以指定該組件應在哪個進程運行。您可以設置此屬性,使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。 此外,您還可以設置 android:process,使不同應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 並使用相同的證書進行簽署。

此外, 元素還支持 android:process 屬性,以設置適用於所有組件的默認值。

如果內存不足,而其他為用戶提供更緊急服務的進程又需要內存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷毀。 當這些組件需要再次運行時,系統將為它們重啟進程。

進程生命周期

Android 系統將盡量長時間地保持應用進程,但為了新建進程或運行更重要的進程,最終需要清除舊進程來回收內存。 為了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的進程,然後是重要性略遜的進程,依此類推,以回收系統資源。

重要性層次結構一共有 5 級

(第一個進程最重要,將是最後一個被終止的進程)

前台進程

正在跟用戶進行交互的進程。如果一個進程滿足以下任一條件,即視為前台進程:

調用用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法) 調用某個 Service,後者綁定到用戶正在交互的 Activity。解析:意思是有一個應用(進程)有正在交互的activity,此時新建一個應用,通過bindActivity()綁定前一個應用,那麼該應用就自動升級為前台進程。 調用正在“前台”運行的 Service(服務已調用 startForeground()) 調用正執行一個生命周期回調的 Service(onCreate()、onStart() 或 onDestroy()。解析:google為保證service能正常啟動,防止中途應用就退出導致service啟動不成功,所以將正在運行初始化方法以及銷毀方法的service所在進程自動升級為前台進程。 調用正執行其 onReceive() 方法的 BroadcastReceiver。解析:同上

通常,在任意給定時間前台進程都為數不多。只有在內在不足以支持它們同時繼續運行這一萬不得已的情況下,系統才會終止它們。 此時,設備往往已達到內存分頁狀態,因此需要終止一些前台進程來確保用戶界面正常響應。
補充:非用戶行為關閉的進程,即因為內存不足或其他因素導致進程關閉,系統會重新啟動進程。

可見進程

沒有任何前台組件,但可見,仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視為可見進程:

調用不在前台、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,如果前台 Activity 啟動了一個對話框,這時該Activity調用 onPause(),失去焦點。即可見卻不在前台。那麼該進程也變成可見進程。 調用綁定到可見Activity 的 Service

可見進程被視為是極其重要的進程,除非為了維持所有前台進程同時運行而必須終止,否則系統不會終止這些進程。

服務進程

使用 startService() 方法啟動的服務且不屬於上述兩個更高類別進程的進程。補充:通過bindService()啟動的服務不屬於服務進程(即不會啟動一個新的進程),而與Activity建立連接,與activity共生死。

盡管服務進程與用戶所見內容沒有直接關聯,但是它們通常在執行一些用戶關心的操作(例如,在後台播放音樂或從網絡下載數據)。因此,除非內存不足以維持所有前台進程和可見進程同時運行,否則系統會讓服務進程保持運行狀態。

後台進程

擁有不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前台進程、可見進程或服務進程使用。

補充:通常會有很多後台進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。
如果某個 Activity 正確實現了生命周期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。 有關保存和恢復狀態的信息(解析,當內存不夠用時,會調用activity的onSaveInstanceState(Bundle outState),將本Activity的信息保存在Bundle裡,當用戶重新回到Activity時,會調用Activity的 onCreate()以及onRestoreInstanceState(Bundle savedInstanceState)方法重新創建Activity,所以並不會對用戶行為造成影響

空進程

不含任何活動應用組件的進程,即不包含android四大組件。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間。 為使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程。

根據進程中當前活動組件的重要程度,Android 會將進程評定為它可能達到的最高級別。例如,如果某進程托管著服務和可見 Activity,則會將此進程評定為可見進程,而不是服務進程。

此外,一個進程的級別可能會因其他進程對它的依賴而有所提高,即服務於另一進程的進程其級別永遠不會低於其所服務的進程。 例如,如果進程 A 中的內容提供程序為進程 B 中的客戶端提供服務,或者如果進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視為至少與進程 B 同樣重要。

線程


應用啟動時,系統會為應用創建一個名為“主線程”的執行線程。 此線程非常重要,因為它負責將事件分派給相應的用戶界面,其中包括繪圖事件。 此外,它也是應用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程。因此,主線程有時也稱為 UI 線程

系統絕對不會為每個組件實例創建單獨的線程。運行於同一進程的所有組件均在 UI 線程中實例化,並且對每個組件的系統調用均由該線程進行分派。因此,響應系統回調的方法(例如,報告用戶操作的 onKeyDown() 或生命周期回調方法)始終在進程的 UI 線程中運行。

例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給widget,而widget反過來又設置其按下狀態,並將無效請求發布到事件隊列中。UI 線程從隊列中取消該請求並通知小工具應該重繪自身。

單線程的缺點

在應用執行繁重的任務以響應用戶交互時,除非正確實施應用,否則這種單線程模式可能會導致性能低下。 特別地,如果 UI 線程需要處理所有任務,則執行耗時很長的操作(例如,網絡訪問或數據庫查詢)將會阻塞整個 UI。一旦線程被阻塞,將無法分派任何事件,包括繪圖事件。從用戶的角度來看,應用顯示為掛起。 更糟糕的是,如果 UI 線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘),用戶就會看到一個讓人厭煩的“應用無響應”(ANR) 對話框。如果引起用戶不滿,他們可能就會決定退出並卸載此應用。

此外,Android UI 工具包並非線程安全工具包。因此,您不得通過工作線程操縱 UI,而只能通過 UI 線程操縱用戶界面。因此,Android 的單線程模式必須遵守兩條規則:

1.不要阻塞 UI 線程
2.不要在 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 線程。以下列出了幾種有用的方法:

1.Activity.runOnUiThread(Runnable)
2.View.post(Runnable)
3.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


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 {
    /** 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 線程內完成。

注意:使用工作線程時可能會遇到另一個問題,即:運行時配置變更(例如,用戶更改了屏幕方向)導致 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() 將應用綁定到服務上。

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