iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 20
1
Software Development

Kotlin for Android系列 第 20

Day 20. Android Activity 生命週期 - 2/6

  今天要講的內容是 Activity 的生命週期,以下是取自 Android 官方開發手冊,今天會以上一章節開發的應用程式,來講解生命週期的流程。

https://ithelp.ithome.com.tw/upload/images/20181103/20111944t43e5Er5fQ.png

  onCreate 大家應該會有印象,每個 .kt 檔產生時都會有這個週期的片段在其中,此週期在開啟應用程式時會是第一個進入的流程,並且在應用程式週期中,也只會執行這一次,之前的範例中會拿來宣告物件監聽,例如按鈕的點擊動作。

https://ithelp.ithome.com.tw/upload/images/20181103/201119440FFMTHBoCn.png


https://ithelp.ithome.com.tw/upload/images/20181103/20111944zvre09ZCTE.png

  onCreate 方法執行完成後,就由 onStart 接力,onStart 的工作是負責將 activity 頁面可視化,並將 UI 物件初始化以便讓使用者互動,這些動作完成後,就會跳至 onResume 方法。

  onResume 時期應用程式處於持續執行中,且一直與使用者互動著,直到使用者的焦點移轉,例如:切換到另一個 Activity 頁面,或是開啟其它應用程式 (有人來電),此時就會進入 onPause 暫停的狀態。


https://ithelp.ithome.com.tw/upload/images/20181103/20111944ITYHTuV8el.png

  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 一系列流程。


https://ithelp.ithome.com.tw/upload/images/20181103/201119440Fy1Kw6Uwz.png

  最後是 onDestroy 階段,除了使用者終止應用程式就會觸發這個流程外,Android 作業系統也可能會因為系統資源極度缺乏的情況下,自動終止在 onStoponPause 狀態的應用程式。


  接下來讓我們用程式碼展示一下實際執行流程,首先建立一個新的 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.ktSecondActivity.kt,把原本繼承的類別改成我們剛剛建立的 Flow,如下:

class MainActivity : AppCompatActivity() --- X
class MainActivity : FlowActivity() --- O
class SecondActivity : FlowActivity() --- O

  這時你會遇到下圖的問題,FlowActivity 這個類別型態是 final,沒辦法用於繼承,主要是建立時沒有指定的話,預設就會是 final,修正很簡單,只要回到 Flowclass 前方加入 open 關鍵字即可。

https://ithelp.ithome.com.tw/upload/images/20181103/201119446hTyRQ2M6A.png

open class FlowActivity : AppCompatActivity() 

  以上的繼承手法是透過自建一個類別 (Flow) 繼承原本必須的 AppCompat,再讓 Main 繼承自建的類別,如此一來每當執行 Main 時,就會執行到我們設計的 Log.d,現在可以試著啟動執行,在下方頁籤選擇 Logcat (或快捷建 Alt + 6),再將篩選器設定為 Debug (因為使用 Log.d),並輸入剛剛設定的 TAG 標籤進行篩選 (如果這個標籤訂得很特殊,前方的篩選器其實可以不用選擇),如此一下來就看到如下的資訊(有兩行是因為作者有按一下按鈕切換到第二頁面):

https://ithelp.ithome.com.tw/upload/images/20181103/20111944Em07O4yfX6.png

  接著就可以進入重頭戲,將所有生命週期加到 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-lifecycle

Log-Android Developers
https://developer.android.com/reference/android/util/Log

Manage your app's memory-Android Developers
https://developer.android.com/topic/performance/memory#ReleaseMemoryAsUiGone

BroadcastReceiver接收事件-By Ivan from itHelp
https://ithelp.ithome.com.tw/articles/10188791

管理應用行為顯示生命週期-Android Developers (中文)
https://developer.android.com/training/basics/activity-lifecycle


上一篇
Day 19. Android Activity 生命週期 - 1/6
下一篇
Day 21. Android Activity 生命週期 - 3/6
系列文
Kotlin for Android30

尚未有邦友留言

立即登入留言