天天看點

【Android】Activity的task相關

古人學問無遺力,少壯工夫老始成。紙上得來終覺淺,絕知此事要躬行。南宋.陸遊《冬夜讀書示子聿(yù)》

軟體行業也是一樣,多少前輩不遺餘力的奮鬥才出現了軟體行業的繁榮的景象,其中已有不少成為大師級人物。今天我們站在偉人的肩膀上,自然會有不少的優勢,但不要忘了,要在對技術的認知方面有所提升,仍需我們去實踐,去實踐。

今天我們來講一下activity的task相關内容。

上次我們講到activity的四種啟動模式的時候,已經了解到一些關于task的技術,今天我再向大家介紹一下。task是一個具有棧結構的容器,可以放置多個activity執行個體。啟動一個應用,系統就會為之建立一個task,來放置根activity;預設情況下,一個activity啟動另一個activity時,兩個activity是放置在同一個task中的,後者被壓入前者所在的task棧,當使用者按下後退鍵,後者從task被彈出,前者又顯示在幕前,特别是啟動其他應用中的activity時,兩個activity對使用者來說就好像是屬于同一個應用;系統task和task之間是互相獨立的,當我們運作一個應用時,按下home鍵回到主屏,啟動另一個應用,這個過程中,之前的task被轉移到背景,新的task被轉移到前台,其根activity也會顯示到幕前,過了一會之後,在此按下home鍵回到主屏,再選擇之前的應用,之前的task會被轉移到前台,系統仍然保留着task内的所有activity執行個體,而那個新的task會被轉移到背景,如果這時使用者再做後退等動作,就是針對該task内部進行操作了。

我們今天就講一下和task相關的知識,主要分一下幾點:

1.activity的affinity(親和力)

2.intent幾種常見的flags

3.<activity>與task相關屬性

affinity:

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。我們會在後面進行詳細講解。

預設情況下,一個應用内的所有activity都具有相同的affinity,都是從application(參考<application>的taskaffinity屬性)繼承而來,而application預設的affinity是<manifest>中的包名,我們可以為<application>設定taskaffinity屬性值,這樣可以應用到<application>下的所有<activity>,也可以單獨為某個activity設定taskaffinity。例如:在系統自帶的browser中,package為com.android.browser,但是<application>卻自定義一個taskaffinity屬性值:

[html]  view plain copy

<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配置如下:

<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 plain copy

intent intent = new intent("android.intent.action.app_a_second_activity");  

startactivity(intent);  

我們要示範幾個步驟:1.在appb中的firstactivity點選按鈕跳轉到appa中的secondactivity;2.按home鍵回到主屏,在主選單中再次啟動appb;3.按home鍵回到主屏,在主選單中啟動appa。示範過程如圖所示:

【Android】Activity的task相關
【Android】Activity的task相關

再次啟動appb應用:

【Android】Activity的task相關

啟動appa應用:

【Android】Activity的task相關

我們發現在從appb跳轉到appa的secondactivity之後,secondactivity執行個體好像是嵌入到了appb中,但是不影響appa的正常運作,這種關系如下圖所示:

【Android】Activity的task相關

然後我們修改一下跳轉的代碼:

intent.setflags(intent.flag_activity_new_task);  

我們加上了flag_new_task标記,在來看一下示範結果:

【Android】Activity的task相關
【Android】Activity的task相關

再次啟動appb:

【Android】Activity的task相關

啟動appa:

【Android】Activity的task相關

我們看到差别了吧,當我們再次啟動appb時已經看不到剛才啟動的appa中的secondactivity,而啟動appa時卻直接看到了,說明這個secondactivity執行個體并不在appb的task内,而是建立了一個task,這個task的affinity就是secondactivity預設的affinity,由于appa的secondactivity的affinity是從application繼承而來,是以當appa啟動時會直接找到這個task,而不是建立新的task。我們看一下解析圖:

【Android】Activity的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執行個體如下:

【Android】Activity的task相關

從thirdactivity跳轉到secondactivity時,跳轉代碼如下:

intent intent = new intent(this, secondactivity.class);  

intent.setflags(intent.flag_activity_clear_top);  

