處理程序和執行緒總覽

應用程式元件啟動,且應用程式沒有任何其他執行中的元件時,Android 系統會以單一執行執行緒啟動應用程式的新 Linux 程序。根據預設,同一個應用程式的所有元件都會在相同的程序與執行緒中執行,此程序稱為「主」執行緒。

如果應用程式元件啟動,且該應用程式已有程序,因為應用程式中的其他元件已經啟動,該元件會在該程序內啟動,並使用相同的執行執行緒。不過,您可以安排應用程式中的不同元件,以獨立程序執行,您也可以為任何程序建立其他執行緒。

本文件討論 Android 應用程式中的處理程序和執行緒運作方式。

處理程序

根據預設,應用程式的所有元件都會在同一個程序中執行,而且大多數應用程式不會變更這項設定。不過,如果您需要控制特定元件所屬的程序,可以在資訊清單檔案中進行。

每種元件元素類型的資訊清單項目 (<activity><service><receiver><provider>) 支援 android:process 屬性,可用於指定元件的執行程序。您可以設定這項屬性,讓每個元件以自己的程序執行,或讓某些元件共用程序,其他則不會。

您也可以設定 android:process,讓不同應用程式的元件在同一程序中執行,前提是應用程式共用相同的 Linux 使用者 ID 並以相同的憑證簽署。

<application> 元素也支援 android:process 屬性,可用於設定套用至所有元件的預設值。

Android 可能會在某個時間點選擇關閉程序,但如果其他程序需要資源,較立即為使用者提供資源,Android 可能就會決定關閉處理程序。如果是在關閉程序中執行的應用程式元件,將會遭到刪除。當這些元件有作用時,系統就會再次啟動程序。

決定要關閉哪些程序時,Android 系統會權衡對使用者的重要性。舉例來說,相較於代管可見活動的程序,也能關閉未顯示在螢幕上的代管活動程序。因此,決定是否要終止程序,取決於該程序中執行的元件狀態。

請參閱「程序和應用程式生命週期」一文,瞭解程序生命週期的詳細資料及其與應用程式狀態的關係。

Threads

應用程式啟動時,系統會為應用程式建立執行作業執行緒,這個執行緒稱為「主執行緒」。這個執行緒非常重要,因為它負責將事件分派到適當的使用者介面小工具,包括繪圖事件。也是應用程式與 Android UI 工具包的 android.widgetandroid.view 套件元件互動的執行緒。因此,主執行緒有時稱為「UI 執行緒」。然而,在特殊情況下,應用程式的主要執行緒可能不是其 UI 執行緒。詳情請參閱「執行緒註解」。

系統「不會」為元件的每個執行個體建立獨立的執行緒。在相同程序中執行的所有元件都會在 UI 執行緒中執行個體化,而對每個元件的系統呼叫都會從該執行緒分派。因此,回應系統回呼的方法 (例如用於回報使用者動作的 onKeyDown(),或生命週期回呼方法),一律在程序的 UI 執行緒中執行。

舉例來說,當使用者輕觸畫面上的按鈕時,應用程式的 UI 執行緒會將觸控事件分派給小工具,進而設定其按下的狀態,並將無效要求發布至事件佇列。UI 執行緒會將要求移出佇列,並通知小工具自行重新繪製。

除非您正確實作應用程式,否則當應用程式為了回應使用者互動而執行大量工作時,這種單一執行緒模型可能會導致效能不佳。在 UI 執行緒中執行長時間作業 (例如網路存取權或資料庫查詢) 會封鎖整個 UI。封鎖執行緒後,就無法分派任何事件,包括繪圖事件。

從使用者的角度來看,應用程式似乎會停止運作。更糟的是,如果 UI 執行緒封鎖超過幾秒,系統就會向使用者顯示「應用程式無回應」(ANR) 對話方塊。使用者可能會決定退出應用程式,甚至解除安裝應用程式。

請注意,Android UI 工具包「並非」確保執行緒安全。因此,請勿透過背景工作執行緒操控 UI。透過 UI 執行緒對使用者介面執行所有操作。Android 單一執行緒模型有兩項規則:

  1. 請勿封鎖 UI 執行緒。
  2. 請勿從 UI 執行緒外部存取 Android UI 工具包。

工作站執行緒

基於這種單一執行緒模型,請務必針對不會封鎖 UI 執行緒的應用程式 UI 回應速度。如果您有不會立即執行的作業,請務必分別在「背景」或「工作站」執行緒中執行這些作業。請注意,您無法從 UI (亦即主要執行緒) 以外的任何執行緒更新 UI。

為協助您遵循這些規則,Android 提供多種從其他執行緒存取 UI 執行緒的方式。下列為可協助您解決問題的方法:

以下範例使用 View.post(Runnable)

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

這項實作在執行緒安全,因為背景作業是從另一個執行緒完成,而 ImageView 則一律透過 UI 執行緒進行處理。

然而,隨著作業複雜度增加,這類程式碼可能會變得複雜且難以維護。如要處理與工作站執行緒進行更複雜的互動,可以考慮在工作站執行緒中使用 Handler 處理 UI 執行緒傳送的訊息。如需如何安排背景執行緒工作,並傳回至 UI 執行緒的完整說明,請參閱背景工作總覽

執行緒安全方法

在某些情況下,您實作的方法是從多個執行緒呼叫,因此必須編寫為安全執行緒。

這主要適用於可以遠端呼叫的方法,例如繫結服務中的方法。如果對 IBinder 中實作的方法呼叫,且與執行 IBinder 的程序相同,該方法就會在呼叫端的執行緒中執行。不過,當呼叫源自於其他程序時,此方法會在系統維護的執行緒集區中所選執行緒執行,而與 IBinder 具有相同的程序。而是在處理程序的 UI 執行緒中執行。

舉例來說,系統會透過服務程序的 UI 執行緒呼叫服務的 onBind() 方法,而 onBind() 回傳的物件中實作的方法 (例如實作遠端程序呼叫 (RPC) 方法的子類別),則是從集區中的執行緒呼叫。由於一項服務可能有多個用戶端,因此多個集區執行緒可以同時參與同一個 IBinder 方法,因此必須實作 IBinder 方法,才能確保執行緒安全。

同樣地,內容供應器也能接收來自其他程序的資料要求。ContentResolverContentProvider 類別會隱藏處理處理序間通訊 (IPC) 的細節,但回應這些要求的 ContentProvider 方法 (query()insert()delete()update()getType() 方法) 是從內容供應器程序中的執行緒集區呼叫,而不是程序的 UI 執行緒。由於這些方法可能會同時從任意數量的執行緒呼叫,因此也必須實作為執行緒安全。

處理序間通訊

Android 使用遠端程序呼叫 (RPC) 提供處理 IPC 的機制,活動或其他應用程式元件會呼叫方法,但在其他程序中從遠端執行,並將任何結果傳回呼叫呼叫端。這包括將方法呼叫及其資料分解到作業系統能夠理解的層級,將其從本機程序和位址空間傳送至遠端程序和位址空間,然後重組並重新進行呼叫。

接著,回傳值會以相反方向傳送。Android 提供執行這些 IPC 交易的所有程式碼,讓您可以專注於定義和實作 RPC 程式設計介面。

如要執行 IPC,應用程式必須使用 bindService() 繫結至服務。詳情請參閱服務總覽