Activity的啓動模式

引言

當面試官說請你介紹一下activity啓動模式,大多數人都能整兩句,什麼棧頂複用啊棧內複用啊,不過,你肯定你真的懂啓動模式嗎?android

若是你能回答出下面的問題,那麼你能夠直接退出當前界面。面試

假設有以下四個activity:瀏覽器

  1. A(standard)
  2. B(singleTop)
  3. C(singleTask)
  4. D(singleInstance)

它們的啓動順序依次是ABCDABCD,請描述activity棧內變化。bash

基於交互的分析

例: 1,用戶在主屏幕中點擊應用的圖標啓動應用後,彈出了第一Activity界面:A,並依次打開了以下界面 A -> B -> C -> D。 2,此時按下home鍵返回主屏幕,而後從新點擊圖標啓動這個應用,咱們會發現彈出的界面仍是 D 而不是界面 A。 3,當咱們連續點擊返回鍵時,應用中界面會按照啓動順序反向的依次展現,也就是D -> C -> B -> A -> 主屏幕微信

經過這個例子咱們能夠知道Android系統會爲應用暫時性的保存一組Activity啓動鏈,記錄啓動順序,這就引出了第一個概念:任務markdown

任務

先說下任務的定義,Android官方把上述這種爲了完成某些工做而鏈式啓動的一系列Activity合集稱之爲 任務數據結構

咱們都知道每一個Activity都是互相獨立的界面,正是有了任務這樣的概念,多個Activity纔可以關聯起來組成一個完整的應用。app

任務能夠同時存在多個嗎測試

固然能夠!spa

例:平時咱們使用手機常常會在刷微博和聊微信來回切換,每次切換系統都會爲咱們保存上一次離開的狀態。

任務裏Activity必須是來自同一個應用嗎

固然不是!

例:當咱們在社交軟件設置用戶頭像時通常會有拍照和相冊兩個選項,選擇拍照會跳轉到攝像機軟件,選擇相冊會跳到系統相冊軟件。經過這幾個軟件之間的共同合做完成了一次任務。

任務中的根Activity

一般狀況下,咱們都是經過設備主屏幕點擊應用圖標啓動應用的,同理設備主屏幕也是大多數任務的起點,而應用中的入口Activity就是這個任務的根Activity,根Activity的聲明方式你必定特別熟悉:

<activity
       android:name=".HelloActivity""> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> 複製代碼

當用戶點擊主屏幕應用圖標打開應用時,若是該應用最近不曾被使用過,則會建立一個任務,並將該應用中的入口Activity做爲任務中的根Activity打開。反之直接把該應用所在的任務調出來置於前臺便可。

瞭解完任務以後,咱們就大概知道了上述幾個例子中Android系統如何保存Activity使用狀態的規則。

返回棧

任務呢是一個特別虛的概念,是爲了方便開發者理解纔有的它,而系統中真正存儲Activity的是一個遵循先進後出原則的數據結構:棧。通常叫它返回棧(任務棧,堆棧,其實叫什麼的都有)。

返回棧是任務的實際載體,每一個任務中全部的Activity都會按照各自的打開順序保存在對應的返回棧中。因此Android系統顯示界面的順序是先找到要顯示界面所在的任務,而後在對應的返回棧中找到顯示的Activity。

值得一提的是因爲返回棧存儲結構的特殊性,外部只能訪問到棧頂的Activity,也就是最後入棧的那個。因此一個Activity想要能顯示在屏幕上那麼它必須存在於棧頂位置。

進棧與出棧

當前 Activity 啓動另外一個 Activity 時,新的 Activity 會被推送到堆棧頂部,成爲焦點顯示在屏幕上。 前一個 Activity 仍保留在堆棧中,可是處於中止狀態。

用戶按「返回」按鈕時,當前 Activity 會從堆棧頂部彈出(Activity 被銷燬),而前一個 Activity 恢復執行。若是用戶繼續按「返回」,堆棧中的相應 Activity 就會彈出,以顯示前一個 Activity,直到用戶返回主屏幕爲止(或者,返回任務開始時正在運行的任意 Activity)。 當全部 Activity 均從堆棧中移除後,任務即不復存在。棧也就會被回收掉。

