活動生命週期的各個階段

1. 歡迎

說明

在本程式碼研究室中,您將進一步瞭解 Android 的基本要素:「活動」活動生命週期是活動在其生命週期內可呈現的狀態組合。生命週期是指活動從最初建立到刪除的這段期間,且系統會回收該活動的資源。當使用者在應用程式 (包括應用程式內外) 的活動間進行瀏覽時,這些活動會在活動生命週期中的不同狀態之間進行轉換。

身為 Android 開發人員,您必須瞭解活動生命週期。如果您的活動未正確回應生命週期狀態變更,應用程式可能會產生異常錯誤、造成使用者混淆行為,或耗用過多 Android 系統資源。瞭解 Android 生命週期,並正確回應生命週期狀態變更,是成為 Android 優良使用者的重要條件。

須知事項

  • 什麼是活動,以及在應用程式中建立活動的方式。
  • 活動 onCreate() 方法的運作內容,以及該方法執行的運算種類。

課程內容

  • 如何將記錄資訊列印至 Logcat。
  • Activity 生命週期的基本概念,以及活動於狀態之間變動時叫用的回呼。
  • 如何覆寫生命週期回呼方法,以在活動生命週期的不同時間點執行運算。

執行步驟

  • 修改名稱為 DessertClicker 的範例應用程式,以便新增 Logcat 中顯示的記錄資訊。
  • 覆寫生命週期回呼方法,並記錄活動狀態的變更。
  • 執行應用程式,並記下活動開始、停止和繼續時的記錄資訊。
  • 實作 onSaveInstanceState() 方法,保留因裝置設定變更而可能遺失的應用程式資料。新增程式碼,以在應用程式再次啟動時還原該資料。

2. 應用程式總覽

在本程式碼研究室中,您將使用名為 DessertClicker 的範例應用程式。在此應用程式中,每當使用者輕觸畫面上的甜點,應用程式就會為使用者「購買」甜點。這款應用程式會更新版面配置中的值,包括已購買的甜點數量,以及使用者花費的總金額。

8216c20f5571fc04.png

此應用程式含有數個與 Android 生命週期相關的錯誤:舉例來說,應用程式會在特定情況下將甜點值重設為 0。瞭解 Android 生命週期有助於瞭解問題發生的原因與修正方法。

取得範例應用程式

下載 DessertClicker 範例程式碼,並在 Android Studio 中開啟。

如果您使用 GitHub 中的範例程式碼,請注意資料夾名稱是 android-basics-kotlin-dessert-clicker-app-starter。在 Android Studio 中開啟專案時,請選取這個資料夾。

如要取得這個程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作:

取得程式碼

  1. 按一下上面顯示的網址。系統會在瀏覽器中開啟專案的 GitHub 頁面。
  2. 在專案的 GitHub 頁面中,按一下「Code」按鈕開啟對話方塊。

5b0a76c50478a73f.png

  1. 在對話方塊中,按一下「Download ZIP」按鈕,將專案儲存到電腦。等待下載作業完成。
  2. 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
  3. 按兩下解壓縮 ZIP 檔案。這項操作會建立含有專案檔案的新資料夾。

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Open an existing Android Studio project」

36cc44fcf0f89a1d.png

注意:如果 Android Studio 已開啟,請依序選取「File」>「New」>「Import Project」選單選項。

