1. 歡迎
這個實作程式碼研究室是「單元 1:開始學習 Android 開發人員基礎知識 (第 2 版)」課程的一部分。如果您按部就班完成各個程式碼研究室,就能充分體驗到本課程的價值:
- 如需課程的完整程式碼研究室清單,請參閱「Android 開發人員基礎知識程式碼研究室 (第 2 版)」。
- 如需課程詳細資訊,包括所有概念章節、應用程式和投影片的連結,請參閱「Android 開發人員基礎知識 (第 2 版)」。
簡介
在本練習中,您將進一步瞭解活動生命週期。生命週期是指活動在整個生命週期中經歷的一組狀態,也就是從系統建立活動、刪除活動,到回收活動資源為止。當使用者瀏覽不同的應用程式活動,以及來回切換應用程式時,活動會在生命週期的不同狀態之間轉換。
活動生命週期中的每個階段都有相對應的回呼方法,例如 onCreate()、onStart()、onPause() 等。當活動變更狀態時,系統會叫用相關聯的回呼方法。您已看過其中一種回呼方法:onCreate()。透過覆寫 Activity 類別中的任何生命週期回呼方法,您就可以變更活動的預設行為,回應使用者或系統動作。
活動狀態也會隨裝置設定變更而變化,例如使用者將裝置從直向轉為橫向時。發生這些設定變更時,系統會刪除活動,並以活動的預設狀態重新建立活動,而使用者可能會失去在活動中輸入的資訊。為避免造成使用者混淆,請務必開發相關應用程式功能,防止資料意外遺失。稍後,您將在本練習中利用設定變更進行實驗,瞭解如何因應裝置設定變更和其他活動生命週期事件,保留活動狀態。
在本練習中,您會在 TwoActivities 應用程式中新增記錄陳述式,觀察活動生命週期的變化。接著您會開始利用這些變更,瞭解如何在這些情況下處理使用者輸入內容。
必要條件
您必須具備以下能力:
- 在 Android Studio 中建立及執行應用程式專案。
- 將記錄陳述式新增至應用程式,並在 Logcat 窗格中查看這些記錄。
- 瞭解並能使用 Activity 和 Intent,且熟悉如何與這些項目互動。
課程內容
- 活動生命週期的運作方式。
- 活動啟動、暫停、停止和刪除的時機。
- 與活動變更相關聯的生命週期回呼方法。
- 設定變更等動作會如何影響活動生命週期事件。
- 如何在生命週期事件中保留活動狀態。
學習內容
- 在先前練習的 TwoActivities 應用程式中新增程式碼,實作各種活動生命週期回呼,並納入記錄陳述式。
- 在應用程式執行期間,以及您與應用程式中各個 Activity 互動時,觀察狀態變更情形。
- 修改應用程式,為意外重建的 Activity 保留例項狀態。導致重建的原因包括使用者行為或裝置設定變更。
2. 應用程式總覽
在本練習中,您會新增程式碼至 TwoActivities 應用程式。此應用程式的外觀和行為與上一個程式碼研究室中大致相同。這包含兩個 Activity 實作項目,可讓使用者在這兩個項目之間傳送內容。您在本練習中對應用程式所做的變更,不會影響使用者看到的應用程式行為。
3. 3. 工作 1:將生命週期回呼新增至 TwoActivities
在這項工作中,您要實作所有活動生命週期回呼方法,讓系統在叫用這些方法時於 Logcat 顯示訊息。這些記錄訊息可讓您瞭解活動生命週期變更狀態的時機,以及這類變更在應用程式執行期間造成的影響。
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」窗格。您應該會看到三個記錄訊息,顯示活動啟動後所經歷的三個生命週期狀態:
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();
- 將生命週期回呼和記錄陳述式新增至第二個活動。(您可以從 MainActivity 複製及貼上回呼方法)。
- 將記錄陳述式新增至 returnReply() 方法,位於 finish() 方法之前:
Log.d(LOG_TAG, "End SecondActivity");
1.4 在應用程式執行期間觀察記錄檔**
- 執行應用程式。
- 按一下 Android Studio 底部的「Logcat」分頁標籤,就會顯示「Logcat」窗格。
- 在搜尋框中輸入「Activity」。Android Logcat 內容可能很冗長且雜亂。由於每個類別的 LOG_TAG 變數都包含 MainActivity 或 SecondActivity 字詞,因此這個關鍵字可用來篩選記錄,只顯示您想瞭解的項目。
請使用應用程式進行實驗,並留意因不同動作而產生的生命週期事件。請特別嘗試下列動作:
- 照常使用應用程式,例如傳送訊息、回覆其他訊息。
- 使用返回按鈕,從第二個活動返回主要活動。
- 使用應用程式列的向上箭頭,從第二個活動返回主要活動。
- 在應用程式的主要和第二個活動的不同時間點旋轉裝置,觀察記錄和畫面中的變化。
- 按下總覽按鈕 (主畫面右側的正方形按鈕),然後關閉應用程式 (輕觸「X」)。
- 返回主畫面,重新啟動應用程式。
提示:如果您是在模擬器中執行應用程式,可以按下 Ctrl+F11 或 Ctrl+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. 4. 工作 2:儲存及還原 Activity 例項狀態
視系統資源和使用者行為而定,應用程式中每個 Activity 遭到刪除及重建的頻率可能會遠超出您的預期。
在上一節中旋轉裝置或模擬器時,您可能已注意到這項行為。旋轉裝置就是裝置設定變更的其中一個例子。雖然旋轉是最常見的設定變更,但所有設定變更都會導致目前的 Activity 遭到刪除,並重新建立為全新活動。如果您的程式碼沒有考量這項行為,那麼在設定變更時,活動版面配置可能會還原為預設外觀和初始值,而使用者可能會失去位置、資料或他們在應用程式中的進度狀態。
每個活動的狀態都會以一組鍵/值組合的形式儲存在 Bundle 物件中,該物件稱為活動例項狀態。系統會在 Activity 停止前,將預設狀態資訊儲存到例項狀態 Bundle,並將該 Bundle 傳遞至新的 Activity 例項來還原。
為避免在 Activity 意外刪除及重建時遺失資料,您需要實作 onSaveInstanceState() 方法。當活動可能遭到刪除及重建時,系統會在活動的 onPause() 和 onStop() 之間呼叫這個方法。
儲存在例項狀態的資料,僅適用於目前應用程式工作階段內此特定 Activity 的例項。停止並重新啟動新的應用程式工作階段時,活動例項狀態會遺失,活動會還原為預設外觀。如果您需要在應用程式工作階段之間儲存使用者資料,請使用共用偏好設定或資料庫。後續練習會進一步說明這兩個概念。
2.1 使用 onSaveInstanceState() 儲存 Activity 例項狀態
您可能已注意到,旋轉裝置完全不會影響第二個活動的狀態。這是因為第二個活動的版面配置和狀態,是由版面配置和啟動該活動的意圖所產生。即使系統已重新建立活動,意圖仍會存在,而且每次呼叫第二個活動的 onCreate() 方法時,仍會使用該意圖中的資料。
此外,您可能會注意到,在每個活動中,您輸入至訊息或回覆 EditText 元素的任何文字都會保留,即使裝置旋轉也一樣。這是因為版面配置中部分 View 元素的狀態資訊會在設定變更時自動儲存,而 EditText 目前的值就屬於這類資訊。
因此,您想瞭解的活動狀態只有回覆標頭的 TextView 元素,以及主要活動中的回覆文字。根據預設,這兩個 TextView 元素不會顯示,只有在您從第二個活動將訊息傳回至主要活動時才會顯示。
在這項工作中,您要新增程式碼,使用 onSaveInstanceState() 保留這兩個 TextView 元素的例項狀態。
- 開啟 MainActivity。
- 將 onSaveInstanceState() 的這個架構實作項目新增至活動,或依序選取「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」鍵。
請只為可能會在活動建立後變更的 View 元素儲存狀態。應用程式中的其他 View 元素 (EditText、Button) 隨時可以透過預設版面配置重新建立。
請注意,系統會儲存部分 View 元素的狀態,例如 EditText 的內容。
2.2 在 onCreate() 中還原 Activity 例項狀態
儲存 Activity 例項狀態後,您也需要在重新建立 Activity 時還原狀態。您可以在 onCreate() 中執行這項操作,也可以實作 onRestoreInstanceState() 回呼,系統會在建立活動後先呼叫 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) {
}
建立活動時,系統會將狀態 Bundle 傳遞至 onCreate(),做為唯一的引數。系統第一次呼叫 onCreate() 並啟動應用程式時,Bundle 為空值,也就是說,應用程式首次啟動時並沒有狀態。後續呼叫 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);
- 執行應用程式。請嘗試旋轉裝置或模擬器,確保在活動重建後,畫面上仍顯示回覆訊息 (如有)。
工作 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 更新為納入所選商品。
使用意圖,在一個活動與另一個活動之間傳遞資訊。請確認系統會在使用者旋轉裝置時,儲存購物清單的目前狀態。
6. 摘要
- 活動生命週期是指活動經歷的一組狀態,從活動首次建立時開始,到 Android 系統回收相關資源時結束。
- 當使用者瀏覽不同的活動,以及來回切換應用程式時,每個活動會在活動生命週期的各個狀態之間移動。
- 活動生命週期中的每個狀態都有對應的回呼方法,可在活動類別中覆寫。
- 生命週期方法包括 onCreate()、onStart()、onPause()、onRestart()、onResume()、onStop() 和 onDestroy()。
- 覆寫生命週期回呼方法,即可新增會在活動轉換為該狀態時發生的行為。
- 您可以在 Android Studio 中依序選取「Code」>「Override」,在類別中新增架構覆寫方法。
- 旋轉等裝置設定變更會導致活動遭到刪除,並重新建立為全新活動。
- 設定變更時,系統會保留部分活動狀態,包括 EditText 元素目前的值。至於所有其他資料,您必須自行明確儲存。
- 在 onSaveInstanceState() 方法中儲存 Activity 例項狀態。
- 例項狀態資料會以簡單的鍵/值組合形式儲存在 Bundle 中。使用套件方法將資料放入套件,也可以從中取出資料。
- 在 onCreate() 中還原例項狀態 (這是建議做法),或在 onRestoreInstanceState() 中還原。返回