Activity之理解Tasks和Back Stack

Task是一組Activity的集合,用戶與之交互用于完成一個特定的任務(wù)。所有的Activity被安排在一個stack中稱之為back stack, Activity按照打開的順序存放于其中。例如,一個郵件APP可以有一個用于顯示接收的新郵件列表的Activity。當(dāng)用戶選擇一封郵件,那一個新的Activity會打開顯示其內(nèi)容。這個新的Activity就會被添加到back stack。 如果用戶按下Back按鈕,那么這個新打開的Activity會被finish并從stack中彈出消失。

當(dāng)App運行在Android7(API Level 24)或更高版本的多窗口的環(huán)境中,系統(tǒng)會為每一個窗口單獨管理task;每一個窗口可以擁有多個task。 設(shè)備的Home屏幕是用于啟動絕大多數(shù)task的地方。當(dāng)用戶觸點Launcher桌面App圖標(biāo)時,App的task會進(jìn)入前臺運行。如果App對應(yīng)的task并不存在(最近用戶并未打開過App),就會創(chuàng)建一個新的task,把打開應(yīng)用的入口Activity放在stack中作為根Activity。 如果當(dāng)前Activity啟動另外一個Activity,那么新打開的Activity會被壓到stack的頂端并得到焦點。而前一個Activity會保留在stack中,并處于stopped狀態(tài)。一個stop的Activity,系統(tǒng)會保留它與用戶交互的狀態(tài)。當(dāng)用戶觸點Back按鈕。當(dāng)前的Activity會從棧頂彈出(Activity被銷毀)而前一個Activity會重新進(jìn)入可見狀態(tài)(UI狀態(tài)被恢復(fù))。在stack中的Activity并不會被重新安排,僅只做彈出和壓入的動作。back stack保持著“后進(jìn),先出”的數(shù)據(jù)結(jié)構(gòu)。如下圖所示

圖1


圖1表示新的Activity是如何添加到back stack中的,當(dāng)用戶觸點Back按鈕時,當(dāng)前Activity被銷毀并使前一個Activity可見。

如果用戶繼續(xù)按Back按鈕,那么stack中的Activity會順次彈出并暴露出前一個Activity,直到用戶返到Home屏幕(或者直到啟動這個task的Activity),當(dāng)所有的activity全部都從stack中移除后,stack也不復(fù)存在了。

在用戶啟動一個新task或者按home按鈕返回到home屏幕時,task是一個可以整體移至后臺的單位。當(dāng)移入后臺時,task中的所有activity都會進(jìn)入stop狀態(tài),失去了焦點,顯示如同圖2

圖2 (有兩個task, taskB接受到用戶的操作移到前臺,同時taskA 移于后臺等待重新恢復(fù)到前臺)

task可以恢復(fù)到前臺,用戶得以可以繼續(xù)操作,相反,例如,當(dāng)前task A在stack中保存有三個activity,有兩個activity在當(dāng)前activity的下面。用戶按下Home按鈕,然后從Launcher桌面上啟動一個新的app, 當(dāng)home屏幕出現(xiàn)時,task A進(jìn)入后臺,當(dāng)新的app啟動時,系統(tǒng)為它啟動一個新的task B用來存放stack, 在用戶和app交互后,用戶再次返回home屏幕,并再次選擇原來啟動task A的app。 現(xiàn)在,task A再次回到前臺,在它的stack中依然保持著三個activity,stack頂部的activity變?yōu)榭梢姟M恚脩粢部梢酝ㄟ^home鍵返回home屏幕,并再選擇進(jìn)和task B的app圖標(biāo)(或者從最近使用的APP)。以上是一個Android多task的例子。

注意:多task可以被保存后臺,然而,如果用戶在后臺運行太多的task時,系統(tǒng)可以銷毀后臺的activity用于恢復(fù)內(nèi)存,這會引發(fā)activity的狀態(tài)丟失。

