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」。
- 在類別頂端新增
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」)。
- 返回主畫面,重新啟動應用程式。
提示:如果您是在模擬器中執行應用程式,可以按下 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」。
- 將
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()
實作項目可測試outState
Bundle
是否存在。如果Bundle
存在,計數器值會還原,並儲存至TextView
。
10. 下一個程式碼研究室
如要找到 Android 開發人員基礎知識 (第 2 版) 課程中的下一個實作程式碼研究室,請參閱「Android 開發人員基礎知識程式碼研究室 (第 2 版)」。
如需課程總覽,包括概念章節、應用程式和投影片的連結,請參閱「Android 開發人員基礎知識 (第 2 版)」。