然後跳轉後secondactivity執行個體如下:

【Android】Activity的task相關

從序列号可以看到這兩個執行個體是不同的,證明它是經過了銷毀和重新的過程。

然後我們把thirdactivity中的跳轉代碼添加flag_activity_single_top标記:

intent.setflags(intent.flag_activity_clear_top | intent.flag_activity_single_top);  

兩次執行個體均如下圖所示:

【Android】Activity的task相關

如果我們不想添加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标記,是以前者會清除,後者不會。關于這個标記,可以下圖示之:

【Android】Activity的task相關

這個标記對于應用存在分割點的情況會非常有用。比如我們在應用主界面要選擇一個圖檔,然後我們啟動了圖檔浏覽界面,但是把這個應用從背景恢複到前台時,為了避免讓使用者感到困惑,我們希望使用者仍然看到主界面,而不是圖檔浏覽界面,這個時候我們就要在轉到圖檔浏覽界面時的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中,我們将跳轉動作做以下改動:

intent viewintent = new intent(intent.action_view, uri.parse("http://www.google.com.hk"));  

startactivity(viewintent);  

進入appb時的界面:

【Android】Activity的task相關

啟動web界面之後:

【Android】Activity的task相關

然後我們按home鍵,是目前應用退居背景,我們回到主選單,重新啟動appb,界面如下:

【Android】Activity的task相關

此時我們在主選單中啟動browser應用,出現在我們眼前的界面是這樣的:

【Android】Activity的task相關

以上這種行為也證明了我們前面的論斷,為了更清楚的說明問題,也為了讓大家自己可以驗證,下面我們要再次示範一下appb和appa的啟動過程:

對于appa,在上面的基礎上,不用修改其他地方,隻需為secondactivity的<activity>元素添加一個屬性,如下:

<activity android:name=".secondactivity" android:allowtaskreparenting="true">  

...           

</activity>  

然後,在appb中的firstactivity跳轉代碼改為:

我們啟動appb,看到一下界面:

【Android】Activity的task相關

然後點選按鈕,跳轉到appa中的secondactivity,界面如下:

【Android】Activity的task相關

此時appb中的firstactivity和appa中的secondactivity處于同一個task中taskid為28,然後我們按下home鍵,在主選單中再次啟動appb,我們發現appa的secondactivity不見了,我們看到的是:

【Android】Activity的task相關

然後我們啟動appa,這是我們不會看到它的firstactivity,而是看到了它的secondactivity:

【Android】Activity的task相關

通常兩個應用分别有自己的task,它們的taskid肯定不同,但這裡的secondactivity卻顯示taskid與appb相同,我們想一下也許就明白了,原來它是appb遷徙過來的,再啟動appa時并未生成任何新的activity執行個體。這個時候如果我們按下後退鍵,appa就會立即退出,證明了此時appa的task裡隻有一個activity執行個體,也就是這個secondactivity執行個體。

需要注意的是,如果appb退居背景之後,沒有再次啟動appb,而是直接啟動appa,将不會出現以上現象。重新宿主的動作發生在appb再次啟動的過程中。

android:allowreparenting的效果圖如下:

【Android】Activity的task相關

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>元素,如下:

<activity android:name=".firstactivity"  

                  android:cleartaskonlaunch="true">  

        ...      

fristactivity界面如下:

【Android】Activity的task相關

然後我們讓firstactivity跳轉到secondactivity,結果如下:

【Android】Activity的task相關

然後我們按home鍵回到主界面,再次啟動appb,我們看到以下結果:

【Android】Activity的task相關

我們看到,再次啟動appb時,我們隻能看到firstactivity界面,此時在firstactivity之上的所有activity都已經被清除出棧。示意圖如下:

【Android】Activity的task相關

4.android:finishontasklaunch

這個屬性和android:allowreparenting屬性相似,不同之處在于allowreparenting屬性是重新宿主到有共同affinity的task中,而finishontasklaunch屬性是銷毀執行個體。如果這個屬性和android:allowreparenting都設定為“true”,則這個屬性勝出。

以上就是今天總結的内容,這些都是常用的知識,除此之外還有很多等着我們去探索,繼續努力。