Activity生命週期與啟動模式

陳子豪發表於2018-10-17

目錄

一、生命週期

    1. Activity的各種生命週期
    1. onSaveInstanceState() 與 onRestoreInstanceState()
    1. Activity生命週期的變化

二、啟動模式

    1. 標準模式 standard
    1. 棧頂複用模式 singleTop
    1. 棧內複用模式 singleTask
    1. 單例項模式 singleInstance
    1. Intent中設定啟動模式

三、總結


一、生命週期

1. Activity的各種生命週期

系統中的Activity由Activity堆疊管理,當啟動一個新的Activity的時候,這個Activity被放置在棧頂,並處於正在執行狀態。前一個Activity在堆疊中位於新的Activity下面,並且在新的Activity退出前不會出現在前臺。

下面用一張圖展示Activity完整生命週期(圖來自於Android官網):

圖來自於Android官網

  • onCreate 在Activity第一次被建立時呼叫onCreate方法。我們通常在onCreate方法中載入佈局,初始化控制元件。
  • onStart 在Activity變為可視的時候,呼叫onStart方法。
  • onResume 當Activity處於棧頂,並處於正在執行狀態,可以與使用者進行互動的時候,呼叫onResume方法。
  • onPause 當Activity已經失去焦點,且依舊是可視狀態時呼叫onPause方法,此時Activity無法與使用者進行互動。
  • onStop 當Activity從可視變為不可視的時候,呼叫onStop方法。
  • onDestory onDestory方法在Activity被銷燬前呼叫。
  • onRestart onRestart方法在Activity被重新啟動時呼叫,在Activity第一次被建立的時候不會呼叫。

完整生命週期: 完整生命週期在Activity第一次建立呼叫onCreate方法到銷燬前呼叫onDestroy方法之間。 可視生命週期: 可視生命週期在Activity呼叫onStart方法變為可視到呼叫onStop方法變為不可視之間。 前臺生命週期: 前臺生命週期在Activity呼叫onResume方法變得可與使用者互動到呼叫onPause方法變得不可與使用者互動之間。

2. onSaveInstanceState() 與 onRestoreInstanceState()

系統在“未經你許可”銷燬Activity時呼叫onSaveInstanceState方法,用於儲存Activity狀態資訊。onRestoreInstanceState方法在Activity被系統銷燬之後恢復Activity時被呼叫,用於恢復Activity狀態資訊。可重寫這兩個方法在系統“未經你許可”銷燬Activity時儲存和恢復你的資料。 所謂“未經你許可”指的是非人為銷燬Activity,例如按下回退鍵退出頁面屬於人為,系統因記憶體不足回收銷燬Activity屬於非人為。

  • 什麼時候呼叫onSaveInstanceState方法 (1)、當使用者按下HOME鍵時。 (2)、切換到其他程式時。 (3)、鎖屏時。 (4)、啟動新的Activity時。 (5)、螢幕方向切換時。

  • 什麼時候呼叫onRestoreInstanceState方法 在Activity被系統銷燬,又回到該Activity的時候。如使用者按下HOME鍵又馬上返回該Activity,這個時候該Activity一般不會因為記憶體不足而被系統回收,故不呼叫onRestoreInstanceState方法。所以onSaveInstanceState與onRestoreInstanceState不一定會成對被呼叫。

3. Activity生命週期的變化

分別新建3個Activity,MainActivity、TestActivity、TransparentActivity,並在每個Activity中重寫onCreate,onStart,onResume,onPause,onStop,onDestroy,onRestart,onSaveInstanceState,onRestoreInstanceState這9個方法,如下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.act_main)
        Log.e("MainActivity", "onCreate")
    }

    override fun onStart() {
        super.onStart()
        Log.e("MainActivity", "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.e("MainActivity", "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.e("MainActivity", "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.e("MainActivity", "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.e("MainActivity", "onDestroy")
    }

    override fun onRestart() {
        super.onRestart()
        Log.e("MainActivity", "onRestart")
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        Log.e("MainActivity", "onRestoreInstanceState")
    }

    override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        Log.e("MainActivity", "onSaveInstanceState")
    }
}
複製程式碼

程式碼準備好了,來觀察下面各種請看下Activity生命週期的變化:

  • 啟動Activity並點選回退鍵退出Activity

