活動生命週期

當使用者瀏覽、離開及返回應用程式時,應用程式中的 Activity 執行個體會在生命週期中轉換不同狀態。Activity 類別提供多個回呼,可讓活動知道狀態何時變更,或系統正在建立、停止或繼續活動,或刪除活動所在的程序。

在生命週期回呼方法中,您可以宣告使用者離開及重新進入活動時,活動的行為。舉例來說,如果您正在建構串流影片播放器,可以在使用者切換至其他應用程式時暫停影片並終止網路連線。當使用者返回時,您可以重新連線至網路,讓使用者從相同位置繼續播放影片。

每個回呼都能讓您執行適合特定狀態變更的特定工作。在適當的時間執行適當的工作,並妥善處理轉換,可讓應用程式更穩健、效能更佳。舉例來說,妥善實作生命週期回呼,可協助應用程式避免下列情況:

  • 使用者在使用應用程式時接到來電或切換到其他應用程式,導致應用程式異常終止。
  • 在使用者未主動使用時,消耗寶貴的系統資源。
  • 使用者離開應用程式後再返回,進度會遺失。
  • 螢幕在橫向和直向之間旋轉時,發生當機或遺失使用者進度的情況。

本文將詳細說明活動生命週期。本文一開始會先說明生命週期範式,接著,本節會說明各個回呼:執行時內部會發生什麼情況,以及您需要在回呼期間實作什麼。

接著,本文會簡要介紹活動狀態與程序容易遭到系統終止之間的關係。最後,本文將討論與活動狀態轉換相關的幾個主題。

如要瞭解如何處理生命週期,包括最佳做法指南,請參閱「Jetpack Compose 中的生命週期」和「儲存 UI 狀態」。如要瞭解如何結合活動和架構元件,建構出完善且品質媲美正式版的應用程式,請參閱「應用程式架構指南」。

活動生命週期概念

為瀏覽活動生命週期各階段之間的轉換,Activity 類別提供一組核心的六個回呼:onCreate、onStart、onResume、onPause、onStop 和 onDestroy。當活動進入新狀態時,系統會叫用這些回呼。

圖 1 呈現這個範例的視覺化表示。

圖 1. 活動生命週期的簡化圖。

當使用者開始離開活動時,系統會呼叫方法來拆解活動。在某些情況下,活動只會部分拆除,且仍會保留在記憶體中,例如使用者切換至其他應用程式時。在這些情況下,活動仍可返回前景。

如果使用者返回活動,活動會從使用者離開的位置繼續。除了少數例外狀況,應用程式在背景執行時無法啟動活動。

系統終止特定程序和其中活動的可能性,取決於活動當時的狀態。如要進一步瞭解狀態與容易遭到剔除之間的關係,請參閱「活動狀態和從記憶體中剔除」一節。

視活動的複雜程度而定,您可能不需要實作所有生命週期方法。不過,請務必瞭解各項功能,並實作可讓應用程式符合使用者預期的功能。

Compose 和生命週期

在 Compose 中,請避免直接在 onStart 或 onResume 等活動回呼中,放置商業邏輯或手動觀察器設定。請改用生命週期感知效果和狀態感知觀察器,自動配合 UI 在畫面上的顯示狀態。

  • 生命週期感知收集:使用 collectAsStateWithLifecycle 消耗 ViewModel 的流程。當 UI 進入「已啟動」狀態時,這個 API 會自動開始收集資料,並在 UI 進入背景時停止收集,避免不必要的資源消耗。以狀態形式收集流程後,您可以使用 LifecycleEffects 在生命週期事件發生時執行程式碼。
  • 邏輯流程:使用這些 API 時,UI 會透過組合樹狀結構自然回應生命週期狀態,確保只有在使用者主動與元件互動時,才會執行商業邏輯。

如要進一步瞭解 Compose 和生命週期,請參閱「Jetpack Compose 中的生命週期」。

生命週期回呼

本節提供活動生命週期中使用的回呼方法概念和實作資訊。

部分動作屬於活動生命週期方法。不過,請將實作依附元件動作的程式碼放在元件中,而非活動生命週期方法中。為此,您必須讓依附元件感知生命週期。如要瞭解如何讓依附元件感知生命週期,請參閱「Jetpack Compose 中的生命週期」。

onCreate

您必須實作這個回呼,系統首次建立活動時會觸發這個回呼。建立活動後,活動會進入「Created」狀態。在 onCreate 方法中,執行基本的應用程式啟動邏輯,在活動的整個生命週期中,這項邏輯只會執行一次。

舉例來說,onCreate 的實作可能會將資料繫結至清單、將活動與 ViewModel 建立關聯,以及將部分類別範圍變數例項化。這個方法會接收 savedInstanceState 參數,也就是包含活動先前儲存狀態的 Bundle 物件。如果活動先前從未存在,Bundle 物件的值會是空值。

如果生命週期感知元件與活動生命週期連結,就會收到 ON_CREATE 事件。系統會呼叫加上 @OnLifecycleEvent 註解的方法,讓生命週期感知元件執行建立狀態所需的任何設定碼。

以下範例說明如何在最基本的活動中整合 Text 可組合項:

class ExampleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent { // In here, we can call composables!
            MaterialTheme {
                Greeting(name = "compose")
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

活動不會停留在「已建立」狀態。onCreate 方法執行完畢後,活動會進入「已啟動」狀態,系統會快速連續呼叫 onStart 和 onResume 方法。

onStart

當活動進入「已啟動」狀態時,系統會叫用 onStart。這項呼叫會讓使用者看到活動,因為應用程式會準備讓活動進入前景並可供互動。例如,這個方法是初始化維護 UI 的程式碼。

當活動進入「已啟動」狀態時,與活動生命週期相關聯的任何生命週期感知元件都會收到 ON_START 事件。

onStart 方法會快速完成,且與「已建立」狀態相同,活動不會停留在「已啟動」狀態。回呼完成後,活動會進入「Resumed」狀態,系統則會叫用 onResume 方法。

onResume

當活動進入「已恢復」狀態時,會移至前景,系統會叫用 onResume 回呼。在這個狀態下,應用程式會與使用者互動。應用程式會維持在這個狀態,直到發生某些事件,導致應用程式失去焦點,例如裝置接到來電、使用者前往其他活動,或裝置螢幕關閉。

當活動進入「已恢復」狀態時,與活動生命週期相關聯的任何生命週期感知元件都會收到 ON_RESUME 事件。生命週期元件可在此啟用任何需要在元件可見且位於前景時執行的功能,例如啟動相機預覽。

發生中斷事件時,活動會進入「Paused」狀態,系統會叫用 onPause 回呼。

如果活動從「已暫停」狀態返回「已恢復」狀態,系統會再次呼叫 onResume 方法。因此,請實作 onResume,初始化在 onPause 期間釋出的元件,並執行每次活動進入「已恢復」狀態時必須執行的任何其他初始化作業。

以下範例說明生命週期感知元件在收到 ON_RESUME 事件時存取相機:

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

LifecycleObserver 收到 ON_RESUME 事件後,上述程式碼會初始化相機。不過,在多視窗模式下,即使活動處於暫停狀態,也可能完全可見。舉例來說,當應用程式處於多視窗模式,且使用者輕觸不含您活動的視窗時,您的活動就會進入「已暫停」狀態。

如果只想在應用程式處於「已恢復」狀態 (在前景顯示並處於活動狀態) 時啟用相機,請在先前示範的 ON_RESUME 事件後初始化相機。如果想在活動暫停但仍可見時 (例如在多視窗模式下) 保持相機啟用狀態,請在 ON_START 事件後初始化相機。

不過,如果活動處於「已暫停」狀態時,相機仍保持啟用,可能會導致多視窗模式下其他「已恢復」的應用程式無法存取相機。有時,您可能需要在活動暫停時保持相機啟用,但這樣做可能會降低整體使用者體驗。

因此,請仔細思考在多視窗模式下,生命週期的哪個階段最適合控管共用系統資源。如要進一步瞭解如何支援多視窗模式,請參閱「支援多視窗模式」。

無論您選擇在哪個建構事件中執行初始化作業,請務必使用對應的生命週期事件來釋出資源。如果您在 ON_START 事件後初始化某些內容,請在 ON_STOP 事件後釋出或終止。如果您在 ON_RESUME 事件後初始化,請在 ON_PAUSE 事件後釋出。

上述程式碼片段會將相機初始化程式碼放在生命週期感知元件中。您也可以將這段程式碼直接放入活動生命週期回呼,例如 onStart 和 onStop,但我們不建議這麼做。將這項邏輯新增至獨立的生命週期感知元件,即可在多個活動中重複使用該元件,不必複製程式碼。如要瞭解如何建立生命週期感知元件,請參閱「Jetpack Compose 中的生命週期」。

onPause

系統會呼叫這個方法,做為使用者離開活動的第一個跡象,但這不一定代表活動正在刪除。這表示活動已不在前景,但如果使用者處於多視窗模式,仍可看到該活動。Activity 可能會因為多種原因進入這個狀態:

  • 中斷應用程式執行的事件 (如 onResume 回呼一節所述) 會暫停目前的活動。這是最常見的情況。
  • 在多視窗模式下,任何時候都只有一個應用程式會獲得焦點,系統會暫停所有其他應用程式。
  • 開啟新的半透明活動 (例如對話方塊) 時,會暫停其覆蓋的活動。只要活動部分可見但未聚焦,就會保持暫停狀態。

當活動進入「已暫停」狀態時,與活動生命週期相關聯的任何生命週期感知元件都會收到 ON_PAUSE 事件。此時,生命週期元件可以停止任何不需要在前景執行的功能,例如停止相機預覽。

當 Activity 處於「已暫停」狀態,且您預期很快就會恢復時,請使用 onPause 方法暫停或調整無法繼續或可能繼續進行的作業。

您也可以使用 onPause 方法釋出系統資源、感應器控制代碼 (例如 GPS),或在活動暫停且使用者不需要時,釋出任何會影響電池續航力的資源。

不過,如 onResume 相關章節所述,如果應用程式處於多視窗模式,暫停的活動可能仍會完整顯示。建議使用 onStop 而非 onPause,完整釋出或調整 UI 相關資源和作業,進一步支援多視窗模式。

以下 LifecycleObserver 對 ON_PAUSE 事件做出反應的範例,與前述 ON_RESUME 事件範例相對應,釋出在收到 ON_RESUME 事件後初始化的相機:

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

在這個範例中,相機釋放程式碼會在 LifecycleObserver 收到 ON_PAUSE 事件後執行。

onPause 執行時間非常短,不一定有足夠的時間執行儲存作業。因此,請不要使用 onPause 儲存應用程式或使用者資料、發出網路呼叫或執行資料庫交易。這類工作可能無法在方法完成前完成。

請改為在 onStop 期間執行高負載關機作業。如要進一步瞭解 onStop 期間適合執行的作業,請參閱下一節。如要進一步瞭解如何儲存資料,請參閱「儲存及還原狀態」一節。

onPause 方法完成並不代表活動會離開「已暫停」狀態。活動會維持在這個狀態,直到恢復或完全不向使用者顯示。如果活動繼續,系統會再次叫用 onResume 回呼。

如果活動從「暫停」狀態返回「已恢復」狀態,系統會將 Activity 執行個體保留在記憶體中,並在系統叫用 onResume 時重新叫用該執行個體。在這種情況下,您不需要重新初始化在任何回呼方法中建立的元件,直到進入「已恢復」狀態。如果活動完全不可見,系統會呼叫 onStop。

onStop

當使用者無法再看到活動時,活動會進入「已停止」狀態,系統會叫用 onStop 回呼。如果新啟動的活動覆蓋整個螢幕,就可能發生這種情況。當活動執行完畢且即將終止時,系統也會呼叫 onStop。

當活動進入「已停止」狀態時,與活動生命週期相關聯的所有生命週期感知元件都會收到 ON_STOP 事件。當元件未顯示在畫面上時,生命週期元件可以停止任何不需要執行的功能。

在 onStop 方法中,釋出或調整應用程式未向使用者顯示時不需要的資源。舉例來說,應用程式可能會暫停動畫,或從精確位置更新切換為粗略位置更新。使用 onStop 而非 onPause,代表即使使用者在多視窗模式下查看活動,UI 相關工作仍會繼續。

此外,請使用 onStop 執行相對耗用 CPU 資源的關機作業。舉例來說,如果找不到更適合將資訊儲存到資料庫的時間,可以在 onStop 期間執行這項操作。以下範例說明 onStop 的實作方式,可將草稿記事內容儲存至永久儲存空間:

override fun onStop() {
    super.onStop()

    // Delegate the save operation to the ViewModel, which handles the
    // background thread operations (e.g., using Kotlin Coroutines and Room).
    noteViewModel.saveDraft()
}

當活動進入「已停止」狀態時,Activity 物件會保留在記憶體中:它會維護所有狀態和成員資訊,但不會附加至視窗管理員。活動恢復時,會重新叫用這項資訊。

活動進入「已停止」狀態後,可能會返回與使用者互動,也可能結束執行並消失。如果活動恢復,系統會叫用 onRestart。如果 Activity 已完成執行,系統會呼叫 onDestroy。

onDestroy

系統會在活動刪除前呼叫 onDestroy。系統叫用這個回呼的原因有兩個:

  1. 活動即將結束,因為使用者完全關閉活動,或在活動中呼叫 finish。
  2. 系統因設定變更 (例如裝置旋轉或進入多視窗模式) 而暫時刪除活動。

當活動進入刪除狀態時,與活動生命週期相關聯的任何生命週期感知元件都會收到 ON_DESTROY 事件。生命週期元件可在 Activity 刪除前,清除所需內容。

請勿在 Activity 中加入邏輯來判斷其刪除原因,而是使用 ViewModel 物件來包含 Activity 的相關檢視資料。如果 Activity 因設定變更而重新建立,ViewModel 則不需要執行任何操作,因為它會保留並提供給下一個 Activity 執行個體。

如果未重新建立 Activity,系統會呼叫 ViewModel 的 onCleared 方法,以便在刪除前清除所需資料。您可以使用 isFinishing 方法區分這兩種情況。

如果活動即將結束,onDestroy 是活動收到的最後一個生命週期回呼。如果因設定變更而呼叫 onDestroy,系統會立即建立新的活動例項,然後在新設定中對該新例項呼叫 onCreate。

onDestroy 回呼會釋出先前未釋出的所有資源,例如 onStop。

活動狀態和從記憶體中移除

系統會在需要釋出 RAM 時終止程序。系統終止特定程序的可能性,取決於程序當時的狀態。而程序狀態則取決於程序中執行的活動狀態。表 1 顯示程序狀態、活動狀態,以及系統終止程序的可能性之間的關聯。只有在程序未執行其他類型的應用程式元件時,才適用這個表格。

遭殺害的可能性

程序狀態

最終活動狀態

最低

前景 (已取得或即將取得焦點)

已重新啟用

低

顯示 (未聚焦)

已啟動/已暫停

較高

背景 (隱藏)

已停止

最高

空白

已刪除

表 1. 程序生命週期與活動狀態之間的關係。

系統絕不會直接終止活動來釋出記憶體,而是會終止活動執行的程序,不僅會刪除活動,也會刪除在程序中執行的所有其他內容。如要瞭解如何在系統啟動的程序終止時,保留及還原活動的 UI 狀態,請參閱「儲存及還原狀態」一節。

使用者也可以在「設定」下使用應用程式管理工具終止對應的應用程式,藉此終止程序。

如要進一步瞭解程序,請參閱「程序和執行緒總覽」。

儲存及還原暫時性 UI 狀態

在設定變更 (例如旋轉或切換至多視窗模式) 的過程中,使用者會期望活動的 UI 狀態保持不變。不過,當這類設定變更發生時,系統會刪除活動,清除任何儲存在活動例項中的 UI 狀態。

同樣地,如果使用者暫時切換至其他應用程式後再返回,也會預期 UI 狀態維持不變。不過,當使用者離開且活動停止時,系統可能會刪除應用程式的程序。

當系統限制破壞活動時,請結合使用 ViewModel (用於複雜的商業邏輯和畫面狀態)、Jetpack Compose rememberSaveable API (用於輕量 UI 狀態) 和/或本機儲存空間,保留使用者的暫時 UI 狀態。如要進一步瞭解使用者期望與系統行為的比較,以及如何在系統啟動的活動和程序終止時,妥善保留複雜的 UI 狀態資料,請參閱「儲存 UI 狀態」。

rememberSaveable 會在幕後組合狀態,因此在設定變更和系統啟動的程序終止後,仍能自動保留狀態,提供流暢體驗,不需要活動層級的樣板。

執行個體狀態

在某些情況下,您的活動會因正常的應用程式行為而遭到刪除,例如使用者按下「返回」按鈕,或活動透過呼叫 finish 方法發出刪除信號。

當使用者按下「返回」或活動自行結束,導致活動遭到刪除時,系統和使用者對該 Activity 執行個體的概念都會永久消失。在這些情況下,使用者的預期與系統行為相符,您不需要執行任何額外工作。

不過,如果系統因系統限制 (例如設定變更或記憶體壓力) 而刪除活動,雖然實際的 Activity 例項已消失,但系統會記住該例項存在。如果使用者嘗試返回活動,系統會使用一組儲存的資料建立該活動的新例項,該資料描述活動刪除時的狀態。

系統用來還原先前狀態的已儲存資料稱為「執行個體狀態」。本質上,這是一組鍵/值組合。根據預設,系統會使用執行個體狀態儲存 UI 版面配置的基本資訊,例如使用者文字輸入或捲動位置。

您可以使用 rememberSaveable 連結至這項系統行為。如果活動例項遭到刪除並重新建立,系統會自動還原 rememberSaveable 內含的所有 UI 狀態,您不需要額外編寫活動層級程式碼。

不過,您的活動可能會有更複雜的狀態資訊需要還原,例如使用者資料、網路回應,或追蹤使用者進度的成員變數。例項狀態機制 (以及 rememberSaveable) 不適合用於保留大量資料,因為這項機制需要在主執行緒上進行序列化,並會耗用系統程序記憶體。

如要保留少量以上的資料,請採用結合本機永久儲存空間、ViewModel 類別和 Compose 狀態提升的方法,詳情請參閱「儲存 UI 狀態」。

使用 rememberSaveable 儲存簡單、輕量的 UI 狀態

當活動開始停止時,系統會準備將狀態資訊儲存至執行個體狀態套件。如要連結至這項系統行為,請直接在可組合函式中使用 rememberSaveable。rememberSaveable 會在重新建立活動時,自動儲存及還原暫時性 UI 狀態,例如使用者文字輸入或捲動位置。

如要儲存自訂的輕量狀態資訊 (例如使用者在遊戲中的進度),請使用 rememberSaveable 宣告狀態。Compose 架構會在背景處理執行個體狀態套件的序列化作業:

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

如要儲存永久資料 (例如使用者偏好設定或資料庫資料),請在活動位於前景時,把握適當時機。如果沒有這類機會,請在 onStop 方法期間儲存永久資料。

使用已儲存的執行個體狀態還原活動 UI 狀態

如果活動在先前遭到刪除後重新建立,系統會自動還原狀態。使用 rememberSaveable 時,您不需要編寫任何明確的還原邏輯、檢查空值套件,或覆寫活動回呼。初始化及儲存狀態的程式碼,也會在活動返回時順暢還原狀態:

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

活動和導覽

應用程式在生命週期內可能會多次在畫面之間轉換,例如使用者輕觸裝置的返回按鈕或選取新目的地。現代 Android 應用程式通常採用單一活動架構。應用程式不會為每個畫面啟動新的 Activity,而是託管單一 Activity,並使用 Navigation 元件,在該活動中切換可組合畫面。

如要瞭解如何實作新式 Compose 優先導覽,請參閱 Jetpack Compose Navigation 3 程式庫指南。

從一個活動啟動另一個活動

活動有時可能需要啟動另一個活動。舉例來說,當應用程式需要從目前畫面移至新畫面時,就會產生這項需求。

視活動是否要從即將啟動的新活動傳回結果而定,您可以使用 startActivity 方法或 startActivityForResult 方法啟動新活動。無論是哪種情況,您都必須傳入 Intent 物件。

Intent 物件會指定要啟動的確切活動,或說明要執行的動作類型。系統會為您選取適當的活動,甚至可能來自其他應用程式。Intent 物件也可以攜帶少量資料,供啟動的活動使用。如要進一步瞭解 Intent 類別,請參閱「意圖和意圖篩選器」。

startActivity

如果新啟動的活動不需要傳回結果,目前的活動可以呼叫 startActivity 方法來啟動。

在自己的應用程式中,您通常只需要啟動已知的活動。舉例來說,下列程式碼片段說明如何啟動名為 SignInActivity 的活動。

val context = LocalContext.current

Button(onClick = {
    val intent = Intent(context, SignInActivity::class.java)
    context.startActivity(intent)
}) {
    Text("Sign In")
}

啟動外部活動

雖然應用程式內部導覽是由 Navigation 處理,但 Activity 偶爾需要啟動其他活動。當您想利用外部應用程式執行特定動作 (例如開啟網路瀏覽器、傳送電子郵件或拍照) 時,通常會發生這種情況。

為此,您可以使用 Intent 物件描述要執行的動作類型,系統就會從其他應用程式啟動適當的活動。

舉例來說,如要讓使用者傳送電子郵件訊息,可以建立下列意圖:

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

如需啟動外部活動並取得結果 (例如要求相機應用程式拍照並傳回圖片),請使用新版 Activity 結果 API,而非已淘汰的 startActivityForResult 回呼。

協調活動

當一個活動啟動另一個活動時,兩者都會經歷生命週期轉換。第一個活動會停止運作,並進入「已暫停」或「已停止」狀態,而另一個活動則會建立。如果這些活動共用儲存至磁碟或其他位置的資料,請務必瞭解,第一個活動在第二個活動建立之前並未完全停止。而是啟動第二個活動的程序與停止第一個活動的程序重疊。

生命週期回呼的順序已明確定義,特別是當兩個活動位於同一個程序 (也就是同一個應用程式),且其中一個活動啟動另一個活動時。以下是 Activity A 啟動 Activity B 時的作業順序:

  1. 執行活動 A 的 onPause 方法。
  2. 活動 B 的 onCreate、onStart 和 onResume 方法會依序執行。活動 B 現在具有使用者焦點。
  3. 如果活動 A 不再顯示於螢幕上,系統會執行 onStop 方法。

這組生命週期回呼可讓您管理資訊從一個活動轉換到另一個活動的過程。

其他資源

如要進一步瞭解活動生命週期,請參閱下列其他資源:

查看內容