21f3eec988dcfbe9.png

  1. 在「Import Project」對話方塊中,前往解壓縮專案資料夾所在的位置 (可能位於「下載」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「Run」按鈕 11c34fc5e516fb1c.png 即可建構並執行應用程式。請確認應用程式的建構符合預期。
  5. 在「Project」工具視窗中瀏覽專案檔案,查看應用程式的設定方式。

3. 探索生命週期方法並新增基本記錄

每個活動都具有生命週期。這是植物和動物生命週期的影射,如同這隻蝴蝶的生命週期。蝴蝶的不同狀態,顯示其從出生到完全成熟的成蟲,再到死亡的成長過程。

c685f48ff799f0c9.png

同樣地,活動生命週期由活動可能經歷的不同狀態組成,從活動首次初始化到最終刪除,且系統回收其記憶體的這段時間。當使用者啟動您的應用程式、在活動間瀏覽、在應用程式內部或外部瀏覽時,活動皆會改變狀態。下圖顯示所有活動生命週期狀態。顧名思義,這些狀態代表活動的狀態。

c803811f4cb4034b.png

通常,當活動生命週期狀態變更時,您會想變更部分行為或執行某些程式碼。因此,Activity 類別本身和 Activity 的任何子類別 (例如 AppCompatActivity) 會實作一系列生命週期回呼方法。當活動狀態改變時,Android 會叫用這些回呼,而您可以在自己的活動中覆寫此類方法,並執行任務來回應生命週期狀態變更。下圖顯示生命週期狀態,以及可用的可覆寫回呼。

f6b25a71cec4e401.png

請務必瞭解這些回呼的叫用時機,以及每個回呼方法的處理方式。但兩個圖表都很複雜,可能會令人感到困惑。在本程式碼研究室中,您不僅將瞭解每個狀態和回呼的意義,還會進行一些偵測工作並瞭解實際情況。

步驟 1:檢查 onCreate() 方法並新增記錄

如要瞭解 Android 生命週期發生的情況,瞭解呼叫各種生命週期方法的時機相當實用。這有助於您找出 DessertClicker 中的問題。

最簡單的方法就是使用 Android 記錄功能。記錄功能可讓您在應用程式執行期間,將簡短訊息寫入主控台,以便顯示不同回呼觸發的時間。

  1. 執行 Dessert Clicker 應用程式,然後在甜點相片上輕觸數次。請注意,已售出甜點值和總金額的變化情況。
  2. 開啟 MainActivity.kt 並檢查此活動的 onCreate() 方法:
override fun onCreate(savedInstanceState: Bundle?) {
...
}

在活動生命週期圖表中,您可能已認出 onCreate() 方法,因為您先前使用過此回呼。這是每項活動都必須實作的方法。onCreate() 是您為活動執行任何一次性初始化作業的方法。舉例來說,在 onCreate() 中,您會加載版面配置、定義點選監聽器,或是設定檢視繫結。

9be2255ff49e0af8.png

當活動初始化後 (在記憶體中建立新的 Activity 物件時),系統會呼叫 onCreate() 生命週期方法一次。執行 onCreate() 後,系統會將活動視為已建立

  1. onCreate() 方法中,於呼叫 super.onCreate() 後新增下列程式碼:
Log.d("MainActivity", "onCreate Called")
  1. 視需要匯入 Log 類別 (Mac 上請按 Alt+EnterOption+Enter,然後選取「Import」)。如果您已啟用自動匯入功能,系統會自動執行這項操作。
import android.util.Log

Log 類別會將訊息寫入「Logcat」。「Logcat」是記錄訊息的主控台。此處會顯示來自 Android 的應用程式相關訊息,包括您透過 Log.d() 方法或其他 Log 類別方法明確傳送至記錄的訊息。

這個指令包含三個部分:

  • 記錄訊息的「優先順序」,也就是訊息的重要性。在這種情況下,Log.d() 方法會寫入偵錯訊息。Log 類別中的其他方法包括資訊訊息的 Log.i()、錯誤的 Log.e()、警示的 Log.w(),或詳細訊息的 Log.v()
  • 記錄「標記」 (第一個參數),在本範例中為 "MainActivity"。這個標記是字串,供您在 Logcat 中輕鬆找到記錄訊息。標記通常是類別的名稱。
  • 實際記錄訊息 (第二個參數) 是簡短字串,在本範例中為 "onCreate called"

編譯時間常數是不會改變的值。在變數宣告之前使用 const,以將其標示為編譯時間常數。

  1. 編譯並執行 DessertClicker 應用程式。輕觸甜點時,就不會在應用程式中看到任何行為差異。在 Android Studio 中,按一下畫面底部的「Logcat」分頁標籤。

6463f587ac6997fe.png

  1. 在「Logcat」視窗的搜尋欄位中輸入 D/MainActivity

bb0b78600cd47789.png

Logcat 可包含許多訊息,其中大部分無法派上用場。篩選 Logcat 項目的方法相當多樣,但搜尋是最簡單的方法。由於您在程式碼中使用 MainActivity 做為記錄標記,因此您可以使用該標記篩選記錄。在開頭加上 D/,表示這是由 Log.d() 建立的偵錯訊息。

您的記錄訊息包括日期和時間、套件名稱 (com.example.android.dessertclicker)、記錄標記 (開頭為 D/),以及實際訊息。由於這則訊息會出現在記錄中,但您知道系統已執行該 onCreate()

步驟 2:實作 onStart() 方法

系統將在 onCreate() 之後呼叫 onStart() 生命週期方法。onStart() 開始執行後,螢幕上會顯示您的活動。onCreate() 僅可呼叫一次以初始化活動,與之不同的是,onStart() 在活動生命週期中可呼叫多次。

385df4ce82ae2de9.png

請注意,onStart() 已和對應的 onStop() 生命週期方法配對。如果使用者啟動應用程式,然後返回裝置的主畫面,活動就會停止,且不再顯示於螢幕上。

  1. 在 Android Studio,於 MainActivity.kt 開啟且游標位於 MainActivity 類別內時選取「Code」(程式碼) >「Override Methods」(覆寫方法),或按下 Control+o (Mac 上的 Command+o)。顯示的對話方塊會列出您可在此類別中覆寫的所有方法。

e1f2460242b2ae.png

  1. 輸入 onStart 以搜尋正確方法。如要捲動至下一個相符的項目,請使用向下鍵。從清單中選擇 onStart(),然後按一下「OK」,插入樣板覆寫程式碼。程式碼如下所示:
override fun onStart() {
   super.onStart()
}
  1. MainActivity.kt (位於類別宣告 class MainActivity. 上方) 頂層新增下列常數
const val TAG = "MainActivity"
  1. onStart() 方法中加入記錄訊息:
override fun onStart() {
   super.onStart()
   Log.d(TAG, "onStart Called")
}
  1. 編譯並執行 DessertClicker 應用程式,然後開啟「Logcat」窗格。在搜尋欄位中輸入 D/MainActivity,以篩選記錄。請注意,將逐一呼叫 onCreate()onStart() 方法,且您的活動會顯示在螢幕上。
  2. 按下裝置的主畫面按鈕,然後使用「最近使用」畫面返回活動。請注意,活動會從上次中斷的地方接續進行,所有值皆相同,且系統會將 onStart() 再次記錄到 Logcat。另請注意,通常不會再次呼叫 onCreate() 方法。
16:19:59.125 31107-31107/com.example.android.dessertclicker D/MainActivity: onCreate Called
16:19:59.372 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called
16:20:11.319 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called

步驟 3:新增更多記錄陳述式

在這個步驟中,您會實作所有其他生命週期方法的記錄功能。

  1. 覆寫 MainActivity 中的其餘生命週期方法,並為每個方法新增陳述式。程式碼如下:
override fun onResume() {
   super.onResume()
   Log.d(TAG, "onResume Called")
}

override fun onPause() {
   super.onPause()
   Log.d(TAG, "onPause Called")
}

override fun onStop() {
   super.onStop()
   Log.d(TAG, "onStop Called")
}

override fun onDestroy() {
   super.onDestroy()
   Log.d(TAG, "onDestroy Called")
}

override fun onRestart() {
   super.onRestart()
   Log.d(TAG, "onRestart Called")
}
  1. 再次編譯並執行 DessertClicker,並檢查 Logcat。這次請注意,除了 onCreate()onStart() 以外,系統也會提供 onResume() 生命週期回呼的記錄訊息。
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

活動從頭開始時,系統會依序呼叫下列三個生命週期回呼:

  • onCreate() 可建立應用程式。
  • onStart() 可啟動活動並在螢幕上顯示。
  • onResume() 可聚焦於活動,並使其準備與使用者進行互動。

儘管有名稱,即使沒有可繼續進行的操作,啟動時仍會呼叫 onResume() 方法。

160054d59f67519.png

4. 探索生命週期用途

現在,DessertClicker 應用程式已進行記錄設定,您可以透過各種方式開始使用該應用程式,並探索如何觸發生命週期回呼來回應用途。

用途 1:開啟及關閉活動

您將從最基本的用途開始,也就是初次啟動應用程式,然後完全關閉應用程式。

  1. 編譯並執行 DessertClicker 應用程式 (如果尚未執行)。如您所見,活動初次開始時,就會呼叫 onCreate()onStart(),以及 onResume() 回呼。
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called
  1. 輕觸杯子蛋糕數次。
  2. 輕觸裝置上的「Back」按鈕。請注意,Logcat 中會依序呼叫 onPause()onStop()onDestroy()
2020-10-16 10:31:53.850 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:31:54.620 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called

在此情況下,使用「Back」按鈕會使活動 (和應用程式) 完全關閉。執行 onDestroy() 方法表示活動已完全終止,且可做為垃圾收集。垃圾收集是指自動清理不再使用的物件。呼叫 onDestroy() 後,系統會知道這些資源皆可捨棄,且會開始清除該記憶體。2dcc4d9c6478a9f4.png

若程式碼手動呼叫活動的 finish() 方法,或是使用者強制退出應用程式,系統也會將活動完全關閉。(例如,使用者可在最近使用畫面中強制退出或關閉應用程式)。若應用程式長時間未處於畫面上,Android 系統也可能會自行關閉您的活動。Android 會藉此節省電池電力,並允許其他應用程式使用您應用程式的資源。

  1. 總覽畫面上找到所有開啟的應用程式,返回 DessertClicker 應用程式 (請注意,這也稱為最近使用畫面或最近使用的應用程式)。Logcat 如下:
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 10:38:00.733 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:38:00.787 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:38:00.788 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

活動已在前一個步驟中遭到刪除,因此當您返回應用程式時,Android 會開始新的活動,並呼叫 onCreate()onStart()onResume() 方法。請注意,系統不會保留先前活動的任何 DessertClicker 記錄。

onCreate() 方法是重要步驟;在這裡可以執行所有初次初始化作業、透過加載首次設定版面配置,以及初始化變數。

用途 2:離開後再返回活動

現在,您已啟動並完全關閉應用程式,您可看到活動首次建立時的大部分生命週期狀態。此外,您也會看到活動完全關閉且刪除後經歷的所有生命週期狀態。然而,當使用者與 Android 裝置互動時,會在應用程式間切換、返回主畫面、啟動新的應用程式,並處理因其他活動 (例如來電) 而中斷的活動。

每次使用者離開該活動時,活動都不會完全關閉:

  • 當系統不再於畫面中顯示活動時,即稱為使活動進入背景。(反之,則活動位於前景或螢幕上。)
  • 使用者返回您的應用程式時,系統會重新啟動相同的活動,並再次顯示該活動。生命週期中的此部分稱為應用程式的可見生命週期。

應用程式在背景運作時,通常不應主動運作,以維持系統資源和電池壽命。您會使用 Activity 生命週期及其回呼來瞭解應用程式移至背景的時間,以便暫停任何進行中的作業。接著您會在應用程式進入前景時重新開始作業。

在這個步驟中,您可以查看應用程式進入背景後再次回到前景的活動生命週期。

  1. 執行 DessertClicker 應用程式時,點選杯子蛋糕數次。
  2. 按下裝置上的「主畫面」按鈕,然後觀察 Android Studio 中的 Logcat。返回主畫面會讓應用程式進入背景,而非完全關閉應用程式。請注意,系統會呼叫 onPause() 方法和 onStop() 方法,但不會呼叫 onDestroy() 方法。
2020-10-16 10:41:05.383 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:41:05.966 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called

呼叫 onPause() 後,應用程式就不再有焦點。onStop() 後,應用程式就不會再顯示於螢幕上。儘管活動已經停止,但 Activity 物件仍位於背景的記憶體中。系統並未刪除該活動。使用者可能會返回應用程式,因此 Android 會保留您的活動資源。b488b32801220b79.png

  1. 使用最近使用畫面返回應用程式。請注意,活動會在 Logcat 中使用 onRestart()onStart() 重新啟動,然後使用 onResume() 重新啟用。
2020-10-16 10:42:18.144 22064-22064/com.example.android.dessertclicker D/MainActivity: onRestart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

當活動返回前景時,就不會再次呼叫 onCreate() 方法。活動物件並未刪除,因此不需要重新建立。系統會呼叫 onRestart() 方法,而不是 onCreate()。請注意,當活動返回前景時,系統會保留「Desserts Sold」數量。

  1. 至少啟動一個 DessertClicker 以外的應用程式,使裝置的最近使用畫面中含有多個應用程式。
  2. 開啟最近使用畫面,並開啟其他最近活動。接著返回最近使用的應用程式,並將 DessertClicker 移回前景。

請注意,此處 Logcat 中顯示的回呼與按下主畫面按鈕時相同。應用程式進入背景時,系統會呼叫 onPause()onStop(),並在應用程式返回時呼叫 onRestart()onStart()onResume()

當應用程式停止並移至背景,或應用程式返回前景並再次啟動時,系統就會呼叫這些方法。如果遇到這類情況,而您必須在應用程式中執行工作,請覆寫相關的生命週期回呼方法。

那麼,onRestart() 呢?onRestart() 方法與 onCreate() 類似。在活動顯示之前,系統會呼叫 onCreate()onRestart()。第一次僅會呼叫 onCreate() 方法,onRestart() 則在後續呼叫。onRestart() 方法是放置程式碼的地方,只有在活動為「非」初次啟動時才會呼叫該程式碼。

用途 3:部分隱藏活動

您已經瞭解應用程式啟動且呼叫 onStart() 時,螢幕上會顯示該應用程式。重新啟用應用程式並呼叫 onResume() 時,應用程式會取得使用者焦點,即使用者可與應用程式互動。應用程式完全顯示在畫面上,且有使用者焦點時,這個生命週期部分便是「互動」生命週期。

應用程式進入背景時,焦點會於 onPause() 後消失,且應用程式在 onStop() 後也不再顯示。

焦點和可見度之間的差異非常重要,因為活動在畫面上可以部分顯示,但沒有使用者焦點。在此步驟中,可查看部分顯示,但沒有使用者焦點的活動。

  1. 在 DessertClicker 應用程式執行時,按一下畫面右上方的「Share」按鈕。

分享活動會顯示在畫面的下半部,但活動仍會在上半部顯示。

e2319779260eb5ee.png

9ddc8b1dc79b1bff.png

  1. 檢查 Logcat,且注意僅呼叫 onPause()
2020-10-16 11:00:53.857 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called

在此用途中,系統不會呼叫 onStop(),因為活動仍部分顯示。不過,這類活動沒有使用者焦點,且使用者無法與其互動,因為位於前景的「分享」活動具有使用者焦點。

為什麼這個差異很重要?僅使用 onPause() 的干擾通常只會持續一小段時間,然後使用者便會返回活動或前往其他活動或應用程式。一般而言,您需要持續更新 UI,應用程式的其餘部分便不會停止運作。

onPause() 中執行的程式碼會使其他內容無法顯示,因此請將程式碼保存在輕量 onPause() 中。例如,如果有來電,onPause() 中的程式碼可能會延遲來電通知。

  1. 按一下分享對話方塊以外的地方,返回應用程式,並留意系統已呼叫 onResume()

onResume()onPause() 都必須與焦點有關。活動有焦點時,系統會呼叫 onResume() 方法,而活動失去聚焦時,就會呼叫 onPause()

5. 探索設定變更

管理活動生命週期的另一個用例也十分重要:設定變更對活動生命週期的影響。

設定變更於裝置狀態改變時發生,因此基本上,使系統解決變更的最簡單方法就是完全關閉,然後重新建立活動。舉例來說,若使用者變更裝置語言,您必須調整整個版面配置,以配合不同的文字方向和字串長度。如果使用者將裝置插入座架或新增實體鍵盤,應用程式版面配置可能必須採用不同的顯示大小或版面配置。如果裝置螢幕方向改變 (例如將裝置從直向轉為橫向或向後旋轉),您可能需要根據新的螢幕方向變更版面配置。讓我們看看應用程式在這個情境中的行為。

裝置旋轉時資料遺失

  1. 編譯並執行應用程式,然後開啟 Logcat。
  2. 將裝置或模擬器旋轉至橫向模式。您可以使用旋轉按鈕或 Control 方向鍵 (在 Mac 上為 Command 和方向鍵),將模擬器向左或向右旋轉。623fce7c623d42bd.png
  3. 檢查 Logcat 中的輸出內容。在 MainActivity 上篩選輸出內容。
2020-10-16 11:03:09.618 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:09.806 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 11:03:09.808 23206-23206/com.example.android.dessertclicker D/MainActivity: onResume Called
2020-10-16 11:03:24.488 23206-23206/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:03:24.490 23206-23206/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:03:24.493 23206-23206/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 11:03:24.520 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:24.569 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called

請注意,當裝置或模擬器旋轉螢幕時,系統會呼叫所有生命週期回呼來關閉活動。接著,當您重新建立活動時,系統會呼叫所有生命週期回呼來啟動活動。

  1. 裝置旋轉且關閉並重新建立活動後,活動就會以預設值啟動,也就是將甜點售出數量和收益重設為零。

使用 onSaveInstanceState() 儲存套件資料

onSaveInstanceState() 方法為回呼,可用於在 Activity 刪除時儲存您需要的任何資料。在生命週期回呼圖中,系統會在活動停止後呼叫 onSaveInstanceState()。每當應用程式進入背景時就會呼叫此方法。

c259ab6beca0ca88.png

您可以將 onSaveInstanceState() 呼叫視為安全防護措施;此呼叫會在活動離開前景時,將少量資訊儲存至套件。系統會在此時儲存資料,因為若等待直到應用程式關閉,系統可能會有資源壓力。

每次儲存資料可確保套件中的已更新資料能夠視需要還原。

  1. MainActivity 中覆寫 onSaveInstanceState() 回呼,並新增記錄陳述式。
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Log.d(TAG, "onSaveInstanceState Called")
}
  1. 編譯並執行應用程式,然後按一下「Home」按鈕,讓應用程式進入背景。請注意,onSaveInstanceState() 回呼緊接在 onPause()onStop() 之後:
2020-10-16 11:05:21.726 23415-23415/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:05:22.382 23415-23415/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:05:22.393 23415-23415/com.example.android.dessertclicker D/MainActivity: onSaveInstanceState Called
  1. 在檔案頂端的類別定義前方,加入以下常數:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"

您將使用這些鍵儲存與擷取執行個體狀態套件中的資料。

  1. 向下捲動至 onSaveInstanceState(),並注意 outState 參數為 Bundle 類型。Bundle 是一系列鍵/值組合,其中鍵一律為字串。您可以將簡易的資料 (例如 IntBoolean 值) 加入套件中。由於系統會將此套件儲存在記憶體中,因此最佳做法是讓套件中的資料保持精簡。此套件的大小有限,但尺寸會因裝置而異。如果儲存過多資料,應用程式可能會因為 TransactionTooLargeException 錯誤而異常終止。
  2. onSaveInstanceState() 中,使用 putInt() 方法將 revenue 值 (整數) 加入套件中:
outState.putInt(KEY_REVENUE, revenue)

putInt() 方法 (以及 Bundle 類別中的類似方法,例如 putFloat()putString()) 使用兩個引數:鍵字串 (KEY_REVENUE 常數),以及要儲存的實際值。

  1. 按照相同的甜點售出數量重複上述步驟:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)