在stack中的activity重來不會被主動重新排序,但如果用戶主動請求一個activity多次時,一個新activity實例會被創(chuàng)建并壓入棧中(而不是把前面已創(chuàng)建的實例移于棧頂)。如圖3

圖3

如果用戶用back按鈕返回時,每一個activity實例都會得以展示(帶有自己的ui狀態(tài))。然而,你可以修改這個行為,如果你不希望activity被實例化多次。如果做呢?見后面討論的管理task。

總結(jié)一下activity和task的默認(rèn)行為:

當(dāng)activity A啟動activity B, Activity A被stop, 但系統(tǒng)會保存它的ui狀態(tài)(如滾動條的位置,表單中輸入的文字)。如果用戶在Activity B按下Back按鈕, Activity A會重新顯示并恢復(fù)它的狀態(tài)。

當(dāng)用戶離開task通過按下home鍵,當(dāng)前的activity會被stop,它對應(yīng)的task會進(jìn)入后臺,系統(tǒng)會保留task中的每一個activity的狀態(tài),如果用戶稍后選擇app icon,可以重新切回task,task的棧頂activity變?yōu)榭梢?/p>

如果用戶按下back按鈕,當(dāng)前activity會被彈出棧頂劃被銷毀,棧中的前一個activity變?yōu)榭梢姟.?dāng)activity被銷毀時,系統(tǒng)不再保留它的狀態(tài)。

activity可以被創(chuàng)建多次,甚至可以位于不同的task。


管理Task


如上所述,Android管理task和stack的方式就是把所有的activity一連串的放在同一個task中并按“后進(jìn),先出”的原則管理。你可以打破這一慣例,也許你希望一個activity啟動時時放在一個新的task中(而不是當(dāng)前task),或者,你希望啟動一個已經(jīng)存在的activity,而不是在棧頂再創(chuàng)建一個新的activity實例。或者你希望在用戶離開task時只保留棧底的根activity,并清除掉其它的activity

你可以做到或做到更多,通過在manifest的<activity>聲明中帶上屬性或者通過在startActivity()時使用Intent帶上標(biāo)記。

相關(guān)的屬性如下:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

相關(guān)的標(biāo)記如下:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ATIVITY_SINGLE_TOP

下面的章節(jié)你會看到如何用屬性和標(biāo)記定義activity和task的關(guān)聯(lián),以及activity在stack中的行為。


定義Launch model


Launch mode允許你定義activity實例與當(dāng)前task如何關(guān)聯(lián),你可以定義不同的啟動方式,通過下面介紹的兩種方式:

用manifest

用Intent flag

有時可能會出現(xiàn)這種情況,例如Activity A 啟動Activity B,Activity B可以在manifest中定義它如何與當(dāng)前task關(guān)聯(lián),而Activity A也可以通過Intent來請求Activity B如何與當(dāng)前task關(guān)聯(lián)。這時,后者優(yōu)先。

注意:有一些Launch mode在manifest中有效,而在flag中無效,反之亦然。

使用manifest配置

通過launchMode屬性,它指定activity應(yīng)該在哪一個task中啟動,有四種不同的launch mode屬性值:

standard(默認(rèn)值) 系統(tǒng)在當(dāng)前task中創(chuàng)建一個activity實例。activity可以被實例化多次,每一個實例可以屬于不同的task,一個task中也可以有多個實例。

singleTop 如果一個activity實例已經(jīng)位于當(dāng)前task頂端,系統(tǒng)會發(fā)送給它intent并調(diào)用onNewIntent(), 而不是創(chuàng)建一個新的實例。Activity可以實例化多次,每一個實例可以屬于不同的task,每一上task可以擁有多個實例(僅出現(xiàn)在stack頂端不存在activity的實例時)

例如,task的棧由根activity A,B,C, D在頂部,一個itent到達(dá)Activity D, 如果D是standard屬性,一個新的實例會創(chuàng)建,stack成為A-B-C-D-D,然而,如果D是singleTop屬性,那么D會收到intent并執(zhí)行onNewIntent(),這時棧保持A-B-C-D。然而,如果itnent是發(fā)給B的,B如果是singleTop,那么B的實例會加入棧。