特殊的任務

經過前面的瞭解,咱們知道若是要打開新的界面須要把Activity實例放到當前任務對應的返回棧的棧頂。該操做是無論該Activity以前有沒有實例化過或者棧中是否已經存在了的。

可是,有些特殊狀況下,咱們會發現一些「例外」。

例1:當來自多個不一樣任務中的應用選擇使用系統瀏覽器訪問網頁的時候,瀏覽器應用並不會在每一個任務的返回棧中都建立Activity,而是將全部網頁以選項卡的形式展現在同一個界面中。

本例中瀏覽器應用的Activity若是已經實例化過了就不會從新建立。

例2:小明在微信中向你分享了一條微博內容,你打開後跳轉到了微博APP中的該條微博詳情頁,當你看完內容後按返回鍵退出該界面發現並非回到了微信聊天界面,而是來到了微博主頁(或上一次在微博中停留的界面)。

本例中微博詳情頁的Activity雖然是由微信應用所在的任務啓動,可是沒有加入到微信應用的任務中,而是加入到了微博的任務棧中。

管理任務

很顯然上述兩個例子在實際使用中並很多見,對於這種特殊的狀況咱們須要針對性的管理任務,而衆所周知的啓動模式僅僅是其中的一種。

定義啓動模式

定義Activity的啓動模式其實就是定義一個Activity的新實例如何(是否)與當前任務作關聯。以什麼樣的方式進入到當前(或其餘)任務中。

若是你只說Activity的啓動模式有四種,實際上是不許確的,由於咱們能夠經過兩種方法定義不一樣的啓動模式:

  • 使用AndroidManifest.xml中定義 在AndroidManifest.xml中<activity>標籤下使用lauchMode屬性來指定當前這個activity的啓動模式。

  • 使用Intent標誌定義 在調用startActivity(Intent intent)前,經過調用intent.addFlags()或者intent.setFlags()方法爲Intent添加一個標誌,用於爲將要啓動的Activity聲明啓動模式。

那二者有什麼區別呢?

上述兩種方法都可覺得activity聲明啓動模式,只是使用情景不一樣。

  1. 若是咱們但願某個activity在任何狀況下都會執行一種特殊的啓動模式,咱們就能夠採用AndroidManifest.xml的方法聲明。

  2. 若是咱們但願某個activity大多數狀況下正常啓動,而少數狀況下執行特殊的啓動模式,咱們就能夠在須要執行特殊啓動模式時在Intent中添加標誌聲明。

  3. 若是一個activity兩種方式都聲明瞭的話,使用Intent標誌的方式要比AndroidManifest.xml的優先級高。

  4. 兩種方式中定義的啓動模式有些是不同的,Intent標誌中定義的某些啓動模式AndroidManifest.xml中沒有,反之同樣。

  5. 咱們常說的四種啓動模式其實說的是AndroidManifest.xml中定義的。

使用AndroidManifest.xml聲明啓動模式

在清單文件中聲明 Activity 時,您可使用<activity>元素的 ][launchMode屬性指定 Activity 應該如何與任務關聯。

您能夠分配給 launchMode 屬性的啓動模式共有四種:

  • standard
  • singleTop
  • singleTask
  • singleInstance

先不用管他們具體的操做是什麼,咱們首先要知道這四種啓動模式能夠分爲兩大類:

  • standardsingleTop 該類啓動模式的activity能夠被屢次的實例化,它們的實例能夠放到任何任務中,而且能夠位於返回棧的任何位置。
  • singleTasksingleInstance 帶有此類啓動模式的activity,它們只能有一個實例存在,且實例只能存在於單獨的任務中。

standard:標準模式

默認啓動模式,啓動activity時直接建立新的實例並壓入啓動它的任務棧頂。

singleTop:棧頂複用模式

該模式惟一與standard不一樣的就是,若是啓動singleTop模式的activity時發現當前任務的棧頂已經存在着這個activity的實例,那麼就不會建立新的實例,而是調用該實例的onNewIntent()方法。其餘的跟標準模式同樣。

singleTask:棧內複用模式

這個模式有些特殊一點,咱們先按使用情景介紹它,當咱們將要啓動該模式的activity時,系統會判斷當前是否有它想要的任務棧:

  1. 沒有它要的任務棧 系統會新建立一個任務,並將該activity實例化做爲該任務的根activity。

  2. 有它要的任務棧 這時候系統會找到該任務棧,若是任務棧裏只有它本身則直接調用該activity實例的onNewIntent()方法。若是任務棧中它的上方還存在別的activity,那麼這些activity會被所有彈出棧。

至於什麼是「它想要的任務棧」,咱們會在下面單獨分析。

singleInstance:單例模式 基本上跟singleTask相同,會爲activity單首創建一個任務並可以複用。可是該模式的activity不容許其餘activity跟本身存在於同一個任務中,由此 activity 啓動的任何 activity 均會被在其餘的任務中打開。

使用Intent標誌聲明啓動模式

此方式能夠經過調用intent.addFlags(int flags)或者intent.setFlags(int flags)方法爲Intent添加一個標誌,用於爲將要啓動的Activity聲明啓動模式。

在開始介紹前,先進行幾點掃盲科普:

  1. 一個Intent能夠設置多個標誌,這就是爲啥有addflags()setFlags()兩個方法的緣由了。
  2. 爲Intent設置標誌的參數都是Intent類的靜態常量。
  3. 設置Intent標誌不光只有設置activity啓動模式這一個功能,設置不一樣的參數還有其餘功能。
  4. Intent標誌中能夠對activity啓動模式進行操做的標誌可多了,咱們只介紹特別典型的三種。

Intent.FLAG_ACTIVITY_SINGLE_TOPAndroidManifest.xml方式中的singleTop啓動模式。

Intent.FLAG_ACTIVITY_NEW_TASKAndroidManifest.xml方式中的singleTask啓動模式。

Intent.FLAG_ACTIVITY_CLEAR_TOP 若是即將啓動的 activity 已經存在於當前任務棧中,則會彈出銷燬它上方的全部 activity,並調用該activity實例的onNewIntent()方法,而不是啓動該 Activity 的新實例。

singleTask有點像但不同,在AndroidManifest.xml方式中沒有與此對應的值。

singleTask默認就包含了FLAG_ACTIVITY_CLEAR_TOP的功能。

關聯任務

在分析singleTask時有提到過該模式下啓動activity前會去找「它想要的任務棧」,那麼如何去找呢?這就引出了AndroidManifest.xml<activity>標籤下的taskAffinity屬性。

taskAffinity 屬性

taskAffinity 屬性學名任務相關性,說白了其實就是這個參數能夠指定當前activity所屬任務棧的名字,該屬性的值爲字符串:

例:android:taskAffinity="com.test.demo.task1"

若是你在<activity>標籤沒指定這個屬性,那麼它就用<application>標籤的taskAffinity屬性,若是<application>標籤下也沒指定,它就應用包名當作默認值。

taskAffinity 與 singleTask

瞭解到taskAffinity屬性後咱們在從新梳理一下singleTask啓動模式。

  • 若是咱們指定了taskAffinity屬性的值,那麼就跟以前分析的同樣,建立新任務等等...
  • 若是咱們未指定taskAffinity屬性的值,新activity就與當前任務的taskAffinity屬性值同樣,因此新activity的實例會被放置到當前的任務棧中。

除了singleTask呢? 如今咱們知道了taskAffinity屬性能夠強行指定activity所屬的任務棧,那麼這個屬性在其餘啓動模式狀況下是什麼樣的呢?網上好多人都說沒有效果,我不信就親自測試了一下得出如下結論:

  1. 剛介紹 SingleInstance的時候說它跟singleTask同樣都會新建一個任務,既然singleTask是根據taskAffinity屬性來決定是否須要新建任務的,那麼singleInstance是否是也須要指定這個屬性呢? 答案是否!只要啓動模式爲singleInstance它必定會單獨開一個任務。

  2. SingleTop模式下指定了taskAffinity屬性的值後,他就會神奇的切換到指定的那個任務棧中,除此以外跟原來同樣。

  3. 最神奇的就是Standard,它也一樣受到了taskAffinity屬性的影響,也會切換到指定的那個任務棧中,但當咱們屢次啓動這個activity時它不會再屢次的建立實例,而是拉起了以前啓動過的實例,更特殊的是,其餘三種啓動模式在複用以前實例時都會調用onNewIntent()方法,他卻不會調用該方法。

taskAffinity的其餘做用

taskAffinity還有一個功能就是能夠從新定向所屬任務,意思就是這個activity原來是屬於任務A的,當有一個跟該activity的taskAffinity屬性值相同的任務B被建立後,這個activity就會從任務A中轉移到任務B中。

想要實現這個功能咱們還須要allowTaskReparenting屬性的配合:

  1. 咱們在清單文件中給taskAffinity="A"的activity標籤下添加屬性android:allowTaskReparenting=true
  2. taskAffinity="B"的任務下啓動這個activity,此時這個activity存在於任務B中。
  3. taskAffinity="A"的任務被建立或者被置於前臺,該activity將被轉移到其任務棧中,位於棧頂位置。

清理任務

若是用戶長時間離開任務,則系統會清除全部 Activity 的任務,根 Activity 除外。 當用戶再次返回到任務時,僅恢復根 Activity。系統這樣作的緣由是,通過很長一段時間後,用戶可能已經放棄以前執行的操做,返回到任務是要開始執行新的操做。

您可使用下列幾個 Activity 屬性修改此行爲:

alwaysRetainTaskState 若是在任務的根 Activity 中將此屬性設置爲 "true",則不會發生剛纔所述的默認行爲。即便在很長一段時間後,任務仍將全部 Activity 保留在其堆棧中。

clearTaskOnLaunch 若是在任務的根 Activity 中將此屬性設置爲 "true",則每當用戶離開任務而後返回時,系統都會將堆棧清除到只剩下根 Activity。 即便只離開任務片刻時間,用戶也始終會返回到任務的初始狀態。

finishOnTaskLaunch 相似於clearTaskOnLaunch,可是更狠一些,當用戶離開任務再回來的時候,整個任務的activity都會清除,連根activity也是,至關於第一次啓動這個任務。

啓動模式的實際應用

我的以爲常見的四種啓動模式中要屬singleTop不是很好理解,其餘的還好。

singleTop

singleTop模式的activity特色就是除了外部能夠啓動它顯示信息外,它也能夠用一樣的方式啓動本身更新顯示信息,這樣就減小了冗餘代碼,下降了維護成本。

例:若是讓你設計一個帶有搜索應用的APP,主頁有一個搜索框,輸入信息點擊搜索按鈕進入結果頁顯示結果,爲方便用戶使用,結果頁也有一個搜索框,跟主頁的搜索框功能同樣,你會怎麼設計?

singleTask

這個啓動模式能夠分爲兩種狀況:

  1. 未指定taskAffinity 此時該activity能夠當應用中的某一模塊的入口處

有以下啓動流程,微信主頁 >> 聊天頁 >> 聊天設置頁 >> 用戶資料頁 >> 聊天頁,此時咱們按下返回鍵直接回到了微信主頁。

  1. 指定了taskAffinity 若是利用該啓動模式新開了任務,在用戶的視角里就至關開了兩個應用(在任務管理器中會看到兩個最近應用),因此謹慎使用,我能想到的使用狀況就是一個Word應用打開了兩份文檔。

singleInstance

這種狀況下無論有多少個任務啓動它,它都會做爲一個單獨任務存在着,這種模式極其特殊,謹慎使用。

例:撥號界面,鬧鐘界面。

相關文章
相關標籤/搜索