Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Activity啟動模式詳解

Activity啟動模式詳解

編輯:關於Android編程

Activity的啟動模式實際上是定義了Activity實例與當前Task的關聯方式。

所以想要清楚的了解Activity的啟動模式,首先得清楚Task是怎麼回事。

Tasks and Back Stack

Tasks and Back Stack的關系,我的理解是每個 task 會有一個 back stack 。

一個 activity 甚至可能會啟動另一個應用中的 activity。 比如,如果你的應用需要發送 email,你可以定義一個 intent 來執行“send” action,其中包含一些數據,如 email 地址、正文等。 然後會打開一個其它應用中已聲明能夠處理這類 intent 的 activity。 這裡是一個發送 email 的 intent,所以會打開一個 email 應用的“新建郵件”activity(如果有多個 activity 都支持同一個 intent,則系統或讓用戶選擇一個打開)。 email 發送完畢後,你的 activity 將會恢復,看起來 email activity 就像是你的應用中的一部分一樣。 雖然這兩個 activity 可能來自不同的應用,通過把它們放入同一個task,Android 保證了無縫的用戶體驗

task 是多個 activity 的集合,用戶進行操作時將與這些 activity 進行交互。 這些 activity 按照啟動順序排隊存入一個棧(即“back stack”)。

大部分 task 都啟動自用戶觸摸 application launcher 中的圖標(或 Home 屏幕上的快捷圖標)時,應用程序的 task 就進入前台。 如果該應用不存在 task(最近沒有使用過此應用),則會新建一個 task,該應用的“main”activity 作為棧的根 activity 被打開。

當用戶返回到 home屏幕執行另一個 task 時(打開另一個app),一個 task 被移動到後台執行,此時它的返回棧(back stack)也被保存在後台, 同時 android 為新 task (app)創建一個新的返回棧(back stack),當它被再次運行從而返回前台時,它的返回棧(back stack)被移到前台,並恢復其之前執行的activity,如下圖所示。 如果後台有太多運行 task ,系統將會殺死一些 task 釋放內存。

如果當前 activity 啟動了另一個 activity,則新的 activity 被壓入棧頂並獲得焦點。 前一個 activity 仍保存在棧中,但是被停止。activity 停止時,系統會保存用戶界面的當前狀態。 當用戶按下返回鍵,則當前 activity 將從棧頂彈出(被銷毀),前一個 activity 將被恢復(之前的用戶界面狀態被恢復)。 activity 在棧中的順序永遠不會改變,只會壓入和彈出——被當前 activity 啟動時壓入棧頂,用戶用返回鍵離開時彈出。 這樣,back stack 以“後進先出”的方式運行。 圖1 以時間線的方式展示了多個 activity 切換時對應當前時間點的 back stack 狀態。

diagram_backstack.png

圖 1. 如圖所示, task 中的每個新 activity 都會相應在 back stack 中增加一項。當用戶按下返回鍵時,當前 activity 被銷毀,前一個 activity 被恢復。

如果用戶不停地按下返回鍵,則棧中每個 activity 都會依次彈出,並顯示前一個 activity,直至用戶回到 Home 屏幕(或者任一啟動該 task 的 activity)。 當所有 activity 都從棧中彈出後, task 就此消失。

一個 task 是一個整體單位, 當用戶啟動一個新的 task 或者回到 Home 屏幕時,這個 task 就轉為“後台”。 當 task 處於後台時,其中所有的 activity 都處於停止狀態,但是這個 task 的 back stack 仍然完整保留——如圖2所示,在其它 task 獲得焦點期間,這個 task 只是失去焦點而已。 task 可以返回“前台”,所以用戶能夠自離開的地方繼續工作。比如,假定當前 task (Task A)的棧中共有3個 activity —— 下面有兩個 activity。 這時,用戶按下Home鍵,然後從 application launcher 中啟動一個新的應用。當 Home 屏幕出現時,Task A 進入後台。 新的應用啟動時,系統會為它開啟一個 task (Task B),其中放入新應用中的 activity。 用完這個應用後,用戶再次返回 Home 屏幕,並選中那個啟動 Task A 的應用。 現在,Task A 進入前台——棧中的三個 activity 仍然完好,位於最頂部的 activity 恢復運行。 這時,用戶仍然可以切回 Task B,通過回到 Home 屏幕並選擇相應圖標即可(或者觸摸並按住Home鍵調出最近 task 列表並選中)。 以下是 Android 多 task 的實例。

