1. 程式人生 > >Activity生命週期及啟動模式——程式碼實踐篇

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啟動程式,用於測試結果。