最近遇到了一個小問題,在我使用了多種Activity啟動模式的時候,重新打開其中的一個Activity會啟動另一個我已經停止的Activity,從而調用了一些已經失效的方法導致程序崩潰。
1 問題重現
由于項目工程復雜,Activity名稱不夠直觀,我新建了一個ActivityTaskTest 工程來重現遇到的問題。
ActivityA是工程的主活動。因為一些必要的原因, ActivityA的啟動模式是SingleInstance的。ActivityA可以啟動ActivityB,ActivityB沒有設置任何啟動模式,即默認的standard啟動模式。在ActivityB中,將會啟動一個ServiceA。 ServiceA中啟動一個了一個ActivityC,由于Activity是在非Acitivity環境下啟動的,需要設置 FLAG_ACTIVITY_NEW_TASK標簽(這里就是我們討論的重點,稍候會詳細分析)。當ActivityC完成任務后會重新跳轉到ActivityA。
最后,見證奇葩的時刻到了,我們點擊ActivityA的啟動ActivityB的button,ActivityC出現在了我們的眼前,而不是ActivityB!!這一刻我仿佛劉謙附體,但在我發現我身邊并沒有董卿之后,我深刻地意識到了我是一個工程師,不能搞這些裝神弄鬼的事情。ok,Let's find out what‘s going on with our precious app!
2 FLAG_ACTIVITY_NEW_TASK使用分析
關于Activity啟動模式和Activity Task的內容推薦一篇非常好的文章:Android中Activity四種啟動模式和taskAffinity屬性詳解。這篇文章已經講得非常詳細了,這里就不再贅述了,偷個懶哈哈。
如果你已經看了文章,你應該已經知道問題的所在了,對,就是這個該死的taskAffinity。簡單的說,就是我們雖然使用了FLAG_ACTIVITY_NEW_TASK標簽去啟動了ActivityC,但是因為我們忘了給Activity設置taskAffinity這個小婊砸,所以導致ActivityC的taskAffinity值和ActivityB一樣都是默認的包名。所以我們啟動ActivityC的時候系統將ActivityC壓入了ActivityB所在的task。我們可以使用adb shell dumpsys activity activities 指令看下一在我們重新從A中啟動B之前,Task的情況:
我們可以看到正如我們所想的,ActivityC和ActivityB在一個Task中,由于ActivityA是singleInstance模式,所以A只能做一輩子單身狗了。那么為什么我們明明啟動的是B,怎么會出現C呢?
我們來先看看Google官方文檔對于FLAG_ACTIVITY_NEW_TASK是怎么說的:
在新的 task 中啟動 activity。如果要啟動的 activity 已經運行于某 task 中,則那個 task 將調入前臺,最后保存的狀態也將恢復,activity 將在onNewIntent()中接收到這個新 intent。
這個過程與前一節所述的"singleTask"launchMode模式值相同。
注意文檔中的內容,“如果要啟動的 activity 已經運行于某 task 中,則那個 task 將調入前臺,最后保存的狀態也將恢復”,注意這里是所在task被直接調入前臺,也就是說B所在的整個Task將被移入前臺。這就解釋了為什么我們去啟動B而出現的是C了。看起來我們好像大功告成了,但是,等等,總覺得哪里有點不太對勁,我們的ActivityB明明沒有設置啟動模式啊,你這個是FLAG_ACTIVITY_NEW_TASK標簽,我沒用啊,我讀書多你可別騙我。
仔細想想應該是ActivityA的singleInstance的鍋。
我們再來看看Google官方文檔對于singleInstance是怎么說的吧:
“singleTask”和“singleInstance”模式同樣只在一個方面有差異: “singleTask”Activity 允許其他 Activity 成為其任務的組成部分。 它始終位于其任務的根位置,但其他 Activity(必然是“standard”和“singleTop”Activity)可以啟動到該任務中。 相反,“singleInstance”Activity 則不允許其他 Activity 成為其任務的組成部分。它是任務中唯一的 Activity。 如果它啟動另一個 Activity,系統會將該 Activity 分配給其他任務 — 就好像 Intent 中包含FLAG_ACTIVITY_NEW_TASK一樣。
看到最后一句,終于可以結案了。也就是說,當一個被設置為singleInstance的Activity去啟動其他的Activity的時候,其默認是自帶FLAG_ACTIVITY_NEW_TASK標簽的。
3 總結
1、FLAG_ACTIVITY_NEW_TASK標簽必須配合taskAffinity屬性使用,如果不設置taskAffinity屬性值,將不會生成新task。
2、當從啟動模式為singleInstance的Acitivity中啟動新的Acitivity時,新的Activity自帶FLAG_ACTIVITY_NEW_TASK標簽。
心得:官方文檔是個好東西。