使用 onCreate() 還原套件資料

您可以在 onCreate(Bundle)onRestoreInstanceState(Bundle) 中還原活動狀態 (由 onSaveInstanceState() 方法填入的 Bundle 會傳遞至這兩個生命週期回呼方法)。

  1. 向上捲動至 onCreate(),並檢查方法簽章:
override fun onCreate(savedInstanceState: Bundle?) {

請注意,每次呼叫時,onCreate() 都會獲得一個 Bundle。當您的活動因程序關閉而重新啟動時,您儲存的套件會傳遞至 onCreate()。如果您的活動重新啟動,onCreate() 中的此 Bundlenull。因此,如果套件並非 null,表示您是從先前的已知點「重新建立」活動。

  1. 在設定 binding 變數後,將此程式碼新增至 onCreate()
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

null 的測試可判斷套件中是否含有資料,或套件是否為 null,以讓您瞭解應用程式是否已重新啟動,或在關閉後重新建立。這項測試是從套件還原資料的常見模式。

請注意,您在此處使用的鍵 (KEY_REVENUE) 與您在 putInt() 中使用的鍵相同。為確保每次都使用相同的鍵,最佳做法是將這些鍵定義為常數。請使用 getInt() 將資料移出套件,如同使用 putInt() 將資料放入套件一樣。getInt() 方法使用兩個引數:

  • 可做為鍵的字串,例如收益值的 "key_revenue"
  • 預設值,假如套件中沒有該鍵的任何值。

系統會將從套件中取得的整數指派給 revenue 變數,且 UI 會使用該值。

  1. 新增 getInt() 方法,還原收益和甜點售出數量。
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
}
  1. 編譯並執行應用程式。按下杯子蛋糕至少五次,直到切換成甜甜圈為止。
  2. 旋轉裝置。請注意,應用程式現在可以顯示正確的收益,以及套件中的甜點售出值。另請注意,甜點已恢復為杯子蛋糕。4179956182ffc634.png

