Android Activity的生命週期和啟動模式詳解

robert_chao發表於2016-07-27

Activity生命週期經典圖解:

按鍵對生命週期的影響:

BACK鍵:

當我們按BACK鍵時,我們這個應用程式將結束,這時候我們將先後呼叫onPause()->onStop()->onDestory()三個方法。

再次啟動App時,會執行onCreate()->onStart()->onResume()

HOME鍵:

當我們開啟應用程式時,比如瀏覽器,我正在瀏覽NBA新聞,看到一半時,我突然想聽歌,這時候我們會選擇按HOME鍵,然後去開啟音樂應用程式,而當我們按HOME的時候,Activity先後執行了onPause()->onStop()這兩個方法,這時候應用程式並沒有銷燬。

而當我們從桌面再次啟動應用程式時,則先後分別執行了onRestart()->onStart()->onResume()三個方法。

一般Activity切換正常生命週期(這裡的一般是指啟動模式為standard,切換activity時沒有加flag標誌):

ActivityA啟動ActivityB:

ActivityA 的生命週期onPause()->onStop(),

ActivityB的生命週期onCreate()->onStart()->onResume()。

ActivityB執行finish返回ActivityA:

ActivityB的生命週期onPause()->onStop()->onDestory()

ActivityA的生命週期了onRestart()->onStart()->onResume()

注意:當ActivityB定義為Dialog樣式時,ActivityA的生命週期是不一樣的,

我們給ActivityB加上theme

<style name="MyDialogStyle">  
      <item name="android:windowBackground">@android:color/transparent</item>  
      <item name="android:windowFrame">@null</item>  
      <item name="android:windowNoTitle">true</item>  
      <item name="android:windowIsFloating">true</item>  
      <item name="android:windowIsTranslucent">true</item>  
      <item name="android:windowContentOverlay">@null</item>  
      <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>  
      <item name="android:backgroundDimEnabled">true</item>  
  </style>

這個時候,ActivityA啟動ActivityB,B沒有完全遮擋A,ActivityB的生命週期跟剛才一樣,但是ActivityA並沒有執行onStop()

還有一點需要特別注意,Activity中直接彈dialog,Acitivity的生命週期是不會變化的。網上有些說法是會執行onPause(),其實並沒有執行!

另外還有幾個跟生命週期相關的方法

@Override  
  protected void onNewIntent(Intent intent) {  
      super.onNewIntent(intent);  
  }  

  @Override  
  protected void onSaveInstanceState(Bundle outState) {  
      super.onSaveInstanceState(outState);  
  }  

  @Override  
  protected void onRestoreInstanceState(Bundle savedInstanceState) {  
      super.onRestoreInstanceState(savedInstanceState);  
  }  

  @Override  
  public void onConfigurationChanged(Configuration newConfig) {  
      super.onConfigurationChanged(newConfig);  
  }

當應用執行起來後就會開啟一條執行緒,執行緒中會執行一個任務棧,當Activity例項建立後就會放入任務棧中。Activity啟動模式的設定在AndroidManifest.xml檔案中,通過配置Activity的屬性Android:launchMode=”"設定。

1. Standard模式(預設)

我們平時直接建立的Activity都是這種模式的Activity,這種模式的Activity的特點是:只要你建立了Activity例項,一旦啟用該Activity,則會向任務棧中加入新建立的例項,退出Activity則會在任務棧中銷燬該例項。

standard模式是所啟動的Activity都是在同一個task容器棧下,不會重新建立新的task容器棧。先壓入棧的Activity例項按順序入棧底,後入棧在棧頂,處於棧的頂部Activity例項處於活動狀態,其他處於非活動狀態。按物理返回鍵,退出當前所處活動狀態Activity視窗,這樣就會從task容器棧中彈出,顯示在手機主螢幕上,從而,有非活動狀態轉換成活動的狀態。其次,standard容器棧可能會存在著相同的Activity例項,只有沒呼叫一次startActivity方法,就會建立目標Activity例項物件壓入task容器棧。

如果Activity啟動順序為A->B->B->A->D,棧中的Acitivy為ABBAD(最先建立的A位於棧底,最後建立的D位於棧頂)

2. SingleTop模式

這種模式會考慮當前要啟用的Activity例項在任務棧中是否正處於棧頂,如果處於棧頂則無需重新建立新的例項,會重用已存在的例項,否則會在任務棧中建立新的例項。

SingleTop有個不錯的用法是防止多次點選建立多個Activity,無論start幾次,SingleTop模式能保證棧頂只有一個例項。 如果Activity啟動順序為A->B->B->A->D,棧中的Acitivy為ABAD(當B位於棧頂時,再次啟動B的時候,B不會重新建立)

3. SingleTask模式

如果任務棧中存在該模式的Activity例項,則把棧中該例項以上的Activity例項全部移除,呼叫該例項的newInstance()方法重用該Activity,使該例項處於棧頂位置,否則就重新建立一個新的Activity例項。

singletask模式,特別需要注意了。啟動的目標Activity例項如果已經存在task容器棧中,不管當前例項處於棧的任何位置,是棧頂也好,棧底也好,還是處於棧中間,只要目標Activity例項處於task容器棧中,都可以重用該Activity例項物件,然後,把處於該Activity例項物件上面全部Activity例項清除掉,並且,task容器棧中永遠只有唯一例項物件,不會存在兩個相同的例項物件。所以,如果你想你的應用不管怎麼啟動目標Activity,都只有唯一一個例項物件,就使用這種啟動模式。 如果Activity啟動順序為A->B->B->A->D,棧中的Acitivy為AD(當A再次被啟動時,A會被移到棧頂,位於A上面的Acitivity全部會出棧)

