Android中Activity launchMode

源自:技術(shù)小黑屋

Android系統(tǒng)中的Activity可以說(shuō)一件很贊的設(shè)計(jì),它在內(nèi)存管理上良好的設(shè)計(jì),使得多任務(wù)管理在Android系統(tǒng)中運(yùn)行游刃有余。但是Activity絕非啟動(dòng)展示在屏幕而已,其啟動(dòng)方式也大有學(xué)問(wèn),本文講具體介紹Activity的啟動(dòng)模式的諸多細(xì)節(jié),糾正一些開(kāi)發(fā)中可能錯(cuò)誤的觀點(diǎn),幫助大家深入理解Activity。

行文之前
在正式行文之前,先介紹一些文章提到的概念

  • 文章后續(xù)會(huì)提到Task,這里的Task指的是與用戶交互的Activity實(shí)例的集合。
  • Task中的Activity實(shí)例以棧的形式存放,這個(gè)棧就是Activity的回退棧。

本文圖片較多,在看圖時(shí),請(qǐng)注意觀察Activity頂部的title,來(lái)區(qū)分具體Activity。

為何有啟動(dòng)模式

應(yīng)用中的每一個(gè)Activity都是進(jìn)行不同的事物處理。以郵件客戶端為例,InboxActivity目的就是為了展示收件箱,這個(gè)Activity不建議創(chuàng)建成多個(gè)實(shí)例。而ComposeMailActivity則是用來(lái)撰寫郵件,可以實(shí)例化多個(gè)此Activity對(duì)象。合理地設(shè)計(jì)Activity對(duì)象是否使用已有的實(shí)例還是多次創(chuàng)建,會(huì)使得交互設(shè)計(jì)更加良好,也能避免很多問(wèn)題。至于想要達(dá)到前面的目標(biāo),就需要使用今天的Activity啟動(dòng)模式。

如何使用

使用很簡(jiǎn)單,只需要在manifest中對(duì)應(yīng)的Activity元素加入android:launchMode屬性即可。如下述代碼

<activity  android:name=".SingleTaskActivity"  android:label="singleTask launchMode"  android:launchMode="singleTask"> </activity> 

接下來(lái)就是介紹launchMode的四個(gè)值的時(shí)刻了。

standard

這是launchMode的默認(rèn)值,Activity不包含android:launchMode或者顯示設(shè)置為standard的Activity就會(huì)使用這種模式。
一旦設(shè)置成這個(gè)值,每當(dāng)有一次Intent請(qǐng)求,就會(huì)創(chuàng)建一個(gè)新的Activity實(shí)例。舉個(gè)例子,如果有10個(gè)撰寫郵件的Intent,那么就會(huì)創(chuàng)建10個(gè)ComposeMailActivity的實(shí)例來(lái)處理這些Intent。結(jié)果很明顯,這種模式會(huì)創(chuàng)建某個(gè)Activity的多個(gè)實(shí)例。

Android 5.0之前的表現(xiàn)
這種Activity新生成的實(shí)例會(huì)放入發(fā)送Intent的Task的棧的頂部。下圖為啟動(dòng)同一程序內(nèi)的Activity。

pre_lollipop_standard_activity_in_same_app.jpg

下面的圖片展示跨程序之間調(diào)用,新生成的Activity實(shí)例會(huì)放入發(fā)送Intent的Task的棧的頂部,盡管它們屬于不同的程序。


pre_lollipop_standard_activity_across_app.jpg

但是當(dāng)我們打開(kāi)任務(wù)管理器,則會(huì)有一點(diǎn)奇怪,應(yīng)為顯示的任務(wù)是Gallery,展示的界面確實(shí)另一個(gè)程序的Activity(因?yàn)槠湮挥赥ask的棧頂)。


pre_lollipop_task_manager_across_app.jpg

這時(shí)候如果我們從Gallery應(yīng)用切換到撥號(hào)應(yīng)用,再返回到Gallery,看到的還是這個(gè)非Gallery的Activity,如果我們想要對(duì)Gallery進(jìn)行操作,必須按Back鍵返回到Gallery界面才可以。確實(shí)有點(diǎn)不太合理。

Android5.0及之后表現(xiàn)
對(duì)于同一應(yīng)用內(nèi)部Activity啟動(dòng)和5.0之前表現(xiàn)一樣,變化的就是不同應(yīng)用之間Activity啟動(dòng)變得合理了。
跨應(yīng)用之間啟動(dòng)Activity,會(huì)創(chuàng)建一個(gè)新的Task,新生成的Activity就會(huì)放入剛創(chuàng)建的Task中。如下圖

lollipop_across_app_new_task.jpg

同時(shí)任務(wù)管理器查看任務(wù)也顯得更加合理了。

lollipop_task_manager_standard.jpg

假設(shè)之前存在我們的測(cè)試程序,然后從Gallery又分享文件到我們的測(cè)試程序,則對(duì)應(yīng)的任務(wù)管理器展示效果如下。

lollipop_standard_across_app_alread_exists.jpg

使用場(chǎng)景:standard這種啟動(dòng)模式適合于撰寫郵件Activity或者社交網(wǎng)絡(luò)消息發(fā)布Activity。如果你想為每一個(gè)intent創(chuàng)建一個(gè)Activity處理,那么就是用standard這種模式。

SingleTop

singleTop其實(shí)和standard幾乎一樣,使用singleTop的Activity也可以創(chuàng)建很多個(gè)實(shí)例。唯一不同的就是,如果調(diào)用的目標(biāo)Activity已經(jīng)位于調(diào)用者的Task的棧頂,則不創(chuàng)建新實(shí)例,而是使用當(dāng)前的這個(gè)Activity實(shí)例,并調(diào)用這個(gè)實(shí)例的onNewIntent方法。

singletop.jpg

singleTop這種模式下,我們需要處理應(yīng)用這個(gè)模式的Activity的onCreateonNewIntent兩個(gè)方法,確保邏輯正常。

使用場(chǎng)景
關(guān)于singleTop一個(gè)典型的使用場(chǎng)景就是搜索功能。假設(shè)有一個(gè)搜索框,每次搜索查詢都會(huì)將我們引導(dǎo)至SearchActivity查看結(jié)果,為了更好的交互體驗(yàn),我們?cè)诮Y(jié)果頁(yè)頂部也放置這樣的搜索框。

假設(shè)一下,SearchActivity啟動(dòng)模式為standard,那么每一個(gè)搜索都會(huì)創(chuàng)建一個(gè)新的SearchActivity實(shí)例,10次查詢就是10個(gè)Activity。當(dāng)我們想要退回到非SearchActivity,我們需要按返回鍵10次,這顯然太不合理了。
但是如果我們使用singleTop的話,如果SearchActivity在棧頂,當(dāng)有了新的查詢時(shí),不再重新創(chuàng)建SearchAc實(shí)例,而是使用當(dāng)前的SearchActivity來(lái)更新結(jié)果。當(dāng)我們需要返回到非SearchActivity只需要按一次返回鍵即可。使用了singleTop顯然比之前要合理。

總結(jié)

  • 只有在調(diào)用者和目標(biāo)Activity在同一Task中,并且目標(biāo)Activity位于棧頂,才使用現(xiàn)有目標(biāo)Activity實(shí)例,否則創(chuàng)建新的目標(biāo)Activity實(shí)例。

  • 如果是外部程序啟動(dòng)singleTop的Activity,在Android 5.0之前新創(chuàng)建的Activity會(huì)位于調(diào)用者的Task中,5.0及以后會(huì)放入新的Task中。

singleTask

singleTask這個(gè)模式和前面提到的standard和singleTop截然不同。使用singleTask啟動(dòng)模式的Activity在系統(tǒng)中只會(huì)存在一個(gè)實(shí)例。如果這個(gè)實(shí)例已經(jīng)存在,intent就會(huì)通過(guò)onNewIntent傳遞到這個(gè)Activity。否則新的Activity實(shí)例被創(chuàng)建。

同一程序內(nèi)
如果系統(tǒng)中不存在singleTask Activity的實(shí)例,那么就需要?jiǎng)?chuàng)建這個(gè)Activity的實(shí)例,并且將這個(gè)實(shí)例放入和調(diào)用者相同的Task中并位于棧頂。

singletask_inapp_create_new_instance.jpg

如果singleTask Activity實(shí)例已然存在,那么在Activity回退棧中,所有位于該Activity上面的Activity實(shí)例都將被銷毀掉(銷毀過(guò)程會(huì)調(diào)用Activity生命周期回調(diào)),這樣使得singleTask Activity實(shí)例位于棧頂。與此同時(shí),Intent會(huì)通過(guò)onNewIntent傳遞到這個(gè)SingleTask Activity實(shí)例。

singletask_sameapp_instance_exists.jpg

然而在Google關(guān)于singleTask的文檔有這樣一段描述

The system creates a new task and instantiates the activity at the root of the new task.

意思為 系統(tǒng)會(huì)創(chuàng)建一個(gè)新的Task,并創(chuàng)建Activity實(shí)例放入這個(gè)新的Task的底部。
然而實(shí)際并非如此,在我的例子中,singleTask Activity并創(chuàng)建并放入了調(diào)用者所在的Task,而不是放入新的Task,使用adb shell dumpsys activity便可以進(jìn)行驗(yàn)證。

Task id #239  TaskRecord{428efe30 #239 A=com.thecheesefactory.lab.launchmode U=0 sz=2}  Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }  
Hist #1: ActivityRecord{429a88d0 u0 com.thecheesefactory.lab.launchmode/.SingleTaskActivity t239}  Intent { cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity }  ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123}  
Hist #0: ActivityRecord{425fec98 u0 com.thecheesefactory.lab.launchmode/.StandardActivity t239}  Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.thecheesefactory.lab.launchmode/.StandardActivity }  ProcessRecord{42243130 18965:com.thecheesefactory.lab.launchmode/u0a123} 

然而想要實(shí)現(xiàn)文檔的描述也并非不可能,我們需要在設(shè)置launchMode為singleTask的同時(shí),再加上taskAffinity屬性即可。

<activity  android:name=".SingleTaskActivity"  android:label="singleTask launchMode"  
android:launchMode="singleTask"  android:taskAffinity=""> </activity> 

完成上面的修改,我們看一下效果,Task的變化如下圖


singleTaskTaskAffinity.jpg

同時(shí),系統(tǒng)中的任務(wù)管理器效果也會(huì)相應(yīng)變化


singletask_task_affinity_task_manger.jpg

跨應(yīng)用之間
在跨應(yīng)用Intent傳遞時(shí),如果系統(tǒng)中不存在singleTask Activity的實(shí)例,那么講創(chuàng)建一個(gè)新的Task,然后創(chuàng)建SingleTask Activity的實(shí)例,將其放入新的Task中。Task變化如下。

singletask_across_app_no_instance.jpg

系統(tǒng)的任務(wù)管理器也會(huì)如下變化

singletask_acrossapp_no_instance_taskmanager.jpg

如果singleTask Activity所在的應(yīng)用進(jìn)程存在,但是singleTask Activity實(shí)例不存在,那么從別的應(yīng)用啟動(dòng)這個(gè)Activity,新的Activity實(shí)例會(huì)被創(chuàng)建,并放入到所屬進(jìn)程所在的Task中,并位于棧頂位置。

singletask_acrossapp_application_exists_activity_nonexists.jpg

更復(fù)雜的一種情況,如果singleTask Activity實(shí)例存在,從其他程序被啟動(dòng),那么這個(gè)Activity所在的Task會(huì)被移到頂部,并且在這個(gè)Task中,位于singleTask Activity實(shí)例之上的所有Activity將會(huì)被正常銷毀掉。如果我們按返回鍵,那么我們首先會(huì)回退到這個(gè)Task中的其他Activity,直到當(dāng)前Task的Activity回退棧為空時(shí),才會(huì)返回到調(diào)用者的Task。

singletask_acrossapp_instance_exists_and_back.jpg

在上圖中,當(dāng)Task2中的相冊(cè)啟動(dòng)分享調(diào)用Task1中的singleTask Activity,而該Activity實(shí)例存在,并位于Task1中回退棧中的第三個(gè)位置(從上到下順序),那么位于該Activity上面的兩個(gè)Activity實(shí)例將會(huì)被銷毀掉,使得該Activity實(shí)例位于棧頂。此時(shí)Task1中的回退棧只剩兩個(gè)Activity,如果點(diǎn)擊返回,那么會(huì)退到的不是相冊(cè)應(yīng)用,而是singleTask Activity棧位置下面的Activity,再次點(diǎn)擊返回方可返回相冊(cè)應(yīng)用。

使用場(chǎng)景
該模式的使用場(chǎng)景多類似于郵件客戶端的收件箱或者社交應(yīng)用的時(shí)間線Activity。上述兩種場(chǎng)景需要對(duì)應(yīng)的Activity只保持一個(gè)實(shí)例即可,但是也要謹(jǐn)慎使用這種模式,因?yàn)樗梢栽谟脩粑锤兄那闆r下銷毀掉其他Activity。

singleInstance

這個(gè)模式和singleTask差不多,因?yàn)樗麄冊(cè)谙到y(tǒng)中都只有一份實(shí)例。唯一不同的就是存放singleInstance Activity實(shí)例的Task只能存放一個(gè)該模式的Activity實(shí)例。如果從singleInstance Activity實(shí)例啟動(dòng)另一個(gè)Activity,那么這個(gè)Activity實(shí)例會(huì)放入其他的Task中。同理,如果singleInstance Activity被別的Activity啟動(dòng),它也會(huì)放入不同于調(diào)用者的Task中。

singleInstance_new_instance.jpg

雖然是兩個(gè)task,但是在系統(tǒng)的任務(wù)管理器中,卻始終顯示一個(gè),即位于頂部的Task中。
singleInstances_taskmanager.jpg

另外當(dāng)我們從任務(wù)管理器進(jìn)入這個(gè)應(yīng)用,是無(wú)法通過(guò)返回鍵會(huì)退到Task1的。
好在有辦法解決這個(gè)問(wèn)題,就是之前提到的taskAffinity="",為launchMode為singleInstance的Activity加入這個(gè)屬性即可。

<activity  android:name=".SingleInstanceActivity"  android:label="singleInstance launchMode"  
android:launchMode="singleInstance"  android:taskAffinity=""> </activity> 

再次運(yùn)行修改的代碼,查看任務(wù)管理器,這樣的結(jié)果就合理了。

singleinstance_task_affinity.jpg

使用情況
這種模式的使用情況比較罕見(jiàn),在Launcher中可能使用。或者你確定你需要使Activity只有一個(gè)實(shí)例。建議謹(jǐn)慎使用。

Intent Flags
除了在manifest文件中設(shè)置launchMode之外,還可以在Intent中設(shè)置flag達(dá)到同樣的效果。如下述代碼就可以讓StandardActivity已singleTop模式啟動(dòng)。

關(guān)于Intent Flags這里暫不做重點(diǎn)介紹,具體可以參考官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容