diagram_multitasking.png

圖 2. 兩個 task :Task B 在前台與用戶交互,而 Task A 在後台等待喚醒。

注意: 可以在後台同時保留多個 task 。但是,假如用戶同時運行著多個後台 task ,則系統可能會銷毀後台 activity (通常會銷毀後台task中的activity,但是會留下個主activity)以便騰出內存,這會導致 activity 狀態的丟失。

因為 back stack 中的 activity 順序永遠不會改變,如果應用允許某個 activity 可以讓用戶啟動多次,則新的實例會壓入棧頂(而不是打開之前位於棧頂的 activity)。 於是,一個 activity 可能會初始化多次(甚至會位於不同的 task 中),如圖3所示。 如果用戶用回退鍵回退時,activity 的每個實例都會按照原來打開的順序顯示出來(用戶界面也都按原來狀態顯示)。 當然,如果你不想讓 activity 能被多次實例化,你可以改變它。

圖 3. activity A 被實例化多次。

下面把 activity 和 task 的默認特性匯總一下:

當 Activity A 啟動 Activity B 時,Activity A 被停止,但系統仍會保存狀態(諸如滾動條位置和 form 中填入的文字)。 如果用戶在 Activity B 中按下回退鍵時,Activity A 恢復運行,狀態也將恢復。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrWx08O7p7C0z8JIb21lvPzA67+qIHRhc2sgyrGjrLWxx7AgYWN0aXZpdHkgzaPWuaOsIHRhc2sg16rI67rzzKihoyDPtc2zu+Gxo7TmIHRhc2sg1tDDv7j2IGFjdGl2aXR5ILXE17TMrKGjyOe5+9PDu6fS1Lrzzai5/dGh1tDG9LavuMMgdGFzayC1xM28serAtLvWuLQgdGFzayCjrCB0YXNrIL7Nu+G72LW9x7DMqKOs1bu2pbXEIGFjdGl2aXR5ILvhu9a4tNTL0NChozwvcD4NCjxwPsjnufvTw7unsLTPwrvYzcu8/KOstbHHsCBhY3Rpdml0eSC009W71tC1r7P2o6yyorG7z/q72aGjINW71tDPwtK7uPYgYWN0aXZpdHkgu9a4tNTL0NChoyC1sSBhY3Rpdml0eSCxu8/6u9nKsaOsz7XNs7K7u+Gxo8H0IGFjdGl2aXR5ILXE17TMrKGjPC9wPg0KPHA+YWN0aXZpdHkgv8nS1LG7yrXA/buvtuC0zqOsyfXWwb/J0tTOu9PasrvNrLXEIHRhc2sg1tChozwvcD4NCjxoMyBpZD0="管理多個task-這就要用到activity的啟動模式">管理多個Task (這就要用到Activity的啟動模式)

如上所述——把所有已啟動的 activity 相繼放入同一個 task 中以及一個“後入先出”棧, Android 管理 task 和 back stack 的這種方式適用於大多數應用, 你也不用去管你的 activity 如何與 task 關聯及如何彈出 back stack 的。 不過,有時你也許決定要改變這種普通的運行方式。 也許你想讓某個 activity 啟動一個新的 task (而不是被放入當前 task ); 或者,你想讓 activity 啟動時只是調出已有的某個實例(而不是在 back stack 頂創建一個新的實例); 或者,你想在用戶離開 task 時只保留根 activity,而 back stack 中的其它 activity 都要清空,

你能做的事情還有很多,利用 ” manifest 元素的屬性和傳入 startActivity() 的 intent 中的標志即可。

這裡,你可以使用的 屬性主要有:

taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch

可用的 intent 標志主要有:

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP

Activity啟動模式

啟動模式定義了一個新的 activity 實例與當前 task 的關聯方式。定義啟動模式的方法有兩種:

使用 manifest 文件

當你在 manifest 文件中聲明一個 activity 時,可以指定它啟動時與 task 的關聯方式。

使用 Intent 標志

調用 startActivity() 時,可以在 Intent 中包含一個標志,用於指明新 activity 如何(是否)與當前 task 相關聯。

這樣,如果 Activity A 啟動了Activity B,則 Activity B 可以在 manifest 中定義它如何與當前 task 關聯(如果存在的話), 並且,Activity A 也可以要求 Activity B 與當前 task 的關聯關系。 如果兩者都定義了,則 Activity A 的請求 (intent 中定義)優先於 Activity B 的定義(在 manifest 中)

注意: manifest 文件中的某些啟動模式在 intent 標志中並不可用,反之亦然,intent 中的某些模式也無法在 manifest 中定義。

使用 manifest 文件 4種

在 manifest 文件中聲明 activity 時,你可以利用 元素的 launchMode 屬性來設定 activity 與 task 的關系。

launchMode 屬性指明了 activity 啟動 task 的方式。 launchMode 屬性可設為四種啟動模式:

“standard” (默認模式)

默認值。系統在啟動 activity 的 task 中創建一個新的 activity 實例,並把 intent 傳送路徑指向它(就是打開這個新實例)。該 activity 可以被實例化多次,各個實例可以屬於不同的 task,一個 task 中也可以存在多個實例。

“singleTop”

如果 activity 已經存在一個實例並位於當前 task 的棧頂,則系統會調用已有實例的onNewIntent()方法把 intent 傳遞給已有實例(既是打開這個實例),而不是創建一個新的 activity 實例。activity 可以被實例化多次,各個實例可以屬於不同的 task,一個 task 中可以存在多個實例(但僅當 back stack 頂的 activity 實例不是該 activity 的)。

比如,假定 task 的 back stack 中包含了根 activity A 和 activities B、C、D(順序是 A-B-C-D;D 在棧頂)。

這時過來一個啟動 D 的 intent。如果 D 的啟動模式是默認的”standard”,則會啟動一個新的實例,棧內容變為 A-B-C-D-D。

但是,如果 D 的啟動模式是”singleTop”,則已有的 D 實例會通過onNewIntent():接收這個 intent,因為該實例位於棧頂——棧中內容仍然維持 A-B-C-D 不變。當然,如果 intent 是要啟動 B 的,則 B 的一個新實例還是會加入棧中,即使 B 的啟動模式是”singleTop”也是如此。

注意: 一個 activity 的新實例創建完畢後,用戶可以按回退鍵返回前一個 activity。 但是當 activity 已有實例正在處理剛到達的 intent 時,用戶無法用回退鍵回到 onNewIntent() 中 intent 到來之前的 activity 狀態。(我的理解是: 如果這個模式下,有activity實例正在處理剛到達的 intent 的話,那麼肯定是自己打開了自己,首先能發送intent的話,會位於棧頂,那麼在singleTop模式下不創建新實例只能位於棧頂,所以回退鍵只能退回當前回退棧的後一個activity了)

“singleTask”

系統將創建一個新的 task,並把 activity 實例作為根放入其中。但是,如果 activity 已經在其它 task 中存在實例,則系統會通過調用其實例的onNewIntent() 方法把 intent 傳給已有實例(打開那個activity,並且為了讓這個activity變為可見,系統會把在這個activity之上的所有activity移除棧),而不是再創建一個新實例。 此 activity 同一時刻只能存在一個實例。

注意: 雖然 activity 啟動了一個新的 task,但用戶仍然可以用回退鍵返回前一個 activity。(再注意,這是個新的task,用戶才能返回,如果要啟動的activity的實例存在的話,不一定,後面會說到)

“singleInstance”

除了系統不會把其它 activity 放入當前實例所在的 task 之外,其它均與”singleTask”相同。activity 總是它所在 task 的唯一成員;它所啟動的任何 activity 都會放入其它 task 中。

這個例子好

舉個例子,Android 的浏覽器應用就把 web 浏覽器 activity 聲明為總是在它自己獨立的 task 中打開——把 activity 設為singleTask模式。 這意味著,如果你的應用提交 intent 來打開 Android 的浏覽器,則其 activity 不會被放入你的應用所在的 task 中。 取而代之的是,或是為浏覽器啟動一個新的 task;或是浏覽器已經在後台運行,只要把 task 調入前台來處理新 intent 即可。

無論 activity 是在一個新的 task 中啟動,還是位於其它已有的 task 中,用戶總是可以用回退鍵返回到前一個 activity 中。 但是,如果你啟動了一個啟動模式設為singleTask的 activity,且有一個後台 task 中已存在實例的話,則這個後台 task 就會整個轉到前台。 這時,當前的 back stack 就包含了這個轉入前台的 task 中所有的 activity,位置是在棧頂。 圖 4 就展示了這種場景。

diagram_backstack_singletask_multiactivity.png

圖 4. 啟動模式為“singleTask”的 activity 如何加入 back stack 的示意。 如果 activity 已經是在後台 task 中並帶有自己的 back stack,則整個後台 back stack 都會轉入前台,並放入當前 task 的棧頂。

使用 Intent 標志 3種

在要啟動 activity 時,你可以在傳給 startActivity() 的 intent 中包含相應標志,以便修改 activity 與 task 的默認關系。這個標志可以修改的默認模式包括:

FLAG_ACTIVITY_NEW_TASK

在新的 task 中啟動 activity。如果要啟動的 activity 已經運行於某 task 中,則那個 task 將調入前台,最後保存的狀態也將恢復,activity 將在onNewIntent()中接收到這個新 intent。
這個過程與前一節所述的”singleTask”launchMode模式值相同。

FLAG_ACTIVITY_SINGLE_TOP

如果要啟動的 activity 就是當前 activity(位於back stack 頂),則已存在的實例將接收到一個onNewIntent()調用,而不是創建一個 activity 的新實例。
這個過程與前一節所述的 “singleTop”launchMode模式值相同。

FLAG_ACTIVITY_CLEAR_TOP

如果要啟動的 activity 已經在當前 task 中運行,則不再啟動一個新的實例,且所有在其上面的 activity 將被銷毀,然後通過onNewIntent()傳入 intent 並恢復 activity(不在棧頂)的運行。
此種模式在launchMode中沒有對應的屬性值。

FLAG_ACTIVITY_CLEAR_TOP 常與 FLAG_ACTIVITY_NEW_TASK 一起使用。這表示先定位其它 task 中已存在的 activity,再把它放入可以響應 intent 的位置。

處理affinity

affinity 表示 activity 預期所處的 task 。 缺省情況下,同一個應用中的所有 activity 都擁有同一個 affinity 值。 因此,同一個應用中的所有 activity 默認都期望位於同一個 task 中。 不過,你可以修改 activity 默認的 affinity 值。 不同應用中的 activity 可以共享同一個 affinity 值,同一個應用中的 activity 也可以賦予不同的 task affinity 值。

你可以用 元素的taskAffinity 屬性修改 activity 的 affinity,taskAffinity 屬性是一個字符串值,必須與 元素定義的包名稱保證唯一性,因為系統把這個包名稱用於標識應用的默認 task affinity 值。

affinity 將在以下兩種情況下發揮作用:

當啟動 activity 的 intent 包含了FLAG_ACTIVITY_NEW_TASK標志。

默認情況下,一個新的 activity 將被放入調用 startActivity() 的 activity 所在 task 中,且壓入調用者所處的 back stack 頂。 不過,如果傳給 startActivity() 的 intent 包含了 FLAG_ACTIVITY_NEW_TASK 標志,則系統會查找另一個 task 並將新 activity 放入其中。這時經常會新開一個任務,但並非一定如此。 如果一個已有 task 的 affinity 值與新 activity 的相同,則 activity 會放入該 task。 如果沒有,則會新建一個新 task。