4. SingleInstance模式

當該模式Activity例項在任務棧中建立後,只要該例項還在任務棧中,即只要啟用的是該型別的Activity,都會通過呼叫例項的newInstance()方法重用該Activity,此時使用的都是同一個Activity例項,它都會處於任務棧的棧頂。此模式一般用於載入較慢的,比較耗效能且不需要每次都重新建立的Activity。

singleInstance啟動模式,簡單說就是可以共享某個Activity。比如,應用1的任務容器棧中建立了MainActivity例項,應用2也要啟用MainActivity,則不需要建立MainActivity例項,直接可以公用MainActivity例項。 尤其值得注意:應用1啟動MainActivity,按home鍵;開啟應用2啟動應用1的MainActivity例項。在按home鍵,開啟應用1,這時候應用1的介面是應該是處於MainActivity介面例項。 SingleInstance的一個任務棧中只有一個Activity,並保證不再有其他Activity例項進入。

特別需要注意的生命週期onNewIntent

當一個Activity被start,而不需要重新建立時,就會執行onNewIntent生命週期。如果一個Activity的啟動模式是SingleTask,我們可以在onNewIntent中執行一些重新整理操作等。

我們一般會把MainAcitivy設定為SingleTask,除了保證MainActivity的唯一,還可以利用singleTask的特性做一些清理工作。自動管理棧,銷燬無用的Acitivity.

Intent Flags

Flags: 表示Intent的標誌位,常用於Activity的場景中,它和Activity的啟動模式有著密切的聯絡。
下面列舉的是和本文主題相關的Flags屬性:

Intent.FLAG_ACTIVITY_NEW_TASK (預設)

預設的跳轉型別,它會重新建立一個新的Activity,不過與這種情況,比如說Task1中有A,B,C三個Activity,此時在C中啟動D的話,如果在AndroidManifest.xml檔案中給D新增了Affinity的值和Task中的不一樣的話,則會在新標記的Affinity所存在的Task中壓入這個Activity。如果是預設的或者指定的Affinity和Task一樣的話,就和標準模式一樣了啟動一個新的Activity.

FLAG_ACTIVITY_SINGLE_TOP

這個FLAG就相當於啟動模式中的singletop,例如:原來棧中結構是A B C D,在D中啟動D,棧中的情況還是A,B,C,D。

FLAG_ACTIVITY_CLEAR_TOP

這個FLAG就相當於啟動模式中的SingleTask,這種FLAG啟動的Activity會把要啟動的Activity之上的Activity全部彈出棧空間。例如:原來棧中的結構是A B C D ,從D中跳轉到B,棧中的結構就變為了A B了。

FLAG_ACTIVITY_BROUGHT_TO_FRONT

這個網上很多人是這樣寫的。如果activity在task存在,拿到最頂端,不會啟動新的Activity。這個有可能會誤導大家! 他這個FLAG其實是這個意思!比如說我現在有A,在A中啟動B,此時在A中Intent中加上這個標記。此時B就是以FLAG_ACTIVITY_BROUGHT_TO_FRONT方式啟動,此時在B中再啟動C,D(正常啟動C,D),如果這個時候在D中再啟動B,這個時候最後的棧的情況是 A,C,D,B。如果在A,B,C,D正常啟動的話,不管B有沒有用FLAG_ACTIVITY_BROUGHT_TO_FRONT啟動,此時在D中啟動B的話,還是會變成A,C,D,B的。

FLAG_ACTIVITY_NO_USER_ACTION

onUserLeaveHint()作為activity週期的一部分,它在activity因為使用者要跳轉到別的activity而要退到background時使用。比如,在使用者按下Home鍵,它將被呼叫。比如有電話進來(不屬於使用者的選擇),它就不會被呼叫。

那麼系統如何區分讓當前activity退到background時使用是使用者的選擇?

它是根據促使當前activity退到background的那個新啟動的Activity的Intent裡是否有FLAG_ACTIVITY_NO_USER_ACTION來確定的。

注意:呼叫finish()使該activity銷燬時不會呼叫該函式

FLAG_ACTIVITY_NO_HISTORY

意思就是說用這個FLAG啟動的Activity,一旦退出,它不會存在於棧中,比如原來是A,B,C這個時候再C中以這個FLAG啟動D的,D再啟動E,這個時候棧中情況為A,B,C,E。

Activity相關屬性taskAffinity

Activity 中的 android:taskAffinity 這個屬性介紹:

Activity為Task擁有的一個affinity。擁有相同的affinity的Activity理論上屬於相同的Task(在使用者的角度是相同的“應用程式”)。Task的affinity是由它的根Activity決定的。

affinity決定兩件事情——Activity重新宿主的Task(參考allowTaskReparenting特性)和使用FLAG_ACTIVITY_NEW_TASK標誌啟動的Activity宿主的Task。

預設情況,一個應用程式中的所有Activity都擁有相同的affinity。捏可以設定這個特性來重組它們,甚至可以把不同應用程式中定義的Activity放置到相同的Task中。為了明確Activity不宿主特定的Task,設定該特性為空的字串。

如果這個特性沒有設定,Activity將從應用程式的設定那裡繼承下來(參考<application>元素的taskAffinity特性)。應用程式預設的affinity的名字是<manifest>元素中設定的package名。

相關文章