啟動Activity,首先建立Activity,然後頁面變得可見,最後頁面準備好與使用者互動,所以生命週期的呼叫順序應該是 onCreate() -> onStart() -> onResume(),如下圖所示:

Activity生命週期與啟動模式

點選回退鍵退出Activity,首先停止與使用者互動,然後頁面變得不可見,最後銷燬Activity,所以生命週期的呼叫順序應該是 onPause() -> onStop() -> onDestory(),如下圖所示:

Activity生命週期與啟動模式

  • 按下HOME鍵並再次點選應用

按下HOME鍵後,首先頁面停止所有互動,然後頁面變得不可見,最後回到桌面。這時候頁面並沒有被銷燬,只是轉入後臺,但是也存在被系統回收的風險,所以系統會在onPause和onStop之間呼叫onSaveInstanceState方法儲存Activity狀態資訊,所以生命週期的呼叫順序應該是 onPause() -> onSaveInstanceState() -> onStop(),如下圖所示:

Activity生命週期與啟動模式

再次點選應用,如果應用沒有被系統回收,只是從後臺調回前臺,那麼首先是重新開啟頁面,然後頁面變得可見,最後頁面變得可以與使用者互動,所以生命週期的呼叫順序應該是 onRestart() -> onStart() -> onResume(),如下圖所示:

Activity生命週期與啟動模式

  • 切換到其他應用並切回來

當我們切換到其他應用的時候,頁面首先是停止與使用者互動,然後頁面變得不可見,然後進入到別的應用中,由於頁面存在被系統回收的風險,所以系統會在onPause和onStop之間呼叫onSaveInstanceState方法儲存Activity狀態資訊,所以生命週期的呼叫順序應該是 onPause() -> onSaveInstanceState() -> onStop(),如下圖所示:

Activity生命週期與啟動模式

切回來的時候,如果應用沒有被系統回收,首先是重新開啟頁面,然後頁面變得可見,最後頁面變得可以與使用者互動,所以生命週期的呼叫順序應該是 onRestart() -> onStart() -> onResume(),如下圖所示:

Activity生命週期與啟動模式

  • 鎖屏並解鎖

鎖屏的時候,頁面首先是停止與使用者互動,然後頁面變得不可見,然後鎖屏,由於頁面存在被系統回收的風險,所以系統會在onPause和onStop之間呼叫onSaveInstanceState方法儲存Activity狀態資訊,所以生命週期的呼叫順序應該是 onPause() -> onSaveInstanceState() -> onStop(),如下圖所示:

Activity生命週期與啟動模式

解鎖的時候,如果應用沒有被系統回收,首先是重新開啟頁面,然後頁面變得可見,最後頁面變得可以與使用者互動,所以生命週期的呼叫順序應該是 onRestart() -> onStart() -> onResume(),如下圖所示:

Activity生命週期與啟動模式

  • 跳轉到另一個非透明Activity並點選回退鍵返回

從MainActivity跳轉到TestActivity,首先停止MainActivity的互動,然後建立TestActivity,然後TestActivity變得可見,然後TestActivity變得可以與使用者互動,最後MainActivity變得不可見,由於MainActivity存在被系統回收的風險(可能性不大),所以系統會呼叫onSaveInstanceState方法儲存MainActivity狀態資訊。生命週期的呼叫順序如下圖所示:

Activity生命週期與啟動模式

點選回退鍵返回MainActivity,首先停止TestActivity的互動,然後重新開啟MainActivity,然後MainActivity變得可見,然後MainActivity變得可以與使用者互動,然後TestActivity變得不可見,最後TestActivity被銷燬。生命週期的呼叫順序如下圖所示:

Activity生命週期與啟動模式

  • 跳轉到另一個透明Activity並點選回退鍵返回

在styles中建立一個透明的主題:

<style name="AppTransparentTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="windowNoTitle">true</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
</style>
複製程式碼

TransparentActivity在AndroidManifest中宣告透明主題

 <activity android:name=".TransparentActivity"
            android:theme="@style/AppTransparentTheme"/>
複製程式碼

從MainActivity跳轉到透明的Activity,也就是TransparentActivity。由於TransparentActivity是一個透明的Activity,所以他的底層MainActivity還是可視的,所以MainActivity的onStop方法沒有被呼叫,只呼叫了onPause方法。生命週期的呼叫如下圖所示:

Activity生命週期與啟動模式

點選返回鍵,回到MainActivity。因為MainActivity跳轉到TransparentActivity時並沒有變為不可視,所以沒有呼叫onStop,回到MainActivity之後,也不需要重新啟動MainActivity,所以只需要恢復互動就可以了。生命週期的呼叫如下圖所示:

Activity生命週期與啟動模式

  • 由豎屏轉為橫屏

旋轉螢幕後,系統會銷燬然後啟動該Activity,首先停止Activity的互動,然後Activity變得不可見,然後銷燬Activity,銷燬過後啟動Activity,然後Activity變得可見且螢幕旋轉,最後旋轉螢幕過後的Activity變得可以互動。由於旋轉螢幕屬於“未經你許可”銷燬Activity,所以同步執行onSaveInstanceState和onRestoreInstanceState這兩個方法來儲存和恢復Activity的狀態。生命週期的呼叫順序如下圖所示:

Activity生命週期與啟動模式


二、啟動模式

Activity儲存在Activity棧中,每次建立Activity都會在Activity棧中新增,Activity被銷燬時也會從Activity棧中退出。大量的建立、銷燬Activity會對系統造成很大開銷,為了節省開銷,我們可能會複用一些Activity,Activity的啟動模式就是為了方便複用Activity而設計的。下面來看一下Activity的4種啟動模式:

1. 標準模式 standard

standard是Activity的預設啟動模式,如果沒有指定啟動模式的話,這個Activity就是標準模式。我們可以在AndroidManifast裡面為Activity設定啟動模式,如下所示:

<activity
    android:name=".MainActivity"
    android:launchMode="standard" />
複製程式碼

啟動模式為標準模式的Activity,每次啟動的時候都會在Activity棧頂建立一個例項,舉個例子:

Activity生命週期與啟動模式

建立兩個Activity,並在各個宣告週期中打一個log。當前為MainActivity,點選下面的按鈕跳轉到啟動模式為標準模式的AActivity,這時Activity棧裡面有兩個Activity,棧頂為AActivity。我們可以通過adb命令來檢視當前應用中Activity棧裡的資訊,在AndroidStudio的Terminal中輸入命令:adb shell " dumpsys activity | grep xxx.xxx.xxx.xxx",xxx為你的包名。按下回車後會輸出一大段資訊,可以從中找到一段這樣的:

Activity生命週期與啟動模式

圖中StackId為Activity的Id,sz為棧內Activity的數量,AActivity在棧內的位置位於MainActivity的上方。

上面是MainActivity跳轉到AActivity的例子,那麼如果多次啟動啟動模式為標準模式的MainActivity,如下所示:

Activity生命週期與啟動模式

Activity棧內有兩個MainActivity,再來看一下生命週期:

Activity生命週期與啟動模式

從上圖可看到MainActivity被前後建立了兩次。既然都是MainActivity,我們可以用其他啟動模式來複用MainActivity,減少系統開銷。下面來看一下其他啟動模式。

2. 棧頂複用模式 singleTop

棧頂複用模式指的是,假如Activity處於棧頂,再次啟動這個Activity的時候,複用該Activity。舉個例子: 把AActivity的啟動模式設定為棧頂複用模式

<activity
    android:name=".AActivity"
    android:launchMode="singleTop" />
複製程式碼

現在從MainActivity跳轉到AActivity,再從AActivity建立自己,執行adb命令檢視Activity棧,如下所示: 先看一下生命週期。

Activity生命週期與啟動模式

從MainActivity跳轉到AActivity時呼叫了了onCreate方法,AActivity建立自己時,只呼叫了onPause方法,然後馬上呼叫onResume方法,說明AActivity在棧頂時,通過棧頂複用模式再次啟動自己的時候得到了複用。

再來看一下Activity棧,只有一個AActivity:

Activity生命週期與啟動模式

啟動模式為棧頂複用模式的AActivity在棧頂時,建立自己能得到複用,那麼不在棧頂的時候呢?下面來看一下: 新建一個BActivity,現在執行的動作為從MainActivity跳轉到AActivity,然後從AActivity跳轉到BActivity,然後再從BActivity跳轉到AActivity,檢視AActivity是否複用。 先看生命週期:

Activity生命週期與啟動模式

