Android 的一個特點就是 application A 的 activity 能夠啓動 application B 的 activity,儘管 A 和 B 是毫無干系的,而在用戶看來,兩個場景緊密聯繫,視覺上兩者構成了一個總體。Android 就是把這種誤覺定義爲 Task,它既不是 class,也不是 AndroidMainifest.xml 中的一個元素。從表現上看 Task 就像是一個 stack,一個一個的 activity 是構成 stack 的元素,作着入棧 (push) 和出棧 (pop-up)這樣簡單重複性的勞動。
默認的規則老是知足大多數的應用場景,可是也總會有一些例外打破習覺得常的慣例。Task 的默認規則一樣並不是牢不可破,修改的方法仍是有的。藉助 Intent 中的 flag 和 AndroidMainifest.xml 中 activity 元素的屬性,就能夠控制到 Task 裏 Activity 的關聯關係和行爲。
在 android.content.Intent 中一共定義了20種不一樣的 flag,其中和 Task 緊密關聯的有四種:
1.FLAG_ACTIVITY_NEW_TASK
2.FLAG_ACTIVITY_CLEAR_TOP
3.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
4.FLAG_ACTIVITY_SINGLE_TOP
在使用這四個 flag 時,一個 Intent 能夠設置一個 flag,也能夠選擇若干個進行組合。
默認狀況下,經過 startActivity() 啓動一個新的 Activity,新的 Activity 將會和調用者在同一個 stack 中。可是,若是在傳遞給 startActivity() 的 Intent 對象裏包含了 FLAG_ACTION_NEW_TASK,狀況將發生變化,–系統將爲新的 Activity 「尋找」一個不一樣於調用者的 Task。不過要找的 Task 是否是必定就是 NEW 呢?若是是第一次執行,則這個設想成立,若是說不是,也就是說已經有一個包含此 Activity 的Task 存在,則不會再啓動 Activity。
若是 flag 是 FLAG_ACTIVITY_CLEAR_TOP,同時當前的 Task 裏已經有了這個 Activity,那麼情形又將不同。Android 不但不會啓動新的 Activity 實例,並且還會將 Task 裏 該 Activity 之上的全部 Activity 一概結束掉,而後將 Intent 發給這個已存在的 Activity。Activity 收到 Intent 以後,能夠在 onNewIntent() 裏作下一步的處理,也能夠自行結束而後從新建立本身。若是 Activity 在 AndroidMainifest.xml 裏將啓動模式設置成」multiple」,– 默認模式,而且 Intent 裏也沒有設置 FLAG_ACTIVITY_SINGLE_TOP,那麼它將選擇後者。不然,它將選擇前者。FLAG_ACTIVITY_CLEAR_TOP 還能夠和 FLAG_ACTION_NEW_TASK 配合使用。
若是 flag 設置的是 FLAG_ACTIVITY_SINGLE_TOP,則意味着若是 Activity 已是運行在 Task 的 top,則該 Activity 將不會再被啓動。html
設置intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); java
下面是關於setFlags的一下屬性的解釋:android
1、Activity和Task(棧)的關係
Task就像一個容器,而Activity就至關與填充這個容器的東西,第一個東西(Activity)則會處於最下面,最後添加的東西(Activity)則會在最低端。從Task中取出東西(Activity)則是從最頂端取出,也就是說最早取出的是最後添加的東西(Activity),一次類推,最後取出的是第一次添加的Activity,而Activity在Task中的順序是能夠控制的,那則在Activity跳轉時用到Intent Flag
2、界面跳轉和服務的啓動都會用到Intent,如今介紹Intent Flag是關於Activity的跳轉
Intent intent = new Intent(this,xxx.class);
//若是activity在task存在,拿到最頂端,不會啓動新的Activity
intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
//若是activity在task存在,將Activity之上的全部Activity結束掉
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//默認的跳轉類型,將Activity放到一個新的Task中
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//若是Activity已經運行到了Task,再次跳轉不會在運行這個Activity
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);web
來自:http://www.anddevs.com/?p=118瀏覽器
1.Activity的affinity(親和力)app
2.Intent幾種常見的flags測試
3.<activity>與task相關屬性this
affinity:google
task對於Activity來講就好像它的身份證同樣,能夠告訴所在的task,本身屬於這個task中的一員;擁有相同affinity的多個Activity理論同屬於一個task,task自身的affinity決定於根Activity的affinity值。affinity在什麼場合應用呢?1.根據affinity從新爲Activity選擇宿主task(與allowTaskReparenting屬性配合工做);2.啓動一個Activity過程當中Intent使用了FLAG_ACTIVITY_NEW_TASK標記,根據affinity查找或建立一個新的具備對應affinity的task。咱們會在後面進行詳細講解。spa
默認狀況下,一個應用內的全部Activity都具備相同的affinity,都是從Application(參考<application>的taskAffinity屬性)繼承而來,而Application默認的affinity是<manifest>中的包名,咱們能夠爲<application>設置taskAffinity屬性值,這樣能夠應用到<application>下的全部<activity>,也能夠單獨爲某個Activity設置taskAffinity。例如:在系統自帶的Browser中,package爲com.android.browser,可是<application>卻自定義一個taskAffinity屬性值:
[html] view plaincopy
<application android:name="Browser"
android:label="@string/application_name"
android:icon="@drawable/ic_launcher_browser"
android:backupAgent=".BrowserBackupAgent"
android:taskAffinity="android.task.browser" >
Intent幾種常見的flags:
在android.content.Intent中定義了若干個flags,其中最重要的有如下幾個:
1.FLAG_ACTIVITY_NEW_TASK:當Intent對象包含這個標記時,系統會尋找或建立一個新的task來放置目標Activity,尋找時依據目標Activity的taskAffinity屬性進行匹配,若是找到一個task的taskAffinity與之相同,就將目標Activity壓入此task中,若是查找無果,則建立一個新的task,並將該task的taskAffinity設置爲目標Activity的taskActivity,將目標Activity放置於此task。注意,若是同一個應用中Activity的taskAffinity都使用默認值或都設置相同值時,應用內的Activity之間的跳轉使用這個標記是沒有意義的,由於當前應用task就是目標Activity最好的宿主。下面咱們會經過實例進行演示這個特性:
咱們新建兩個項目,分別命名爲appA和appB,而且分別建立FirstActivity和SecondActivity,咱們準備讓appB中的FirstActivity跳轉到appA的SecondActivity。appA中的SecondActivity配置以下:
[html] view plaincopy
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="android.intent.action.APP_A_SECOND_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
而後,在appB中的FirstActivity跳轉代碼以下:
[java] view plaincopy
Intent intent = new Intent("android.intent.action.APP_A_SECOND_ACTIVITY");
startActivity(intent);
咱們要演示幾個步驟:1.在appB中的FirstActivity點擊按鈕跳轉到appA中的SecondActivity;2.按Home鍵回到主屏,在主選單中再次啓動appB;3.按Home鍵回到主屏,在主選單中啓動appA。演示過程如圖所示:
再次啓動appB應用:
啓動appA應用:
咱們發如今從appB跳轉到appA的SecondActivity以後,SecondActivity實例好像是嵌入到了appB中,可是不影響appA的正常運行,這種關係以下圖所示:
而後咱們修改一下跳轉的代碼:
[java] view plaincopy
Intent intent = new Intent("android.intent.action.APP_A_SECOND_ACTIVITY");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
咱們加上了FLAG_NEW_TASK標記,在來看一下演示結果:
再次啓動appB:
啓動appA:
咱們看到差異了吧,當咱們再次啓動appB時已經看不到剛纔啓動的appA中的SecondActivity,而啓動appA時卻直接看到了,說明這個SecondActivity實例並不在appB的task內,而是建立了一個task,這個task的affinity就是SecondActivity默認的affinity,因爲appA的SecondActivity的affinity是從Application繼承而來,因此當appA啓動時會直接找到這個task,而不是建立新的task。咱們看一下解析圖:
2.FLAG_ACTIVITY_CLEAR_TOP:當Intent對象包含這個標記時,若是在棧中發現存在Activity實例,則清空這個實例之上的Activity,使其處於棧頂。例如:咱們的FirstActivity跳轉到SecondActivity,SecondActivity跳轉到ThirdActivity,而ThirdActivity又跳到SecondActivity,那麼ThirdActivity實例將被彈出棧,使SecondActivity處於棧頂,顯示到幕前,棧內只剩下FirstActivity和SecondActivity。這個SecondActivity既能夠在onNewIntent()中接收到傳來的Intent,也能夠把本身銷燬以後從新啓動來接受這個Intent。在使用默認的「standard」啓動模式下,若是沒有在Intent使用到FLAG_ACTIVITY_SINGLE_TOP標記,那麼它將關閉後重建,若是使用了這個FLAG_ACTIVITY_SINGLE_TOP標記,則會使用已存在的實例;對於其餘啓動模式,無需再使用FLAG_ACTIVITY_SINGLE_TOP,它都將使用已存在的實例,Intent會被傳遞到這個實例的onNewIntent()中。
下面咱們來驗證一下這個過程:
首先,Activity啓動模式都按照默認值「standard」。從FirstActivity跳轉到SecondActivity,SecondActivity實例以下:
從ThirdActivity跳轉到SecondActivity時,跳轉代碼以下:
[java] view plaincopy
Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
而後跳轉後SecondActivity實例以下:
從序列號能夠看到這兩個實例是不一樣的,證實它是通過了銷燬和從新的過程。
而後咱們把ThirdActivity中的跳轉代碼添加FLAG_ACTIVITY_SINGLE_TOP標記:
[java] view plaincopy
Intent intent = new Intent(this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
兩次實例均以下圖所示:
若是咱們不想添加FLAG_ACTIVITY_SINGLE_TOP,那麼把SecondActivity的啓動模式改成「standard」以外的三種便可,效果和上面同樣,都不會建立新的實例。
3.FLAG_ACTIVITY_SINGLE_TOP:當task中存在目標Activity實例而且位於棧的頂端時,再也不建立一個新的,直接利用這個實例。咱們在上邊的例子中也有講到。
4.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:若是一個Intent中包含此屬性,則它轉向的那個Activity以及在那個Activity其上的全部Activity都會在task重置時被清除出task。當咱們將一個後臺的task從新回到前臺時,系統會在特定狀況下爲這個動做附帶一個FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,意味着必要時重置task,這時FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就會生效。通過測試發現,對於一個處於後臺的應用,若是在主選單點擊應用,這個動做中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,長按Home鍵,而後點擊最近記錄,這個動做不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,因此前者會清除,後者不會。關於這個標記,能夠下圖示之:
這個標記對於應用存在分割點的狀況會很是有用。好比咱們在應用主界面要選擇一個圖片,而後咱們啓動了圖片瀏覽界面,可是把這個應用從後臺恢復到前臺時,爲了不讓用戶感到困惑,咱們但願用戶仍然看到主界面,而不是圖片瀏覽界面,這個時候咱們就要在轉到圖片瀏覽界面時的Intent中加入此標記。
5.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:這個標記在如下狀況下會生效:1.啓動Activity時建立新的task來放置Activity實例;2.已存在的task被放置於前臺。系統會根據affinity對指定的task進行重置操做,task會壓入某些Activity實例或移除某些Activity實例。咱們結合上面的CLEAR_WHEN_TASK_RESET能夠加深理解。
<activity>的task相關屬性
在<activity>中定義了幾個常見的task相關屬性,它們分別表明了task內部不一樣的行爲特徵,咱們就來逐個介紹一下:
1.android:allowTaskReparenting
這個屬性用來標記一個Activity實例在當前應用退居後臺後,是否能從啓動它的那個task移動到有共同affinity的task,「true」表示能夠移動,「false」表示它必須呆在當前應用的task中,默認值爲false。若是一個這個Activity的<activity>元素沒有設定此屬性,設定在<application>上的此屬性會對此Activity起做用。例如在一個應用中要查看一個web頁面,在啓動系統瀏覽器Activity後,這個Activity實例和當前應用處於同一個task,當咱們的應用退居後臺以後用戶再次從主選單中啓動應用,此時這個Activity實例將會從新宿主到Browser應用的task內,在咱們的應用中將不會再看到這個Activity實例,而若是此時啓動Browser應用,就會發現,第一個界面就是咱們剛纔打開的web頁面,證實了這個Activity實例確實是宿主到了Browser應用的task內。咱們就來結合實例演示一下這個過程:
首先,在appB的FirstActivity中,咱們將跳轉動做作如下改動:
[java] view plaincopy
Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com.hk"));
startActivity(viewIntent);
進入appB時的界面:
啓動web界面以後:
而後咱們按Home鍵,是當前應用退居後臺,咱們回到主選單,從新啓動appB,界面以下:
此時咱們在主選單中啓動Browser應用,出如今咱們眼前的界面是這樣的:
以上這種行爲也證實了咱們前面的論斷,爲了更清楚的說明問題,也爲了讓你們本身能夠驗證,下面咱們要再次演示一下appB和appA的啓動過程:
對於appA,在上面的基礎上,不用修改其餘地方,只需爲SecondActivity的<activity>元素添加一個屬性,以下:
[html] view plaincopy
<activity android:name=".SecondActivity" android:allowTaskReparenting="true">
...
</activity>
而後,在appB中的FirstActivity跳轉代碼改成:
[java] view plaincopy
Intent intent = new Intent("android.intent.action.APP_A_SECOND_ACTIVITY");
startActivity(intent);
咱們啓動appB,看到一下界面:
而後點擊按鈕,跳轉到appA中的SecondActivity,界面以下:
此時appB中的FirstActivity和appA中的SecondActivity處於同一個task中taskid爲28,而後咱們按下Home鍵,在主選單中再次啓動appB,咱們發現appA的SecondActivity不見了,咱們看到的是:
而後咱們啓動appA,這是咱們不會看到它的FirstActivity,而是看到了它的SecondActivity:
一般兩個應用分別有本身的task,它們的taskid確定不一樣,但這裏的SecondActivity卻顯示taskid與appB相同,咱們想一下也許就明白了,原來它是appB遷徙過來的,再啓動appA時並未生成任何新的Activity實例。這個時候若是咱們按下後退鍵,appA就會當即退出,證實了此時appA的task裏只有一個Activity實例,也就是這個SecondActivity實例。
須要注意的是,若是appB退居後臺以後,沒有再次啓動appB,而是直接啓動appA,將不會出現以上現象。從新宿主的動做發生在appB再次啓動的過程當中。
android:allowReparenting的效果圖以下:
2.android:alwaysRetainTaskState
這個屬性用來標記應用的task是否保持原來的狀態,「true」表示老是保持,「false」表示不可以保證,默認爲「false」。此屬性只對task的根Activity起做用,其餘的Activity都會被忽略。
默認狀況下,若是一個應用在後臺呆的過久例如30分鐘,用戶從主選單再次選擇該應用時,系統就會對該應用的task進行清理,除了根Activity,其餘Activity都會被清除出棧,可是若是在根Activity中設置了此屬性以後,用戶再次啓動應用時,仍然能夠看到上一次操做的界面。
這個屬性對於一些應用很是有用,例如Browser應用程序,有不少狀態,好比打開不少的tab,用戶不想丟失這些狀態,使用這個屬性就極爲恰當。
3.android:clearTaskOnLaunch
這個屬性用來標記是否從task清除除根Activity以外的全部的Activity,「true」表示清除,「false」表示不清除,默認爲「false」。一樣,這個屬性也只對根Activity起做用,其餘的Activity都會被忽略。
若是設置了這個屬性爲「true」,每次用戶從新啓動這個應用時,都只會看到根Activity,task中的其餘Activity都會被清除出棧。若是咱們的應用中引用到了其餘應用的Activity,這些Activity設置了allowTaskReparenting屬性爲「true」,則它們會被從新宿主到有共同affinity的task中。
無圖無真相,咱們就來以實例演示一下這個過程,咱們首先修改appB的根Activity的<activity>元素,以下:
[html] view plaincopy
<activity android:name=".FirstActivity"
android:clearTaskOnLaunch="true">
...
</activity>
FristActivity界面以下:
而後咱們讓FirstActivity跳轉到SecondActivity,結果以下:
而後咱們按Home鍵回到主界面,再次啓動appB,咱們看到如下結果:
咱們看到,再次啓動appB時,咱們只能看到FirstActivity界面,此時在FirstActivity之上的全部Activity都已經被清除出棧。示意圖以下:
4.android:finishOnTaskLaunch
這個屬性和android:allowReparenting屬性類似,不一樣之處在於allowReparenting屬性是從新宿主到有共同affinity的task中,而finishOnTaskLaunch屬性是銷燬實例。若是這個屬性和android:allowReparenting都設定爲「true」,則這個屬性勝出。
以上就是今天總結的內容,這些都是經常使用的知識,除此以外還有不少等着咱們去探索,繼續努力。