今天要講的內容是 Activity 的生命週期,以下是取自 Android 官方開發手冊,今天會以上一章節開發的應用程式,來講解生命週期的流程。
onCreate 大家應該會有印象,每個 .kt 檔產生時都會有這個週期的片段在其中,此週期在開啟應用程式時會是第一個進入的流程,並且在應用程式週期中,也只會執行這一次,之前的範例中會拿來宣告物件監聽,例如按鈕的點擊動作。
onCreate 方法執行完成後,就由 onStart 接力,onStart 的工作是負責將 activity 頁面可視化,並將 UI 物件初始化以便讓使用者互動,這些動作完成後,就會跳至 onResume 方法。
onResume 時期應用程式處於持續執行中,且一直與使用者互動著,直到使用者的焦點移轉,例如:切換到另一個 Activity 頁面,或是開啟其它應用程式 (有人來電),此時就會進入 onPause 暫停的狀態。
onPause 觸發時,可以將你的應用程式做一些特別的動作,例如偵測到有人來電,音樂撥放器就將音樂暫停,如果選擇拒接(在通知列拒接而非開啟電話 App 拒接),就會再回到 onResume 階段;但若接起電話,開啟了電話 App或回到主畫面、開啟其他的 App,就會進入 onStop 階段。
onStop 可設計將手機的資源釋放出來,讓給使用者進行其它操作使用,例如在 onStop 方法中將 BroadcastReceiver 釋放掉,如原本用來撰寫監聽網路連線狀態,並會在畫面上提示使用者,但是現在使用者已經不再關注於本應用程式中,就沒有必要繼續占用資源,這是 Memory Management 的概念。
同時,取消註冊已經沒有用途的監聽也能省電,另外需特別注意的是,隨著 Android 版本的演進,例如在 Android 7.0 開始注重系統電量控制,一些傳統作法也須跟著調整,可以參考:Background Optimizations in Android 7.0 Nougat,本文就不深入討論。
onStop 在上面的流程圖中有個左側支點,意思代表若有其它優先權更高的應用程式執行,並且手機的資源不夠 (記憶體),就會將本應用程式處理階段清除掉,釋放資源,等到使用者回來就會從 onCreate 全新的開始。右側支點,代表資源充足的情況下,使用者做完其它事情再度回到本應用程式時,就會進入 onRestart 接續 OnStart > OnResume 一系列流程。
最後是 onDestroy 階段,除了使用者終止應用程式就會觸發這個流程外,Android 作業系統也可能會因為系統資源極度缺乏的情況下,自動終止在 onStop 或 onPause 狀態的應用程式。
接下來讓我們用程式碼展示一下實際執行流程,首先建立一個新的 EmptyActivity,可以命名 FlowActivity,接著要運用 Log 技巧,這個 API 能用於將程式執行階段的狀態記錄下來,顯示在 Logcat 視窗,有幾種用法:
Log.v()
=> verbose,用於記錄詳細資料Log.d()
=> debug,用於除錯註記使用Log.i()
=> info,用於一般資訊記錄Log.w()
=> warning,用於警告訊息記錄Log.e()
=> error,用於錯誤訊息記錄Log.wtf()
=> What a Terrible Failue,用於毀滅等級的訊息記錄... 在 FlowActivity.kt 中先加入一個變數 TAG
,這個標籤是方便之後在記錄視窗中辨識使用,接著在 onCreate()
內加入一段 Log.d()
,第一個參數是指定 TAG
,第二個參數放要記錄的文字,在這邊使用 javaClass.simpleName
取得當前頁面的物件名稱,後方的用法則是取方法名稱 (開頭的 {}
兩個括號是必要的)。
class FlowActivity : AppCompatActivity() {
val TAG = "Flow"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_flow)
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
}
第二步驟到 MainActivity.kt、SecondActivity.kt,把原本繼承的類別改成我們剛剛建立的 Flow
,如下:
class MainActivity : AppCompatActivity() --- X
class MainActivity : FlowActivity() --- O
class SecondActivity : FlowActivity() --- O
這時你會遇到下圖的問題,FlowActivity
這個類別型態是 final
,沒辦法用於繼承,主要是建立時沒有指定的話,預設就會是 final
,修正很簡單,只要回到 Flow
在 class
前方加入 open
關鍵字即可。
open class FlowActivity : AppCompatActivity()
以上的繼承手法是透過自建一個類別 (Flow) 繼承原本必須的 AppCompat
,再讓 Main
繼承自建的類別,如此一來每當執行 Main
時,就會執行到我們設計的 Log.d
,現在可以試著啟動執行,在下方頁籤選擇 Logcat
(或快捷建 Alt + 6),再將篩選器設定為 Debug
(因為使用 Log.d),並輸入剛剛設定的 TAG
標籤進行篩選 (如果這個標籤訂得很特殊,前方的篩選器其實可以不用選擇),如此一下來就看到如下的資訊(有兩行是因為作者有按一下按鈕切換到第二頁面):
接著就可以進入重頭戲,將所有生命週期加到 FlowActivity
上,以下作者已經幫您做苦工了:
open class FlowActivity : AppCompatActivity() {
val TAG = "Flow"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_flow)
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
override fun onStart() {
super.onStart()
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "頁面:${javaClass.simpleName},流程:${{ }.javaClass.enclosingMethod!!.name}")
}
}
在執行前可以在 Logcat
按垃圾桶圖示,將先前的記錄刪除以便觀察,在我們啟動模擬器後,可以看到以下三行記錄。
D/Flow: 頁面:MainActivity,流程:onCreate
D/Flow: 頁面:MainActivity,流程:onStart
D/Flow: 頁面:MainActivity,流程:onResume
下一章將進行一些流程操作,進行完整的生命週期展示,另外上面使用的列出目前 Activity 及 Method 的使用方式也可以再改良一下,這些會明日的課程中說明,我們明天見!
資料參考
Understand the Activity Lifecycle-Android Developers
https://developer.android.com/guide/components/activities/activity-lifecycleLog-Android Developers
https://developer.android.com/reference/android/util/LogManage your app's memory-Android Developers
https://developer.android.com/topic/performance/memory#ReleaseMemoryAsUiGoneBroadcastReceiver接收事件-By Ivan from itHelp
https://ithelp.ithome.com.tw/articles/10188791管理應用行為顯示生命週期-Android Developers (中文)
https://developer.android.com/training/basics/activity-lifecycle