AActivity分別在從MainActivity跳轉和從BActivity跳轉的時候都呼叫了onCreate方法,說明建立了兩個AActivity。因為從AActivity跳轉到BActivity的時候,BActivity在棧頂,所以從BActivity跳轉到AActivity,的時候,不能複用。再來看看Activity棧:

Activity生命週期與啟動模式

一共4個Activity,其中有兩個AActivity,沒有被複用。這種情況下,如果想複用AActivity,可以使用棧內複用模式。下面來看看棧內複用模式。

3. 棧內複用模式 singleTask

棧內複用模式跟棧頂複用模式相似,只要在同一個棧內啟動模式為棧內複用模式的Activity,再次啟動的時候就可以複用。先舉一個跟棧頂複用模式相似的例子: 把AActivity的啟動模式設定為棧頂複用模式

<activity
    android:name=".AActivity"
    android:launchMode="singleTop" />
複製程式碼

現在從MainActivity跳轉到AActivity,再從AActivity建立自己,執行adb命令檢視Activity棧,如下所示:

Activity生命週期與啟動模式

從結果來看跟棧頂複用模式一樣,AActivity得到了複用。下面再從AActivity跳轉到BActivity,然後從BActivity跳轉到AActivity,執行adb命令檢視Activity棧:

Activity生命週期與啟動模式

結果也是隻有兩個Activity,AActivity在棧頂。BActivity去哪了呢?來看一下生命週期:

Activity生命週期與啟動模式

上圖中AActivity只呼叫了1次onCreate,說明AActivity得到了複用,BActivity在最後執行了onDestroy方法,說明BActivity被銷燬了。原來棧內複用模式還有清除頂部的效果,當AActivity跳轉到BActivity時,BActivity在棧頂,這個時候BActivity跳轉到棧內複用模式的AActivity,AActivity來到了棧頂,之前在AActivity上面的全部被頂出棧外,也就是被清除了。

4.單例項模式 singleInstance

被設定啟動模式為單例項模式的Activity獨自享有一個Activity棧。舉個例子: 把AActivity啟動模式設定為單例項模式:

<activity
    android:name=".AActivity"
    android:launchMode="singleInstance" />
複製程式碼

MainActivity跳轉到AActivity,然後從AActivity跳轉到BActivity,檢視生命週期和Activity棧的資訊:

Activity生命週期與啟動模式

上圖中,#41和#42是兩個不同的Activity,由於AActivity啟動模式是單例項模式,獨享一個棧,而BActivity不是單例項模式,屬於預設棧,這時棧頂為BActivity,點選返回鍵,退回到MainActivity,如下所示:

Activity生命週期與啟動模式

再看一下生命週期:

Activity生命週期與啟動模式

由於棧頂是BActivity,與MainActivity是同一個棧,所以在BActivity出棧被銷燬後,MainActivity回到棧頂,而AActivity在另一個棧中,不受影響。這個時候再次跳轉到AActivity,AActivity得到複用,生命週期如下所示:

Activity生命週期與啟動模式

總的來說,單例項模式的Activity就是一旦被建立,除非被銷燬,否則無論從哪裡跳轉該Activity,都能得到複用。

5. Intent中設定啟動模式

除了在AndroidManifest裡設定Activity的啟動模式之外,還能在startActivity時傳入的Intent裡設定啟動模式,下面列舉一些常用的啟動模式標誌:

  • FLAG_ACTIVITY_SINGLE_TOP 同棧頂複用模式singleTop
  • FLAG_ACTIVITY_CLEAR_TOP 類似棧內複用模式singleTask,不同的是,FLAG_ACTIVITY_CLEAR_TOP會把自己和在他上面的Activity全部清除,再重新建立自己,而singleTask不會清除自己。
  • FLAG_ACTIVITY_NEW_TASK 類似棧內複用模式singleTask,但不具備清除頂部棧的效果。
  • FLAG_ACTIVITY_NEW_DOCUMENT 在一個新的棧中啟動Activity

三、總結

Activity是Android四大元件之一,也是與使用者互動最頻繁的元件。本篇文章主要介紹了Activity的生命週期與啟動模式。掌握這兩個知識點能讓我們在開發時得到很大的便利,以最正確的姿勢寫出想要的效果。

微信掃描下方二維碼關注微信公眾號"AndroidCzh"一起學習吧!這裡將長期為您分享原創文章、Android開發經驗等!

Activity生命週期與啟動模式
另外還有Android開發QQ交流群: 705929135

相關文章