注意:當(dāng)一個新的activity實例被創(chuàng)建,用戶可以按back鍵返回前一個activity. 但是當(dāng)存在的activity處理了新的intent執(zhí)和onNewIntent(),那用戶不能通過back返回到以前的狀態(tài).

singleTask 系統(tǒng)創(chuàng)建一個新的task并實例activity放在task的棧底。然而,如果activity實例已存在一個task中,系統(tǒng)會發(fā)送intent并調(diào)用onNewIntent(),而不是創(chuàng)建一個新的實例。在任一時刻,只有一個activity的實例。

注意:雖然activity在一個新的task中啟動,back操作仍可以返回前一個activity.

singleInstance 如同singleTask,除了系統(tǒng)不會將其它activity放入保存當(dāng)前的task中,這個activity永遠(yuǎn)單獨存在于自己的task中;這個activity啟動任意其他activity都會在其它的task打開.

舉一個例子,Android瀏覽器app聲明的web activity應(yīng)該總是在它自己的task中打開--通過指定singleTask屬性。這意味著,如果你的APP打開Android Browser,web activity不會出現(xiàn)在你的app頁面對應(yīng)的task,不論web activity是否已經(jīng)存在。

不管activity是否啟動新task還是和啟動它的Activity在同一個task,back鍵總是將用戶帶到前一個activity.然而,如果你啟動activity用singleTask,并且如果activity已經(jīng)存在于一個后臺task,那么整個task會移置前臺。從這一點上講,stack現(xiàn)包括了從task轉(zhuǎn)移所有的activity。

圖4說明這一場景。

圖4說明singleTask的activity如何被加入stack,如果activity已經(jīng)是后臺task的自有stack中,那么整個stack都被移到前臺,在當(dāng)前task的頭部。

使用Intent flag

有如下標(biāo)記可用:

FLAG_ACTIVITY_NEW_TASK 啟動一個activity在一個新的task. 如果task已經(jīng)存在并運行著一個你想啟動的Activity,那么這個task被置于前臺,并恢復(fù)activity的狀態(tài),調(diào)用onNewIntent() 這個行為與上面討論的singTask Launch model相同。

FLAG_ACTIIVTY_SIGNLE_TOP 如果activity已經(jīng)啟動并且位于stack頂部,那么會被調(diào)用onNewIntent(),而不是創(chuàng)建一個新的實例。 這個行為同singleTop Launch model

FLAG_ACTIVITY_CLEAR_TOP 如果activity已經(jīng)在當(dāng)前task中運行,那么就不會新建實例,在其上的所有activity都會被銷毀,這個activity會移到stack頂部并調(diào)用onNewIntent 這個值在launchMode中無對應(yīng)配置

FLAG_ACTIVTY_CLEAR_TOP通常與FLAG_ACTIVITY_NEW_TASK連用,用于定位一個在其他task中已存在的activity,并移于task頭部。

注意:如果launchMode設(shè)置為standard, 它也會被從stack中移除,而是重新創(chuàng)建一個新的實例,那是因為在standard模式下一個新的Intent就會導(dǎo)致創(chuàng)建一個新的activity實例。


處理affinity


affinity指task應(yīng)該歸屬于哪個task. 默認(rèn)情況下,同一個app的所有activity都有相同的affinity。所以默認(rèn)情況下,同一個app的所有activity都屬于同一個task,然而,你可以修改這一行為。不同app的activity可以共享affinity,所以同一app的activity也可以指定不同的affinity.

你可以修改<activity>的taskAffinity屬性。 的taskAffinity屬性是一個字符串值,它必須區(qū)分與<manifest>中定義的包名,因為系統(tǒng)使用這個名字做為默認(rèn)affinity的值

affinity在兩個狀態(tài)下使用

當(dāng)啟動activity的Intent包括FLAG_ACTIVITY_NEW_TASK標(biāo)記。默認(rèn)情況下,一個activity調(diào)用startActivity()會導(dǎo)致,一個新的activity實例創(chuàng)建并放在同一個task的stack中,然而如果intent帶有FLAG_ACTIVITY_NEW_TAS標(biāo)記, 系統(tǒng)會尋找一個新的task存放新的Activity,通常會新建一個task,然而也可以不必創(chuàng)建,只要已經(jīng)存在一個task且task的affinity與新activity的affinity相同,那么新的activity就會被放在task中,而不用新建。 如果activity放在新建的task中,那么用戶點擊home按鈕離開activity時,就必須有留一條路讓用戶可以重新導(dǎo)航回來。例如,通知管理器總是啟動activity在一個新的task中,因為它發(fā)出的intent總是帶有FLAG_ACTIVITY_NEW_TASK, 如果你的activity可以從外部調(diào)用可以使用這種方式,如通過launch icon重新返回。

當(dāng)activity的allowTaskReparenting屬性設(shè)為true 在這種情況下,activity可以根據(jù)affinity從一個后臺的task移到一個前臺的task

舉例,假設(shè)一個旅行內(nèi)容的APP,它有一個根據(jù)選擇的城市顯示天氣的activity, 這個Activity與應(yīng)用里其他activity具有相同的affinity,并且它的allowTaskReparenting屬性設(shè)為true。當(dāng)你的APP調(diào)用天氣的activity時,它會進(jìn)入你activity對應(yīng)的task, 然而,當(dāng)旅行app置入前臺時,它又會重新回到原來的task中。


清除stack


如果用戶離開task較長時間,系統(tǒng)會清除掉task中所有的actvity,除了根activity,當(dāng)用戶再次返回task,只有根activity可以恢復(fù)狀態(tài),因為系統(tǒng)認(rèn)為超出一定的時間,用戶很可能已經(jīng)放棄他們以前瀏覽的頁面,返回時會新建頁面。

有一些activity屬性可以讓你修改這一行為。

alwaysRetainTaskState 如果一個task的根activity的屬性設(shè)為true, 那么默認(rèn)的行為就不會發(fā)生。task會保留所有的activity在Stack中,即使用戶離開很長時間。

clearTaskOnLaunch 如果一個task的根activity的屬性設(shè)為true, 那么Stack中activity會被清除,但會保留根activity,換句話說,他是alwaysRetainTaskState相反的特性,用戶返咽task時總是會發(fā)生又重新回到初始狀態(tài),即使用戶只是離開了一小會兒。

finishOnTaskLaunch 這個屬性比較象clearTaskOnLaunch,但是它只操作在一個activity上,而不是作用于整個task, 它可能讓任何一個activity離開,包括根activity。當(dāng)他設(shè)置為true時,task中只保留部分activity,當(dāng)前activity不存在了。


啟動task


你可以設(shè)置一個activity做為task的入口點,以"android.intent.action.MAIN" action和 "android.intent.category.LAUNCHER" category, 這種類型的Intent filter會導(dǎo)致一個icon和標(biāo)題顯示在桌面上,讓用戶可以啟動它并再次返回task.

第二個能力很重要,用戶可以離開task后再次點icon返回task,因為這個原因,有兩個launchMode標(biāo)識的Activity總是會初始化一個task, "singleTask" 和 "singleInstance",這兩個launchMode只應(yīng)該在有 ACTION_MAIN和CATEGORY_LAUNCHER filter時使用。想象一下,如果filter缺失會發(fā)生什么:一個Intent啟動一個singleTask的activity, 初如化一個task,然后用戶在task工作了一段時間,用戶按下home鍵,task進(jìn)入后臺不可見。現(xiàn)在用戶再也無法返回task,因為他沒有出現(xiàn)在launch桌面上。 為了某些原因,你不希望用戶可以重新返回Activity, 你可以設(shè)置<activity>的屬性finishOnTaskLaunch為true.

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

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