1. 歡迎
簡介
在本練習中,您將進一步瞭解「活動生命週期」。生命週期是指活動在整個生命週期中經歷的一組狀態,也就是從系統建立活動、刪除活動,到回收活動資源為止。當使用者瀏覽不同的應用程式活動,以及來回切換應用程式時,活動會在生命週期的不同狀態之間轉換。

活動生命週期中的每個階段都有相對應的回呼方法,例如 onCreate()、onStart()、onPause() 等。當活動變更狀態時,系統會叫用相關聯的回呼方法。您已看過其中一種回呼方法:onCreate()。透過覆寫 Activity 類別中的生命週期回呼方法,您就可以變更活動的預設行為,回應使用者或系統動作。
活動狀態也會隨裝置設定變更而變化,例如使用者將裝置從直向轉為橫向時。發生這些設定變更時,系統會刪除活動,並以活動的預設狀態重新建立活動,而使用者可能會失去在活動中輸入的資訊。為避免造成使用者混淆,請務必開發相關應用程式功能,防止資料意外遺失。稍後,您將在本練習中利用設定變更進行實驗,瞭解如何因應裝置設定變更和其他活動生命週期事件,保留活動狀態。
在本練習中,您會在 TwoActivities 應用程式中新增記錄陳述式,觀察活動生命週期的變化。接著您會開始利用這些變更,瞭解如何在這些情況下處理使用者輸入內容。
必備知識
您必須具備以下能力:
- 在 Android Studio 中建立及執行應用程式專案。
- 將記錄陳述式新增至應用程式,並在「Logcat」窗格中查看這些記錄。
- 瞭解並能使用
Activity和Intent,且熟悉如何與這些項目互動。
課程內容
Activity生命週期的運作方式。- 啟動、暫停、停止和刪除
Activity的時機。 - 與
Activity變更相關聯的生命週期回呼方法。 - 設定變更等動作會如何影響
Activity生命週期事件。 - 如何跨生命週期事件保留
Activity狀態。
執行步驟
- 新增程式碼至先前練習中的 TwoActivities 應用程式,實作各種
Activity生命週期回呼,並納入記錄陳述式。 - 在應用程式執行期間,以及您與應用程式中各個
Activity互動時,觀察狀態變更情形。 - 修改應用程式,為意外重建的
Activity保留例項狀態。導致重建的原因包括使用者行為或裝置設定變更。
2. 應用程式總覽
在本練習中,您會新增程式碼至 TwoActivities 應用程式。此應用程式的外觀和行為與上一個程式碼研究室中大致相同。這包含兩個 Activity 實作項目,可讓使用者在這兩個項目之間傳送內容。您在本練習中對應用程式所做的變更,不會影響使用者看到的應用程式行為。
3. 工作 1:將生命週期回呼新增至 TwoActivities
在這項工作中,您要實作所有 Activity 生命週期回呼方法,讓系統在叫用這些方法時於 Logcat 顯示訊息。這些記錄訊息可讓您瞭解 Activity 生命週期變更狀態的時機,以及這類變更在應用程式執行期間造成的影響。
1.1 (選用) 複製 TwoActivities 專案
在本練習的工作中,您將修改在上一個練習中建構的 TwoActivities 專案。如要保留先前的 TwoActivities 專案,請按照「附錄:公用程式」中的步驟複製專案。
1.2 在 MainActivity 中實作回呼
- 在 Android Studio 中開啟 TwoActivities 專案,然後依序點選「Project」>「Android」窗格,開啟「MainActivity」。
- 在
onCreate()方法中加入下列記錄陳述式:
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
- 為
onStart()回呼新增覆寫,並提供該事件記錄的陳述式:
@Override
public void onStart(){
super.onStart();
Log.d(LOG_TAG, "onStart");
}
如需快速指令,請在 Android Studio 中依序選取「Code」>「Override Methods」。隨即顯示的對話方塊會列出可在類別中覆寫的所有可能方法。您從清單中選擇一或多個回呼方法後,系統就會為這些方法插入完整範本,包括父類別所需的呼叫。
- 使用
onStart()方法做為範本,實作onPause()、onRestart()、onResume()、onStop()和onDestroy()生命週期回呼。
所有回呼方法的簽章皆相同 (名稱除外)。如果您透過複製及貼上 onStart() 來建立其他回呼方法,請務必更新其中內容,才能在父類別中呼叫適當方法,並記錄正確方法。
- 執行應用程式。
- 按一下 Android Studio 底部的「Logcat」分頁標籤,就會顯示「Logcat」窗格。您應該會看到三個記錄訊息,顯示
Activity啟動後所經歷的三個生命週期狀態:
D/MainActivity: ------- D/MainActivity: onCreate D/MainActivity: onStart D/MainActivity: onResume
1.3 在 SecondActivity 中實作生命週期回呼
現在您已為 MainActivity 實作生命週期回呼方法,請為 SecondActivity 執行相同操作。
- 開啟「SecondActivity」SecondActivity。
- 在類別頂端新增
LOG_TAG變數的常數:
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
- 將生命週期回呼和記錄陳述式新增至第二個
Activity。您可以從MainActivity複製及貼上回呼方法。 - 將記錄陳述式新增至
finish()方法之前的returnReply()方法:
Log.d(LOG_TAG, "End SecondActivity");
1.4 在應用程式執行期間觀察記錄內容
- 執行應用程式。
- 按一下 Android Studio 底部的「Logcat」分頁標籤,就會顯示「Logcat」窗格。
- 在搜尋框中輸入「Activity」。Android Logcat 內容可能很冗長且雜亂。由於每個類別的
LOG_TAG變數都包含MainActivity或SecondActivity字詞,因此這個關鍵字可用來篩選記錄,只顯示您想瞭解的項目。