如果本標志使得 activity 啟動了一個新的 task,用戶按下 Home 鍵離開時,必須采取一些措施讓用戶能回到此 task。 某些應用(比如通知管理器)總是讓 activity 放入其它 task 中啟動,而不是放入自己的 task 中。 因此,它們總是把 FLAG_ACTIVITY_NEW_TASK 標志置入傳給 startActivity() 的 intent 中。如果你的 activity 可以被外部應用帶此標志來啟動,請注意用戶會用其它方式返回啟動 task,比如通過應用圖標(task 的根 activity 帶有一個 CATEGORY_LAUNCHER intent 過濾器)。

當一個 activity 的allowTaskReparenting屬性設為 “true”。

這種情況下,當某個 task 進入前台時,activity 的 affinity 值又與其相同,則它可以從啟動時的 task 移入這個 task 中。

比如,假定某旅游應用中有一個 activity 根據所選的城市來報告天氣情況。 它的 affinity 與同一應用中的其它 activity 一樣(整個應用默認的 affinity),且它允許重新指定此屬性的歸屬。 當你的另一 activity 啟動此天氣報告 activity 時,它會在同一個 task 中啟動。 然而,當旅游應用的 task 進入前台時,則天氣報告 activity 將會重新放入其 task 中並顯示出來。

提示: 如果一個 .apk 文件中包含了多個“application”,你可能需要用 taskAffinity 屬性來指定每個“application”中 activity 的 affinity 值。

清理back stack

如果用戶長時間離開某個 task,系統將會僅保留一個根 activity,而把其它 activity 都清除掉。 當用戶返回 task 時,只有根 activity 會被恢復。 系統之所以這麼處理,是因為經過了很長時間後,用戶是要放棄之前進行的工作,返回 task 是為了開始新的工作。

你可以利用 activity 的某些屬性來改變這種方式:

alwaysRetainTaskState

如果 task 中根 activity 的此屬性設為 “true” ,則默認的清理方式不會進行。即使過了很長時間,task 中所有的 activity 也都會保留在棧中。

clearTaskOnLaunch

如果 task 中根 activity 的此屬性設為 “true”,則只要用戶離開並再次返回該 task,棧就會被清理至根 activity。也就是說,正好與alwaysRetainTaskState相反。用戶每次返回 task 時看到的都是初始狀態,即使只是離開一會兒。

finishOnTaskLaunch

此屬性類似於clearTaskOnLaunch,只是它只對一個 activity 有效,不是整個 task。這能讓任何一個 activity 消失,包括 根 activity。如果 activity 的此屬性設為 “true”,則只會保留 task 中當前 session 所涉及的內容。如果用戶離開後再返回 task,它就不存在了。
啟動task

你可以把某個 activity 設為 task 的入口,通過發送一個 action 為 “android.intent.action.MAIN”、category 為 “android.intent.category.LAUNCHER” 的 intent即可。 比如:


    
        
        
    
    ...

這種 intent 過濾器將會讓此 activity 的 icon 和 label 作為應用啟動圖標來顯示,用戶可以啟動此 activity,並且在之後任何時候返回其啟動時的 task。

後一個能力非常重要:用戶必須能離開一個 task ,之後能再回來使用這個啟動 task 的 activity。 因此,標明 activity 每次都會啟動 task 的這兩種 啟動模式 “singleTask” 和 “”singleInstance” 應該僅對帶有ACTION_MAIN 和 CATEGORY_LAUNCHER 過濾器的 activity 才能使用。 想象一下,如果未給出過濾器時會發生什麼:某個 intent 啟動了一個 “singleTask” activity, 並新建了一個 task,用戶在此 task 中工作了一段時間。然後他按了 Home 鍵。 tassk 就轉入後台,變為不可見狀態。這時用戶就無法再回到 task 了,因為它在 application launcher 中沒有相應的條目了。

對於那些不想讓用戶返回的 activity,把 元素的 finishOnTaskLaunch 設為 “true” 即可(參閱 #清理back stack。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved