——堅持就是勝利!關鍵是你能堅持嗎?不能的話,你註定是個失敗者。
引言
關於Android應用程式原理及術語,前面兩篇:
- Android開發之旅:應用程式基底礎及組件
- Android開發之旅:應用程式基底礎及組件(續)
介紹了Android應用程式的進程運行方式:每一個應用程式運行在它自己的Linux進程中。當應用程式中的任何代碼需要執行時,Android將啟動進程;當它不在需要且系統資源被其他應用程式請求時,Android將關閉進程。而且我們還知道了Android應用程式不像別的應用程式那樣(有Main函數進入點),它沒有單一的程式進入點,但是它必須要有四個組件中的一個或幾個:活動(Activities) 、服務(Services) 、廣播接收者(Broadcast receivers) 、內容提供者(Content providers)。且分別介紹它們的作用,及如何啟用和關閉它們、如何在資訊清單檔(AndroidManifest.xml)中聲明它們及Intent過濾器。
在簡單回顧之後,本篇還是繼續介紹Android應用程式原理及術語——活動與任務(Activities and Tasks)。
- 1、活動與任務概述
- 2、親和度和新任務(Affinities and new tasks)
- 3、啟動模式(Launch modes)
- 4、清除棧(Clearing the stack)
- 5、啟動任務(Starting tasks)
1、活動與任務概述
如前所述,一個活動(activity)能啟動另一個活動,包括定義在別的應用程式中的活動。再次舉例說明,假設你想讓使用者顯示某地的街道地圖。而且已經有了一個活動能做這個事情(假設這個活動叫做地圖查看器),因此你的活動要做的就是將請求資訊放進一個Intent對象,然後將它傳給startActivity()。地圖查看器就啟動並顯示出地圖。當使用者點擊返回按鈕之後,你的活動就會重新出現在螢幕上。
對使用者來說,這個地圖查看器就好像是你的應用程式的活動一樣,雖然它定義在其他的應用程式中且運行在那個應用程式的進程中。Android將這些活動保持在同一個任務(task)中以維持使用者的體驗。簡單地講,任務是使用者體驗上的一個“應用程式”,是排成堆棧的一組相關活動。棧底的活動(根活動)是起始活動——一般來講,它是使用者在應用程式啟動器(也稱應用程式列表,下同)中選擇的一個活動。棧頂的活動是正在啟動並執行活動——它關注使用者的行為(操作)。當一個活動啟動另一個,新的活動被壓入棧頂,變為正在啟動並執行活動。前面那個活動儲存在棧中。當使用者點擊返回按鈕時,當前活動從棧頂中彈出,且前面那個活動恢複成為正在啟動並執行活動。(關於棧的先進後出特性不要我在這裡講吧!)
棧中包含對象,因此如果一個活動(再次說明:活動是Activity的子類)啟動了多個執行個體——例如多個地圖查看器,則棧對每個執行個體有一個獨立的入口。(可以這樣理解:假設有四個活動以這樣的順序排在棧中——A-B-C-D,現在又有一個C的執行個體,則棧變成A-B-C-D-C,這兩個C的執行個體是獨立的。)棧中的活動從不會被重新排列,只會被壓入、彈出。這點很好理解,因為活動的調用順序是固定的。
任務是一棧的活動,而不是資訊清單檔中聲明的某個類或元素,因此無法獨立於它的活動為任務賦值。整個任務的值是在棧底活動(根活動)設定的。例如,下節將討論的“任務親和度”,親和度資訊就是從任務的根活動中擷取的。
一個任務的所有活動作為一個整體運行。整個任務(整個活動棧)可置於前台或發送到後台。例如,假設當前任務有四個活動在棧中——三個活動在當前活動下面。使用者按下HOME鍵,切換到程式啟動器,並選擇一個新的應用程式(實際上是一個新的任務)。當前任務進入後台,新任務的根活動將顯示。接著,過了一會,使用者回到主畫面並再次選擇之前的應用程式(之前的任務)。那個任務棧中的所有四個活動都變為前台運行。當使用者按下返回鍵時,不是離開當前任務回到之前任務的根活動。相反,棧頂的活動被移除且棧中的下一個活動將顯示。
上面所描述的是活動和任務的預設行為,但是有方法來改變所有這些行為。活動與任務之間的聯絡及任務中活動的行為,是由啟動活動的Intent對象的標誌(flags)和資訊清單檔中活動元素的屬性共同決定的。
在這方面,主要的Intent標誌有:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP
主要的屬性有:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
接下來的小節將討論這些標誌和屬性的作用,他們怎麼互動,及使用的注意事項。
2、親和度和新任務(Affinities and new tasks)
預設情況下,一個應用程式的所有活動互相之間都有一個親和度(affinity)——也就是說,他們屬於同一個任務的偏好(preference)。然而,也可以通過元素的taskAffinity
屬性為每個活動設定個體親和度。定義在不同應用程式中的活動能夠共用親和度,同一個應用程式中的活動可以分配不一樣的親和度。親和度發揮作用的兩種情況:1)啟動活動的Intent對象包含FLAG_ACTIVITY_NEW_TASK
標誌時;2)一個活動的allowTaskReparenting
屬性為"true"時。
如前所述,預設情況下,一個新的活動被啟動到調用startActivity()方法的活動所在的任務。它被壓入調用它的活動的棧中。但是,如果傳遞給方法的Intent對象包含FLAG_ACTIVITY_NEW_TASK
標誌,系統找一個不同的任務容納活動。通常,顧名思義它表示一個新任務。但是,他並非一定如此。如果已經存在一個任務與新活動親和度一樣,該活動將啟動到該任務。如果不是,則啟動一個新任務。
如果一個活動的allowTaskReparenting
屬性為"true",它可以從啟動它的任務轉移到與它有親和度並轉到前台啟動並執行任務中。例如,假設一個天氣預報的活動,但選擇城市是一個旅遊應用程式的一部分。它與同一個應用程式中的其他活動具有相同的親和度,且允許重新選擇父活動(reparenting)。你的一個活動啟動天氣預報活動,因此他初始是跟你的活動屬於同一個任務。但是,當旅遊應用程式切換到前台運行時,天氣預報活動將被重新分配和顯示到該任務。
如果一個.apk檔案,從使用者的角度看包含不止一個“應用程式”,你可能要為與他們有關的些活動指定不一樣的親和度。
3、啟動模式(Launch modes)
有四種不同的啟動模式可以分配到元素的launchMody
屬性:
- "standard"(預設模式)
- "singleTop "
- "singleTask"
- "singleInstance"
這些模式的在以下四方面不同:
- 哪個任務將持有響應意圖(intent)的活動。對"standard"和"singleTop "模式,是產生意圖的任務(調用startActivity()方法)——除非Intent對象包含
FLAG_ACTIVITY_NEW_TASK
標誌。在那種情況下,像上一節親和度和新任務(Affinities and new tasks)所描述的那樣選擇一個不同的任務。
相反,"singleTask"和"singleInstance"模式,總是將主動標記為一個任務的根活動。他們定義一個任務,而從不啟動到其他任務。
- 活動是否可以執行個體化多次。"standard"或"singleTop "活動可以執行個體化多次。這些執行個體可以屬於多個任務,且一個給定任務可以包含同一個活動的多個執行個體。
相反,"singleTask"和"singleInstance"活動僅可以被執行個體化一次。因為這些活動是一個任務的根,這個限制意味著裝置上一個時間只有不多於一個任務的執行個體。
- 是否允許執行個體所在任務有其他活動。"singleInstance"活動所在任務只有它一個活動。如果他啟動別的活動,那些活動將啟動到不同的任務中,無論它的模式如何——就好像Intent對象包含
FLAG_ACTIVITY_NEW_TASK
標誌。在所有其他方面,"singleInstance"模式等同於"singleTask"模式。
其它三種模式允許多個活動屬於一個任務。"singleTask"活動總是任務的根活動,但是它能啟動其他活動到它的任務。"standard"或"singleTop "活動的執行個體可以出現的棧中的任何位置。
- 響應一個意圖時是否需要產生類的新執行個體。對於預設的"standard"模式,建立新的執行個體去響應每一個新的意圖。每個執行個體僅處理一個意圖。對於"singleTop "模式,一個類已存在的執行個體可以重新用了處理新的意圖,如果它位於目標任務的活動棧的棧頂。如果不是在棧頂,就不可以重用。相反,將建立一個新的執行個體並壓入棧頂。
例如,一個任務的活動棧由根活動A、B、C和D組成,順序為A-B-C-D。當一個意圖到達請求類型D時,如果D是預設的"standard"模式,將產生D類的新執行個體且棧變為A-B-C-D-D。然而,如果D的啟動模式是"singleTop ",已存在的D執行個體將去處理新的意圖(因為它在棧頂)且棧仍然是A-B-C-D。
如果,另一方面,到達的意圖是請求類型B時,一個B的新執行個體將啟動而不管B的模式是"standard"還是"singleTop "(因為B不是在棧頂),因此棧的結構為A-B-C-D-B。
如前所述,"singleTask"和"singleInstance"活動僅可以被執行個體化一次,因此他們的執行個體將處理所有的新意圖。一個"singleInstance"活動總是在棧頂(因為僅有一個活動在任務中),因此它總是在可以處理意圖的位置。然而,一個"singleTask"活動在棧中可能有或可能沒有其他活動在它上面。如果有,即它不在處理意圖的位置,意圖會被丟棄(即使意圖被丟棄了,它的到來使任務轉到並保持在前台運行)
當一個已存在的活動被請求處理一個新的意圖,Intent對象將通過onNewIntent()調用傳到活動。(產生啟動活動的意圖對象可以由getIntent()擷取。)
注意到當一個活動的新執行個體被建立去處理新意圖時,使用者總是可以按返回鍵返回到之前的狀態(之前的活動)。但是當一個已存在的活動執行個體去處理新意圖是,使用者不可以按返回鍵返回到意圖到達之前的狀態。
4、清除棧(Clearing the stack)
如果使用者離開一個任務很長時間,系統將會清除根活動之外的活動。當使用者再次返回到這個任務時,像使用者離開時一樣,僅顯示初始的活動。這個想法是,一段時間後,使用者可能已經放棄之前做的東西,及返回任務做新的事情。這是預設情況,有些活動屬性可以用來控制和改變這個行為。
alwaysRetainTaskState
屬性
如果在任務的根活動中這個屬性被設定為"true",剛才描述的預設行為將不會發生。任務將保留所有的活動在它的棧中,甚至是離開很長一段時間。
clearTaskOnLaunch
屬性
如果在任務的根活動中這個屬性被設定為"true",只有使用者離開就清除根活動之外的活動。換句話說,它與alwaysRetainTaskState
截然相反。使用者總是返回到任務的初始狀態,甚至是只離開一會。
finishOnTaskLaunch
屬性
這個屬性類似於clearTaskOnLaunch
,但是它作用於單個活動,而不是整個任務。而且它能移除任何活動,包括根活動。當它被設定為"true",任務本次會話的活動的部分還存在,如果使用者離開並返回到任務時,它將不再存在。
有其他的方法強制從棧中移除活動。如果Intent對象包含FLAG_ACTIVITY_CLEAR_TOP
標誌,目標任務已經有一個指定類型的活動執行個體,棧中該執行個體上面的其它活動將被移除而使它置於棧頂響應意圖。如果指定的活動的啟動類型是"standard",它自己也將被移除出棧,且一個新的執行個體將被啟動去處理到來的意圖。這是因為當模式是"standard"時,總是建立一個新的執行個體去處理新的意圖。
FLAG_ACTIVITY_CLEAR_TOP
標誌經常與FLAG_ACTIVITY_NEW_TASK
一起使用。當一起使用時,這些標誌的方式是定位到另一個任務中的已存在的活動並把它放到可以處理意圖的位置。
5、啟動任務(Starting tasks)
通過給定活動一個意圖過濾器"android.intent.action.MAIN"作為指定行為(action)和"android.intent.category.LAUNCHER"指定種類(category),活動就被設定為任務的進入點了。上篇Android開發之旅:應用程式基底礎及組件(續)第四節Intent過濾器中我們舉了這樣一個例子,它將導致該活動的表徵圖(icon)和標籤(label)顯示在應用程式啟動器,給使用者啟動它或啟動之後任意時候返回到它。
它的第二個功能非常重要:使用者可以離開任務且之後可以返回到它。基於這個原因,兩個啟動模式"singleTask"和"singleInstance"標記活動總是初始化一個任務來響應意圖,僅可以使用在有MAIN和LAUNCHER過濾器的活動中。想象一下,如果沒有這個過濾器將會發生什麼:一個意圖啟動一個"singleTask"活動,開始一個新任務,使用者在任務中做一些操作。然後使用者按下HOME鍵,任務現在退到後台運行且被主畫面遮蔽住。而且,由於活動不在應用程式啟動器中顯示,使用者無法再返回。
類似的困難也出現在FLAG_ACTIVITY_NEW_TASK
標誌。如果這個標誌導致一個活動開始一個新的任務且使用者按HOME鍵離開它,就必須要有某種方法是使用者能夠導航回來。一些實體(如通知管理器)總是在外部任務啟動活動,從不作為他們自己的一部分,因此他們總是將帶FLAG_ACTIVITY_NEW_TASK
標誌的意圖傳到startActivity()方法啟動活動。如果你有活動能調用外部實體,可以使用此標誌,注意使用者有一個獨立的方式返回到開始的任務。
如果您希望使用者離開活動後就不能再回到這個活動,可以將元素的finishOnTaskLaunch
屬性設定為"true"。可以參見清除棧那節。