請使用應用程式進行實驗,並留意因不同動作而產生的生命週期事件。請特別嘗試下列動作:
- 照常使用應用程式,例如傳送訊息、回覆其他訊息。
- 使用返回按鈕,從第二個
Activity返回主要Activity。 - 使用應用程式列的向上箭頭,從第二個
Activity返回主要Activity。 - 在主要和第二個應用程式
Activity的不同時間點旋轉裝置,觀察畫面和記錄中的變化。 - 按下總覽按鈕 (主畫面右側的正方形按鈕),然後關閉應用程式 (輕觸「X」X)。
- 返回主畫面,重新啟動應用程式。
提示:如果您是在模擬器中執行應用程式,可以按下 Control+F11 鍵或 Control+Function+F11 鍵,模擬旋轉動作。
工作 1 解決方案程式碼
下列程式碼片段為第一項工作的解決方案程式碼。
MainActivity
下列程式碼片段為 MainActivity 中新增的程式碼,但不是整個類別。
onCreate() 方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Log the start of the onCreate() method.
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
}
其他生命週期方法:
@Override
protected void onStart() {
super.onStart();
Log.d(LOG_TAG, "onStart");
}
@Override
protected void onPause() {
super.onPause();
Log.d(LOG_TAG, "onPause");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(LOG_TAG, "onRestart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(LOG_TAG, "onResume");
}
@Override
protected void onStop() {
super.onStop();
Log.d(LOG_TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
}
SecondActivity
下列程式碼片段為 SecondActivity 中新增的程式碼,但不是整個類別。
SecondActivity 類別頂部:
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
returnReply() 方法:
public void returnReply(View view) {
String reply = mReply.getText().toString();
Intent replyIntent = new Intent();
replyIntent.putExtra(EXTRA_REPLY, reply);
setResult(RESULT_OK, replyIntent);
Log.d(LOG_TAG, "End SecondActivity");
finish();
}
其他生命週期方法:
與上述 MainActivity 相同。
4. 工作 2:儲存及還原 Activity 例項狀態
視系統資源和使用者行為而定,應用程式中每個 Activity 遭到刪除及重建的頻率可能會遠超出您的預期。
在上一節中旋轉裝置或模擬器時,您可能已注意到這項行為。旋轉裝置就是裝置「設定變更」的其中一個例子。雖然旋轉是最常見的設定變更,但所有設定變更都會導致目前的 Activity 遭到刪除,並重新建立為全新活動。如果您的程式碼沒有考量這項行為,那麼在設定變更時,Activity 版面配置可能會還原為預設外觀和初始值,而使用者可能會失去位置、資料或他們在應用程式中的進度狀態。
每個 Activity 的狀態都會以一組鍵/值組合的形式儲存在 Bundle 物件中,該物件稱為 Activity「例項狀態」。系統會在 Activity 停止前,將預設狀態資訊儲存到例項狀態 Bundle,並將 Bundle 傳遞至新的 Activity 例項來還原。
為避免在 Activity 意外刪除及重建時遺失資料,您需要實作 onSaveInstanceState() 方法。當 Activity 可能遭到刪除及重建時,系統會在 Activity 上的 onPause() 和 onStop() 之間呼叫這個方法。
儲存在例項狀態的資料,僅適用於目前應用程式工作階段內此特定 Activity 的例項。停止並重新啟動新的應用程式工作階段時,Activity 例項狀態會遺失,Activity 會還原為預設外觀。如果您需要在應用程式工作階段之間儲存使用者資料,請使用共用偏好設定或資料庫。後續練習會進一步說明這兩個概念。
2.1 使用 onSaveInstanceState() 儲存 Activity 例項狀態
您可能已注意到,旋轉裝置完全不會影響第二個 Activity 的狀態。這是因為第二個 Activity 的版面配置和狀態,是由版面配置和啟動該活動的 Intent 所產生。即使系統已重新建立 Activity,Intent 仍然存在,而且每次呼叫第二個 Activity 的 onCreate() 方法時,仍會使用該 Intent 中的資料。
此外,您可能會注意到,在每個 Activity 中,您輸入至訊息或回覆 EditText 元素的任何文字都會保留,即使裝置旋轉也一樣。這是因為版面配置中部分 View 元素的狀態資訊會在設定變更時自動儲存,而 EditText 目前的值就屬於這類資訊。
因此,您想瞭解的 Activity 狀態只有回覆標頭的 TextView 元素,以及主要 Activity 中的回覆文字。根據預設,這兩個 TextView 元素不會顯示,只有在您從第二個 Activity 將訊息傳回至主要 Activity 時才會顯示。
在這項工作中,您要新增程式碼,使用 onSaveInstanceState() 保留這兩個 TextView 元素的例項狀態。
- 開啟「MainActivity」MainActivity。
- 將
onSaveInstanceState()的這個架構實作項目新增至Activity,或依序選取「Code」>「Override Methods」,插入架構覆寫方法。
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
- 確認目前是否會顯示標頭。如果會顯示,請使用
putBoolean()方法和"reply_visible"鍵,將其顯示狀態放入狀態Bundle。
if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
outState.putBoolean("reply_visible", true);
}
請注意,在出現來自第二個 Activity 的回覆之後,回覆標頭和文字才會標示為隱藏。如果標頭會顯示,就需要儲存回覆資料。請注意,我們只想瞭解顯示狀態,而標頭文字不會變更,所以不需要儲存實際的文字。
- 在同一檢查中,將回覆文字新增至
Bundle。
outState.putString("reply_text",mReplyTextView.getText().toString());
如果標頭會顯示,您可以假設回覆訊息本身會一併顯示。您不需要測試或儲存回覆訊息目前的顯示狀態。只有訊息的實際文字會進入 Bundle 狀態中,並具有 "reply_text" 鍵。
請只為可能會在 Activity 建立後變更的 View 元素儲存狀態。應用程式中的其他 View 元素 (EditText、Button) 隨時可以透過預設版面配置重新建立。
請注意,系統會儲存部分 View 元素的狀態,例如 EditText 的內容。
2.2 在 onCreate() 中還原 Activity 例項狀態
您儲存 Activity 例項狀態後,也需要在 Activity 重新建立時還原狀態。方法是在 onCreate() 中還原,或是實作 onRestoreInstanceState() 回呼。系統會在 Activity 建立後先呼叫 onStart(),接著再呼叫此回呼。
大多數情況下,還原 Activity 狀態的最佳位置是在 onCreate() 中,可確保盡快取得 UI 和狀態。您可以在完成所有初始化作業後於 onRestoreInstanceState() 中執行還原作業,或是讓子類別決定是否要使用預設實作項目,這麼做有時很方便。
- 在
onCreate()方法中,請於使用findViewById()初始化View變數後,新增測試來確認savedInstanceState並非空值。
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
// Restore the state.
if (savedInstanceState != null) {
}
Activity 建立後,系統會將狀態 Bundle 傳遞至 onCreate(),當做唯一的引數。系統第一次呼叫 onCreate() 並啟動應用程式時,Bundle 為 null。也就是說,應用程式首次啟動時並沒有狀態。後續呼叫 onCreate() 時,系統會使用 onSaveInstanceState() 中儲存的資料填入 Bundle。
- 在該檢查中,使用
"reply_visible"鍵從Bundle取得目前的顯示狀態 (true 或 false)。
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
}
- 在前一行的下方,為 isVisible 變數新增測試。
if (isVisible) {
}
如果狀態 Bundle 中有 reply_visible 鍵 (而 isVisible 因此為 true),就會需要還原狀態。
- 在
isVisible測試中,讓系統顯示標頭。
mReplyHeadTextView.setVisibility(View.VISIBLE);
- 使用
"reply_text"鍵從Bundle取得的文字回覆訊息,然後將回覆TextView設為顯示該字串。
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
- 讓系統一併顯示回覆
TextView:
mReplyTextView.setVisibility(View.VISIBLE);
- 執行應用程式。請嘗試旋轉裝置或模擬器,確保在
Activity重建後,畫面上仍顯示回覆訊息 (如有)。
工作 2 解決方案程式碼
下列程式碼片段顯示這項工作的解決方案程式碼。
MainActivity
下列程式碼片段為 MainActivity 中新增的程式碼,但不是整個類別。
onSaveInstanceState() 方法:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// If the heading is visible, message needs to be saved.
// Otherwise we're still using default layout.
if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
outState.putBoolean("reply_visible", true);
outState.putString("reply_text",
mReplyTextView.getText().toString());
}
}
onCreate() 方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
// Restore the saved state.
// See onSaveInstanceState() for what gets saved.
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
// Show both the header and the message views. If isVisible is
// false or missing from the bundle, use the default layout.
if (isVisible) {
mReplyHeadTextView.setVisibility(View.VISIBLE);
mReplyTextView.setText(savedInstanceState
.getString("reply_text"));
mReplyTextView.setVisibility(View.VISIBLE);
}
}
}
完整專案:
Android Studio 專案:TwoActivitiesLifecycle
5. 程式設計挑戰
挑戰:請建立簡單的購物清單應用程式,而主要活動應用於使用者正在建立的清單,第二個活動則用於一般購物商品的清單。
- 主要活動應包含要建立的清單,而該清單應含有十個空白
TextView元素。 - 點選主要活動上的「Add Item」按鈕,即可啟動第二個活動。該活動應包含常見的購物商品清單,例如起司、米、蘋果等。使用
Button元素顯示商品。 - 選擇商品後,系統會將使用者帶回主要活動,並將空白的
TextView更新為納入所選商品。
使用 Intent 在 Activity 之間傳遞資訊。請確認系統會在使用者旋轉裝置時,儲存購物清單的目前狀態。
6. 摘要
- 活動生命週期是指
Activity經歷的一組狀態,從Activity首次建立時開始,到 Android 系統回收相關資源時結束。 - 當使用者瀏覽不同的
Activity,以及來回切換應用程式時,每個Activity會在Activity生命週期的各個狀態之間移動。 Activity生命週期中的每個狀態都有對應的回呼方法,可在Activity類別中覆寫。- 生命週期方法包括
onCreate()、onStart()、onPause()、onRestart()、onResume()、onStop()、onDestroy()。 - 覆寫生命週期回呼方法,即可新增會在
Activity轉換為該狀態時發生的行為。 - 您可以在 Android Studio 中依序選取「Code」>「Override」,在類別中新增架構覆寫方法。
- 旋轉等裝置設定變更會導致
Activity遭到刪除,並重新建立為全新活動。 - 設定變更時,系統會保留部分
Activity狀態,包括EditText元素目前的值。至於所有其他資料,您必須自行明確儲存。 - 將
Activity例項狀態儲存在onSaveInstanceState()方法中。 - 例項狀態資料會以簡單的鍵/值組合形式儲存在
Bundle中。使用Bundle方法可將資料放入Bundle,也可以從中取出資料。 - 還原例項狀態的建議位置為
onCreate(),但也可以選擇在onRestoreInstanceState()中還原。
7. 相關概念
相關概念說明文件請參閱「2.2:活動生命週期和狀態」。
8. 瞭解詳情
Android Studio 說明文件:
Android 開發人員說明文件:
9. 作業
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
建構並執行應用程式
- 建立具有版面配置的應用程式,其中包含計數器
TextView、用來調高計數器的Button,以及EditText。請參考下方的螢幕截圖。您不必精確重現此版面配置。 - 為
Button新增可調高計數器的點擊處理常式。 - 執行應用程式,然後調高計數器。在
EditText中輸入文字。 - 旋轉裝置。請注意,計數器會重設,但
EditText不會重設。 - 實作
onSaveInstanceState(),儲存應用程式目前的狀態。 - 更新
onCreate(),還原應用程式的狀態。 - 確認在旋轉裝置時可保留應用程式狀態。

回答問題
第 1 題
如果在實作 onSaveInstanceState() 前執行作業應用程式,然後旋轉裝置,會發生什麼事?請選擇其中一個選項:
EditText不再包含您輸入的文字,但系統會保留計數器。- 計數器會重設為 0,
EditText不再包含您輸入的文字。 - 計數器會重設為 0,但
EditText的內容會保留。 - 計數器和
EditText的內容都會保留。
第 2 題
發生裝置設定變更 (例如旋轉) 時,系統會呼叫哪些 Activity 生命週期方法?請選擇其中一個選項:
- Android 會呼叫
onStop(),立即關閉Activity。程式碼必須重新啟動Activity。 - Android 會呼叫
onPause()、onStop()和onDestroy()來關閉Activity。程式碼必須重新啟動Activity。 - Android 會呼叫
onPause()、onStop()和onDestroy()來關閉Activity,然後再重新啟動活動,呼叫onCreate()、onStart()和onResume()。 - Android 會立即呼叫
onResume()。
第 3 題
在 Activity 生命週期中,何時會呼叫 onSaveInstanceState()?請選擇其中一個選項:
onSaveInstanceState()是在onStop()方法之前呼叫。onSaveInstanceState()是在onResume()方法之前呼叫。onSaveInstanceState()是在onCreate()方法之前呼叫。onSaveInstanceState()是在onDestroy()方法之前呼叫。
第 4 題
在 Activity 完成或刪除前,最適合使用哪一個 Activity 生命週期方法儲存資料?請選擇其中一個選項:
onPause()或onStop()onResume()或onCreate()onDestroy()onStart()或onRestart()
提交應用程式以供評分
評分者專用指南
請確認應用程式具備下列功能:
- 會顯示計數器、用來調高計數器的
Button,以及EditText。 - 按一下
Button,計數器就會增加 1。 - 裝置旋轉時,系統會保留計數器和
EditText狀態。 MainActivity.java實作項目會使用onSaveInstanceState()方法儲存計數器值。onCreate()實作項目可測試outStateBundle是否存在。如果Bundle存在,計數器值會還原,並儲存至TextView。
10. 下一個程式碼研究室
如要找到 Android 開發人員基礎知識 (第 2 版) 課程中的下一個實作程式碼實驗室,請參閱「Android 開發人員基礎知識程式碼實驗室 (第 2 版)」。
如需課程總覽,包括概念章節、應用程式和投影片的連結,請參閱「Android 開發人員基礎知識 (第 2 版)」。