Android中的Launch Mode詳情

一. 多任務和Task、啟動模式

Android 手機在早期,下方通常會內置三個實體的觸摸按鍵,分別是:桌面菜單返回。大概在Android 5.0 之後,Android開始流行系統內置虛擬按鍵,其中的菜單被替換成瞭多任務,一旦我們按下多任務按鍵,一個個的任務快照就以流的形式展現在屏幕之上。

隨著Google對多任務更好地支持,越來越多的廠商將正面的實體按鍵,替換成瞭虛擬按鍵,也將菜單按鍵刪除,替換成瞭多任務視圖按鈕,顯然多任務在之後的Android版本中是非常重要的一個概念。

甚至由於虛擬按鍵的出現,一些特定型號的手機在下方可能會形成奇怪的五層下巴:

我們將這一個個的任務叫做Task,多任務視圖中,會顯示各個Task頂層Activity的快照(之所以是快照,是因為Activity不一定是存活的,有可能它隻是一張圖片。)Task中,以回退棧的形式堆疊Activity。

image.png

每個Task,對應一個名稱,如果我們不去設置,那麼就是我們Application的應用包名,默認情況下,start一個新的Activity就會被裝入該Task當中,然後在這個Task中進行堆疊,新打開的Activity在上方,用戶可以通過按下返回鍵回退到上一個Activity當中。

每個Activity,都有一個TaskAffinity屬性,標志瞭它想去的Task是哪一個,如果你不填寫,那麼通常是默認的目標棧,但是要註意的是,TaskAffinity需要和LaunchMode搭配在一起使用

  • 如果不設置LaunchMode(即采用默認的Standard),那麼你從ActivityA中啟動一個設置瞭TaskAffinity的ActivityB,你會發現它不生效,它仍然在當前包名的ActivityA的對應的任務棧當中。
  • 如果你將TaskAffinity配置和LaunchMode = SingleTask一起使用,在你打開瞭ActivityB時,你按下多任務按鈕,你會發現一個App出現瞭兩個Task,即兩個回退棧,這兩個回退棧的分別對應ActivityA和ActivityB的對應的Task的回退棧:

它們擁有相同的項目名稱,因為隻是名為router的app模塊下的兩個不同TaskAffinity的Activity,不過這個TaskAffinity並不顯示在多任務視圖當中。

二. 四種啟動模式詳解

所以,四種啟動模式,對應的具體的啟動情況如下:

1. Standard

在當前的Task的回退棧中,啟動一個Activity實例,放在棧頂。

在這種模式下,使用TaskAffinity是無效的。即使填寫瞭TaskAffinity,最終也會被創建在執行啟動命令的Activity對應的Task棧的棧頂,而不是TaskAffinity對應的Task的棧頂。

2. SingleTask

在TaskAffinity指定的退回棧中嘗試啟動一個Activity實例:

  • 如果指定的回退棧中,含有該Activity相同類型的實例,那麼就回調onNewIntent()方法,告知原先已經存在的實例X,然後回調onResume()方法,並將原先實例上方的實例全部移除出回退棧,這樣一來,原先已經存在的實例X就會出現在棧頂。
  • 如果指定的回退棧中,不包含Activity相同類型的實例,那麼就在棧頂創建,走正常的生命周期回調:onCreate()->onStart()->onResume()
  • 如果TaskAffinity對應的Task都不存在的情況下,會先去創建目標Task,再走創建Activity實例的流程,最後壓入棧頂。
  • 如果沒有指定TaskAffinity,那麼就指定為當前調用啟動操作的Activity的Task,將Activity壓入該Task回退棧的棧頂。

註意:

創建在當前Task和其它Task的Activity跳轉動畫是不相同的。

SingleTask + TaskAffinity實際上也是一種全局的單例,因為它的創建結果最終都會將Activity創建在指定的TaskAffinity的Task下。即使不填寫TaskAffinity,也會隻在當前Activity對應的Task下創建或者復用唯一的一個實例。

3. SingleTop

在TaskAffinity指定的退回棧中嘗試在啟動一個Activity實例。和SingleTask類似,但是復用條件稍有不同。僅在目標Task的回退棧的頂部含有相同類型的Activity時,觸發復用,回調onNewIntent和onResume。

4.SingleInstance

在TaskAffinity指定的退回棧中,創建一個Activity實例,但是,一個Task中僅允許一個Activity存在,如果兩個Activity對應的TaskAffinity是相同的,例如從A中以SingleInstance啟動B,B中以SingleInstance啟動C,BC的TaskAffinity是相同的。

這種情況下,如果我們在C中,呼出多任務視圖菜單,我們會發現,此時棧中隻有C和A對應的Task,B對應的Task並不存在,你可能會認為B和C在同一個棧中,B在C之下。但是如果你以另外一種方式: 在C中,呼出多任務菜單,回到C後,然後點擊返回,你認為應該回到B,但是你會發現,直接退到桌面瞭。 或者你在C中,直接按回車,你會發現從C->B和從B->A的跳轉過場動畫,都是Task間的轉場動畫,而不是Task內部的跳轉動畫。

這涉及到另外我們就要講另外一個問題,Task間跳轉時,Task間的堆疊問題(Task疊在另一個Task上面),而打開多任務列表或者按下Home鍵會導致堆疊被破壞。

B和C即使有同一個TaskAffinity命名,並且根據我們說的一個Task中僅允許一個Activity存在,C打開時,B應該被關閉,從多任務上來看,似乎也是這樣的,因為隻有C和A的Task在多任務視圖中,但是我們確實又可以從C的Task返回到B的Task,在任務棧中我們又看不到B的身影,這可以下一個結論:多任務視圖中的不可見的Task不一定不存在,如果發生SingleInstance + TaskAffinity沖突的情況,例如:

Activity A和Activity B都是SingleInstance的,並且又都是設置瞭TaskAffinity為 com.example.newTask。如果既要保證AB都在同一個Task中,又要保證該Task隻能有一個>Activity,那麼就會導致沖突。

經過測試,當沖突發生時,Android會為兩個Activity都創建一個Task,但是同一時間,隻有一個能>在多任務視圖中被看見,但是如果順序啟動:A、B,是可以在B中回退到A中的。並且回退的動畫是Task 間切換的動畫。

那麼Task中可見的Activity一定在運行嗎?答案也是否定的,如果我們在ActivityA按下返回鍵,退回到桌面,我們此時打開多任務,我們會發現,ActivityA的Task的快照仍然保留在多任務視圖之上,但是它此時已經“死瞭”,我們點擊它,實際上是創建瞭一個新的ActivityA。所以,多任務視圖中中的不可見的Task不一定不存在,多任務視圖中可見的Activity也不一定就是Running狀態的

三. Task間堆疊與Task Reparenting

1. Task間堆疊

考慮如下的兩個任務棧間切換場景:

 我們需要從ActivityC中啟動Activity E,此時ActivityE的啟動模式被設置成瞭:SingleTask,TaskAffinity是Task2。ActivityE被啟動之後,在屏幕上顯示出來瞭。

基於這個狀態,接下來有幾個問題:

  • 問題1:如果此時按多次返回鍵,會發生什麼?
  • 問題2:如果此時先按Home回到桌面,再從多任務列表打開Task1,顯示的是ActivityC還是ActivityE?
  • 問題3:如果此時先按Home回到桌面,再從多任務列表打開Task2,顯示的是ActivityE,如果此時不不斷地按返回鍵,會發什麼?
  • 問題4:如果此時先打開多任務列表,再按返回,返回Task2,此時顯示的是ActivityE。如果此時不不斷地按返回鍵,會發什麼?

對於問題1,答案是:Activity E/D/C/B/A同時出棧,A出棧之後回到桌面。 對於問題2,答案是:顯示的是ActivityC 對於問題3、4,答案是:Activity E/D出棧,D出棧之後,回到桌面,而不是Activity C。

問題1的原因是因為,Task2的任務被打開之後,整個Task2成為瞭優先顯示的Task,被堆在屏幕之上,一旦Task2的Activity退完瞭,Task1可以無縫地銜接上:

 就好像Task2被堆在瞭Task1之上,這樣的堆疊,保證瞭用戶交互的連貫性。

這樣的Task間的堆疊跳轉特性適用於用戶跳轉某個頁面之後,按返回鍵不想馬上跳回原Activity的一種情況。

但是,這種堆疊僅限於Task跳轉剛剛發生的情況,一旦用戶進行瞭:

  • Home鍵返回桌面
  • 切出多任務視圖

這類的操作,將頂端的Task變為後臺Task之後,那麼這種「堆疊」就會立刻失效,並且不再恢復,所以在問題3、4中,最後返回的是桌面,而不是Task1的ActivityC,這種堆疊被破壞瞭,Task1和Task2又重新回到平級的狀態瞭。

但是在最新的API32版本中,切到Android 自帶的多任務視圖並不會導致Task2重新被移動到Task1平級的位置,這意味著,你從多任務切回來之後,在ActivityE按下返回,仍然會回退到到Task1的ActivityC之上。

如果有一些場景,你希望打開ActivityE之後不退到D,直接退到C那應該怎麼做呢?其實不設置SingleTask就好瞭,默認的就是這種情況,比如外賣平臺支付訂單之後,付款軟件的的付款結果頁的一個實例就被留在瞭外賣軟件的Task中。

2. Task Reparenting/Task重定父級

通常來說,一個Activity被裝進一個Task的棧之後,就不會去移動瞭,但是我們可以借助Task Reparenting來做父級的重新指定。

例如上述的例子中,我們隊ActivityE設置:allowTaskReparenting。我們從Task1中的ActivityC啟動ActivityE時,ActivityE會啟動在Task1中。

一旦我們切到桌面,再重新從桌面圖標重新打開Task1時,我們會發現ActivityE從Task1中消失瞭,而從桌面打開Task2對應的App圖標時,會發現,ActivityE重新回到瞭自己的Task2中,例如我們按照如下定義:

        <activity
            android:name=".ActivityE"
            android:allowTaskReparenting="true"
            android:exported="false"
            android:taskAffinity="com.example.anotherTask" />

如果我們在默認的,和包名相同的com.rEd.router下創建該MainActivityE,那麼此時的MainActivity會在當前的com.rEd.router下創建該MainActivityE實例,然後我退到桌面,再重新在桌面的App圖標打開App,我會發現,啟動的App的當前的Activity變成瞭ActivityC,而不是ActivityE:

而且,多任務視圖中的Task2中的快照是空白的,我們點開Task2,發現ActivityE回到瞭它taskAffinity指定的Task中:

另外,如果同時設置瞭allowTaskReparenting=true和LaunchMode,那麼LaunchMode會優先生效,Activity會直接創建在其他的Task中。

到此這篇關於Android中的Launch Mode詳情的文章就介紹到這瞭,更多相關Android Launch Mode內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: