Activity生命週期及啟動模式——程式碼實踐篇
一、概述
本篇主要簡單介紹一下activity的生命週期和啟動模式。同時,我們會用程式碼實踐在不同的啟動模式下,生命週期的具體執行方式。
二、Activity的生命週期
首先,讓我們看一下關於Activity生命週期的一張經典圖片:
在講Activity生命週期之前,我們先需要理解三種活動的狀態。
前臺活動:當前activity正在前臺與使用者互動。
可見活動:當前activity對使用者是可見的,但是很可能不在與使用者進行互動。
後臺活動:activity對使用者不可見,資料在後臺被儲存起來。
從圖上可以看出,生命週期中,onCreate,onStart,onResume,onPause,onStop,onDestroy這六個方法最為重要,而這六個方法是兩兩對應的。
onCreate():activity被建立時呼叫,應該在這個方法中完成佈局載入,時間繫結等初始化操作。
onStart():activity可見時呼叫。
onResume():activity在前臺時呼叫。
onPause():activity不是在前臺是呼叫。
onStop():activity已經不可見時呼叫。
onDestroy():activity正在停止或即將被銷燬時呼叫。
下面,我們通過程式碼實踐中列印的日誌來了解android生命週期。
1、activity A 中啟動 activity B
2567-2567/example.com.activitydemo I/Lifecycle: A onPause 2567-2567/example.com.activitydemo I/Lifecycle: B onCreate 2567-2567/example.com.activitydemo I/Lifecycle: B onStart 2567-2567/example.com.activitydemo I/Lifecycle: B onResume 2567-2567/example.com.activitydemo I/Lifecycle: A onStop
從日誌中我們可以看出,A呼叫了onPause 和onStop進入了後臺狀態,而B被建立並且進入了前臺狀態。
那麼這裡A和B的生命週期有沒有固定的順序呢?在Android官方文件中,有寫出,只有在老的activity執行onPause完成後,才能執行新activity的onResume。然而從sdk23的原始碼中,我們可以看到其實是在老的activity執行onPause後,新的activity才被啟動,所以,若是activity的啟動機制沒有大改,老activity的onPause將會最先執行。而老activity的onStop則沒有固定的順序。
2、activity B 返回 activityA
2567-2567/example.com.activitydemo I/Lifecycle: B onPause
2567-2567/example.com.activitydemo I/Lifecycle: A onStart
2567-2567/example.com.activitydemo I/Lifecycle: A onResume
2567-2567/example.com.activitydemo I/Lifecycle: B onStop
2567-2567/example.com.activitydemo I/Lifecycle: B onDestroy
從日誌中我們可以看出,B被銷燬了,而A不需要重新建立,然後進入了前臺狀態。
順序和之前同理,老activity的onPause總被最先執行。
3、activity A 按home鍵
2567-2567/example.com.activitydemo I/Lifecycle: A onPause
2567-2567/example.com.activitydemo I/Lifecycle: A onStop
正常情況下,按home鍵之後,activity會進入後臺狀態。那麼有沒有異常情況呢?那肯定是有的。當系統記憶體不足時,會回收優先順序較低的activity,優先順序基本按照 前臺activity>可見activity>後臺activity。因此,進入後臺的activity很可能被回收。activity被回收時,會呼叫 onSaveInstanceState() 用於儲存現場,開發者可以重寫這個函式,用於activity被回收後儲存需要留存的變數,儲存現場後,會呼叫 onDestroy()銷燬activity。 當activity再次被啟動時,會先呼叫onCreate,然後呼叫onRestoreInstanceState()函式來恢復現場。
三、activity 啟動模式
activity有四種啟動模式:standard, singleTop,singleTask,singleInstance。
1、standard
標準啟動模式,系統預設的activity啟動模式,上個章節介紹的生命週期,均是以standard模式啟動。
2、singleTop
棧頂複用模式。使用這種模式啟動新的activity,如果將要啟動的activity在棧頂的話,將不會被重新建立例項。如果要啟動的不在棧頂的,那麼會新建一個例項,其過程和standard模式相同。
我們來看下已經在棧頂的E,以singleTop模式再次啟動E時,會發生什麼。
2567-2567/example.com.activitydemo I/Lifecycle: E onPause
2567-2567/example.com.activitydemo I/Lifecycle: E onNewIntent
2567-2567/example.com.activitydemo I/Lifecycle: E onResume
E呼叫了onPause退出前臺,然後呼叫onNewIntent,再呼叫onResume回到前臺。3、singleTask
棧內複用模式。類似於單例模式,在這種啟動模式下,activity只要在一個棧中存在,那麼就不會重新建立例項,並且在重新啟動時,會將activity調到棧頂,注意,這裡的調到棧頂指的是將被重新啟動的activity上面的所有activity全部出棧。
我們先來看下,在棧內為 AFBC其中F為棧內複用模式啟動,當我們再次啟動F時,生命週期會發生什麼
3071-3071/example.com.activitydemo I/Lifecycle: B onDestroy
3071-3071/example.com.activitydemo I/Lifecycle: C onPause
3071-3071/example.com.activitydemo I/Lifecycle: F onNewIntent
3071-3071/example.com.activitydemo I/Lifecycle: F onStart
3071-3071/example.com.activitydemo I/Lifecycle: F onResume
3071-3071/example.com.activitydemo I/Lifecycle: C onStop
3071-3071/example.com.activitydemo I/Lifecycle: C onDestroy
這裡可以看到,和我們上面說的情況相同,BC被銷燬出棧,使得F恢復到棧頂,然後F並不會重新建立,而是呼叫了onNewIntant函式。
當F在已經在棧頂,並且在前臺時,再次呼叫F,與singleTop的生命週期呼叫是相同的。
這裡還要再提到一種情況,那麼當有多個activity棧的時候,重新啟動一個singleTask模式的activity會出現什麼情況。
現在我們模擬一種情況: 後臺棧為 AFB,前臺棧為DC, 其中F為棧內複用模式啟動,C為當前前臺活動,如下圖
此時我們以singleTask模式啟動F會發生什麼情況呢
24481-24481/example.com.activitydemo I/Lifecycle: C onPause
24481-24481/example.com.activitydemo I/Lifecycle: B onDestroy
24481-24481/example.com.activitydemo I/Lifecycle: F onNewIntent
24481-24481/example.com.activitydemo I/Lifecycle: F onStart
24481-24481/example.com.activitydemo I/Lifecycle: F onResume
24481-24481/example.com.activitydemo I/Lifecycle: C onStop
從日誌可以看出, C並沒有被銷燬,而是轉到了後臺,B被銷燬退出了棧。F沒有被重新建立,而是被呼叫了onNewIntent函式,並且轉到了前臺。此時的活動棧情況如下圖:
想看詳細棧的情況的話,可用通過命令 adb shell dumpsys activity 來檢視,這裡就不貼出來了。
4、singleInstance
單例項啟動模式。這個和singleTask有些類似,簡單來說,是singleTask的加強版,即以singleInstance啟動模式啟動的activiy,在所有棧中只能存在一個例項,且該例項單獨佔有一個獨立的棧。在該例項的活動棧中啟動其他活動,也會轉到其他棧中建立。
我們構造一個G為單例項模式啟動,A、B為標準模式啟動,示意過程如下圖。
5、使用方式
在程式碼中,如需使用這四種啟動模式, 需要在AndroidManifest.xml中指定,如下:
<activity android:name=".D"
android:taskAffinity="com.example.activitydemo1"/>
<activity android:name=".E"
android:launchMode="singleTop"/>
<activity android:name=".F"
android:launchMode="singleTask"/>
<activity android:name=".G"
android:launchMode="singleInstance"/>
launchMode是指定activity的啟動模式,taskAffinity是指定activity的啟動棧。
四、Activity 啟動模式的 Flags
Activity的啟動模式,除了上面說的在xml檔案中指定外,還能可以在啟動時指定intent中的flags來啟動。
1、FLAG_ACTIVITY_NEW_TASK
這個關鍵字需要配合 taskAffinity屬性去使用。當你為activity指定一個任務棧時,需要通過該Flag來啟動activity,否則直接啟動的話,會使得activity無法在新的任務棧中啟動。這裡還有一個需要注意的地方,在taskAffinity所指定的任務棧中,已經存在將要啟動的activity例項,那麼使用FLAG_ACTIVITY_NEW_TASK啟動的話,將會使得startActivity失效,生命週期不會被呼叫。
在開發中時,我們都是通過Activity的上下文來啟動另一個Activity。那麼如果需要使用非Activity的上下文來啟動一個activity行不行呢?當然可以,但是不是直接啟動,直接啟動的話會報以下異常:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.
這個異常說明的很明顯,需要用FLAG_ACTIVITY_NEW_TASK這個flag來啟動activity。是的,這個就是FLAG_ACTIVITY_NEW_TASK的另一個作用,當你使用非activity的上下文啟動activity時,會因為沒有對應的任務棧,而導致啟動失敗,FLAG_ACTIVITY_NEW_TASK關鍵字便是建立一個新的任務棧,來存放啟動的activity。那麼會有人問,那應用啟動的第一個activity不是也沒有對應的任務棧嗎?是的,launch的activity的啟動模式實際上是singleTask,這個後面的部落格,會對這裡進行分析。
2、FLAG_ACTIVITY_SINGLE_TOP
這個關鍵字非常簡單,和singleTop啟動模式一模一樣,與在xml中指定launchMode為singleTop沒有區別,這裡就不詳述了。
3、FLAG_ACTIVITY_CLEAR_TOP
使用該flag啟動activity時,會檢視在啟動activity的任務棧中是否有相同的activity例項,有的話,則將該activity例項以及其之上的所有activity全部出棧,在建立一個新的activity例項壓入棧中。示意圖如下
如果有多個B的例項,那麼此時以FLAG_ACTIVITY_CLEAR_TOP啟動B,會出現什麼情況呢?
在有多個例項的情況下,只會出棧到最靠近棧頂的例項,如下圖:
那麼有多個任務棧時,FLAG_ACTIVITY_CLEAR_TOP會不會像singleTask一樣,將後臺任務棧切到前臺呢?
其實並不會,該flag只關注與activity將要進入的棧,如下圖:
五、最後說幾句
其實 activity的啟動,包括其生命週期,這裡只是一個概述,在具體的開發過程中,會遇到很多廠商修改系統,導致在一些操作中生命週期的執行和預想的並不相同。
在寫這篇文章之前,看了許多書籍和其他部落格,發現大家寫的不盡相同。此篇部落格的結論,是使用小米5、小米6、MX6等主流手機,在 Android 6.0 7.0 7.1 等主流版本上測試得到的結果,然後結合 SDK23的原始碼得出的結論。若在其他手機上有出入。可留言說明。
為了這篇部落格,寫了一個小的activity啟動程式,用於測試結果。