天天看點

android 程序與線程

前言

當手機上某個應用啟動(元件)且該應用沒有運作其他任何元件時,Android 系統會使用單個執行線程為應用啟動新的 Linux 程序。

(類似從應用中建立線程,開啟service),四大元件通過: 進入介紹 可以設定在不同的程序中運作

預設情況下,

同一應用的所有元件在相同的程序和線程中運作(主線程/ui線程)。 如果某個應用元件啟動且該應用已存在程序(因為存在該應用的其他元件),

則該元件會在此程序内啟動并使用相同的執行線程。 但是,

您可以安排應用中的其他元件在單獨的程序中運作,并為任何程序建立額外的線程。(程序包含線程)

本文檔介紹程序和線程在 Android 應用中的工作方式。

程序

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

各類元件元素的清單檔案條目—​

​activity,service,receiver​

​​ 和​

​provider​

​—均支援 android:process 屬性,此屬性可以指定該元件應在哪個程序運作。您可以設定此屬性,使每個元件均在各自的程序中運作,或者使一些元件共享一個程序,而其他元件則不共享。 此外,您還可以設定 android:process,使不同應用的元件在相同的程序中運作,但前提是這些應用共享相同的 Linux 使用者 ID 并使用相同的證書進行簽署。

此外,​

​application​

​​元素還支援 ​

​android:process​

​ 屬性,以設定适用于所有元件的預設值。

如果記憶體不足,而其他為使用者提供更緊急服務的程序又需要記憶體時,Android 可能會決定在某一時刻關閉某一程序。在被終止程序中運作的應用元件也會随之銷毀。 當這些元件需要再次運作時,系統将為它們重新開機程序。

決定終止哪個程序時,Android 系統将權衡它們對使用者的相對重要程度。例如,相對于托管可見 Activity 的程序而言,它更有可能關閉托管螢幕上不再可見的 Activity 的程序。 是以,是否終止某個程序的決定取決于該程序中所運作元件的狀态。 下面,我們介紹決定終止程序所用的規則。

程序生命周期

Android 系統将盡量長時間地保持應用程序,但為了建立程序或運作更重要的程序,最終需要移除舊程序來回收記憶體。 為了确定保留或終止哪些程序,系統會根據程序中正在運作的元件以及這些元件的狀态,将每個程序放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的程序,然後是重要性略遜的程序,依此類推,以回收系統資源。

重要性層次結構一共有 5 級。以下清單按照重要程度列出了各類程序(第一個程序最重要,将是最後一個被終止的程序):

前台程序

使用者目前操作所必需的程序。如果一個程序滿足以下任一條件,即視為前台程序:

托管使用者正在互動的 Activity(已調用 Activity 的 onResume() 方法)
托管某個 Service,後者綁定到使用者正在互動的 Activity
托管正在“前台”運作的 Service(服務已調用 startForeground())
托管正執行一個生命周期回調的 Service(onCreate()、onStart() 或 onDestroy())
托管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前台程序都為數不多。隻有在記憶體不足以支援它們同時繼續運作這一萬不得已的情況下,系統才會終止它們。 此時,裝置往往已達到記憶體分頁狀态,是以需要終止一些前台程序來確定使用者界面正常響應。      

可見程序

沒有任何前台元件、但仍會影響使用者在螢幕上所見内容的程序。 如果一個程序滿足以下任一條件,即視為可見程序:
托管不在前台、但仍對使用者可見的 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 的單線程模式必須遵守兩條規則:

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<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 參考文檔:

1.可以使用泛型指定參數類型、進度值和任務最終值

2.方法 doInBackground() 會在工作線程上自動執行

3.onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調用

4.doInBackground() 傳回的值将發送到 onPostExecute()

5.您可以随時在 doInBackground() 中調用publishProgress(),以在 UI 線程中執行 onProgressUpdate()

6.您可以随時取消任何線程中的任務      

注意:使用工作線程時可能會遇到另一個問題,即:運作時配置變更(例如,使用者更改了螢幕方向)導緻 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​

​ 程式設計接口即可。