您還需要再執行一項操作,確保應用程式從關閉狀態完全恢復成原先的狀態。

  1. MainActivity 中檢查 showCurrentDessert() 方法。請注意,此方法會根據目前的甜點售出數量和 allDesserts 變數中的甜點清單,判斷要在活動中顯示的甜點圖片。
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

此方法仰賴甜點的售出數量來選擇正確的圖片。因此,您無須採取任何行動,以儲存 onSaveInstanceState() 中套件的圖片參照。您已在該套件中儲存甜點售出數量。

  1. onCreate() 中,從套件還原狀態的區塊呼叫 showCurrentDessert()
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   showCurrentDessert()
}
  1. 編譯並執行應用程式,然後旋轉畫面。請注意,甜點售出值、總收益和甜點圖片已順利還原。

6. 摘要

活動生命週期

  • 活動生命週期是一組活動轉移的狀態。活動生命週期是從首次建立時開始,並在活動刪除時結束。
  • 使用者在不同活動之間及應用程式內外瀏覽時,每個活動會在活動生命週期中的狀態間切換。
  • 活動生命週期中的每個狀態都有相應的回呼方法,可在 Activity 類別中覆寫。生命週期方法的核心組合如下:onCreate()onStart()onPause()onRestart()onResume()onStop()onDestroy()
  • 如要新增在活動轉換為生命週期狀態時發生的行為,請覆寫狀態的回呼方法。
  • 如要在 Android Studio 中為類別新增架構覆寫方法,請依序選取「Code」>「Override Methods」,或按下 Control+o

使用記錄檔進行記錄

  • Android Logging API (尤其是 Log 類別),可讓您編寫在 Android Studio 的 Logcat 中顯示的簡短訊息。
  • 請使用 Log.d() 編寫偵錯訊息。這個方法使用兩個引數:記錄標記,通常是類別名稱,以及記錄訊息,此為簡短字串。
  • 使用 Android Studio 中的「Logcat」視窗檢視系統記錄,包括您編寫的訊息。

保留活動狀態

  • 應用程式進入背景時,只要呼叫 onStop() 後,即可將應用程式資料儲存至套件中。系統會自動儲存部分應用程式資料 (例如 EditText 的內容)。
  • 套件是 Bundle 執行個體,也是鍵和值的集合。鍵一律是字串。
  • 使用 onSaveInstanceState() 回呼將其他資料儲存至您要保留的套件 (即使應用程式會自動關閉)。如要將資料放入套件,請使用開頭為 put 的套件方法,例如 putInt()
  • 您可以從 onRestoreInstanceState() 方法中的套件,或更常見的 onCreate() 取回資料。onCreate() 方法的 savedInstanceState 參數可用來保存套件。
  • 如果 savedInstanceState 變數為 null,表示活動是在沒有狀態套件的情況下開啟動,且沒有可擷取的狀態資料。
  • 如要透過鍵從套件擷取資料,請使用以 get 開頭的 Bundle 方法,例如 getInt()

設定變更

  • 設定變更於裝置狀態改變時發生,因此基本上,使系統解決變更的最簡單方法就是刪除,然後重新建立活動。
  • 最常見的設定變更範例為,使用者將裝置從直向轉為橫向模式,或從橫向轉為直向模式時。裝置語言變更或外接硬體鍵盤時,也可能會發生設定變更。
  • 發生設定變更時,Android 會叫用所有活動生命週期的關閉回呼。接著,Android 會從頭重新啟動活動,並執行所有生命週期啟動回呼。
  • Android 因設定變更而關閉應用程式時,Android 會以 onCreate() 可用的狀態套件來重新啟動活動。
  • 如同程序中斷,請將應用程式的狀態儲存到 onSaveInstanceState() 的套件中。

7. 瞭解詳情