Activity啓動模式的那點事

要說Activity是Android四大組件最重要的一點也不爲過,因爲用戶最能直觀感受的也就是Activity。在講啓動模式之前有兩個概念要先理一理,Stack和Task。

Task

Activity是顯示以及用戶直接進行交互的組件,我們可以簡單理解爲許多個Activity組合在一起就是一個app。而這麼多的Activity就在一個或多個Task中。

Stack

Activity存在於Task中,而Task有先後之分,比如可以從相機中打開相冊,也可以從相冊中打開相機。那麼這樣也就導致了Activity的顯示有先後順序,在Android系統中用來管理Activity的顯示順序就是Activity Stack,即Activity棧。一般情況下,Activity都遵循棧的規則FILO,最早展現的Activity位於棧的最底部,而位於棧頂的Activity就是當前顯示的Activity。


不同的啓動模式會影響Activity的顯示順序,也就是今天要講的。改變Activity的啓動模式可以在AndroidManifest中設置,也可以在代碼中通過設置Intent的Flag來實現。


AndroidManifest中設置:

#android:launchMode

launchMode是被提到最多的用來改變Activity棧內狀態的方式,一共有四種狀態(standard、singleTop、singleTask、singleInstance

·standard:標準模式。每次啓動Activity都會重新創建一個Activity實例並壓入上一個啓動Activity的Task棧頂(除非Intent中設置了FLAG_ACTIVITY_NEW_TASK)。

·singleTop:棧頂唯一模式。這個模式與standard的唯一區別就是當要啓動的Activity位於棧頂時,不會再重新創建一個新的Activity實例,並且將新的Intent傳入到已有實例的onNewIntent方法中。

·singleTask:棧內唯一模式。顧名思義,棧內唯一的模式就是隻要當前Task棧內存在目標Activity的實例,啓動時都不會重新創建新的實例,而是將Intent傳入到已有實例的onNewIntent方法中,並將目標實例之上的Activity全都移出棧。假設我們依次啓動A、B、C、D,然後在D中啓動B,其中B是singleTask模式的,那麼此時Stack中的情況就是A、B,因爲在A、B、C、D的時候啓動B的時候,B已經存在,所以就將B之上的Activity全部移出棧。

·singleInstance:單實例模式。這種模式永遠只會創建一個目標Activity實例,並且該實例會單獨佔有一個Task棧,並且不允許其它Activity壓入該Task棧。做個實驗,依次啓動A、B、C、D四個Activity,然後在D中再啓動B,其中B是singleInstance模式,其它都是標準模式,然後打印一下棧內情況。


依次啓動A、B、C、D四個Activity,我們可以看到A、C、D是在一個986e4cd的Task棧內,B獨佔一個8cd3460的Task,在Stack中的情況依舊是A、B、C、D,符合FILO的規則;


D中再啓動B,這下更清晰了,Stack中的情況是A、C、D、B,而且兩個Task的情況也沒有變,依舊是A、C、D在一個Task中,B單獨一個Task。D在啓動 B的時候,B實例已經存在了,所以並沒有重新創建Activity,而是將B移動到Stack的棧頂。

此時如果按照返回鍵來逐級返回,Activity的出棧順序就是B、D、C、A。


#android:taskAffinity

taskAffinity可以理解爲Activity的Task歸屬,每個Activity都有taskAffinity屬性,默認情況下與上一個啓動它的Activity相同,且第一個Activity的taskAffinity值就是包名,具有相同taskAffinity的Activity會在同一個Task中。但是單純在AndroidManifest中設置這個屬性並不會生效,必須與其他屬性搭配使用。

搭配一:launchMode設置成singleTask,或者Intent的Flag設置成FLAG_ACTIVITY_NEW_TASK。這兩個的效果是一樣的,這樣啓動的時候,會先判斷目標Activity的taskAffinity的Task是否存在,如果存在,就直接壓入;如果不存在,先創建Task,再壓入。判斷Task是否存在的時候不僅僅是在當前的應用內尋找,不同應用間如果taskAffinity相同,只要Task存在,就能被壓入。假設這麼一種情況,app_a先打開A_Activity,app_b再打開B_Activity,如果B_Activity的taskAffinity與A_Activity一致,那麼打開app_a,就會發現B_Activity在app_a中顯示。

搭配二:android:allowTaskReparenting屬性設置true,這個放到後面具體講。


#android:allowTaskReparenting

是否允許Task的轉移,承接上面的搭配二來仔細說說這個屬性,按照字面意思就是是否允許Activity從所屬的Task轉移去另一個Task。

做個實驗。先啓動app_a,包名爲com.aaa.abc,第一個Activity爲A_Activity;然後切換到後臺啓動app_b,包名爲com.bbb.abc,第一個Activity爲MainActivity,然後啓動NewActivity,其taskAffinity爲"com.aaa.abc",並且將allowTaskReparenting設置爲true。這時候打印Task的日誌我們可以發現,com.aaa.abc的Task內只有A_Activity;com.bbb.abc的Task中的情況是MainActivity、NewActivity。

這時候將app_b切換到後臺,將app_a切換到前臺 ,顯示在前臺的是app_b的NewActivity。打印日誌發現com.aaa.abc的Task中的情況是A_Activity、NewActivity,com.bbb.abc的Task中只有一個MainActivity。也就是說設置了NewActivity被轉移到了目標taskAffinity的棧內,也就是com.aaa.abc的Task中。


#android:clearTaskOnLaunch

啓動時清空Task。一般情況下,如果應用已經啓動了,那麼再次點擊Launcher圖標,會進入到最近一次顯示的Activity。但是clearTaskOnLaunch設置爲true的情況下,每次點擊Launcher圖標,都會將Task內的Activity全部清空,然後再啓動第一個Activity,所以我們也能猜到這個屬性只能對根Activity設置纔有用。


#android:alwaysRetainTaskState

長期保留Task狀態。一般而言,app在後臺停留太久,再次打開的時候,系統會清理Task內的Activity,然後再啓動根Activity,就像重新啓動一樣。alwaysRetainTaskState設置爲true的情況下,系統會長期保留Task的狀態,也就是再次啓動的時候依舊顯示最後一個Activity。


#android:finishOnTaskLaunch

Task切換到前臺的時候是否銷燬。android:finishOnTaskLaunch設置爲true的情況下,當Task重新切換到前臺時,設置該屬性的Activity會銷燬並移出棧。當finishOnTaskLaunch和allowTaskReparenting都設置成true的情況下優先使用finishOnTaskLaunch。雖然理論上可以這麼理解,但是我在自己寫demo測試的時候發現finishOnTaskLaunch與taskAffinity和singleTask一起使用的時候會變得很詭異。

比如Task內情況爲A、B、C、D,B設置finishOnTaskLaunch爲true,D同時設置單獨的taskAffinity和singleTask。然後將Task重新切換到前臺,此時Task的情況爲A、D、C。

再比如Task內情況爲A、B、C、D,B設置finishOnTaskLaunch爲true,C同時設置單獨的taskAffinity和singleTask。然後將Task重新切換到前臺,此時Task的情況爲C、D、A。

對此我不太能理解,先佔個坑以後理解力的時候再回來補上。如果此時看到這篇文章的你知道是什麼原因的話希望能賜教。




Intent中設置:

在Intent中設置flags也能影響Activity在的狀態。不過在講各種flags之前先看兩個方法,addFlags方法會覆蓋原來的flags,所以要設置多個flag的時候就要使用addFlags方法。

    public Intent setFlags(int flags) {
        mFlags = flags;
        return this;
    }
    public Intent addFlags(int flags) {
        mFlags |= flags;
        return this;
    }


#FLAG_ACTIVITY_BROUGHT_TO_FRONT

這個flag一般不是由應用的代碼來設置的,當Activity的launchMode設置成singleTask時系統會默認設置成這個flag。


#FLAG_ACTIVITY_CLEAR_TASK

設置了這個flag的Activity在啓動的時候會將Task裏的Activity全部清空,然後變成根Activity。單獨設置沒有效果,要設置singleTask或者FLAG_ACTIVITY_NEW_TASK纔有效。

#FLAG_ACTIVITY_CLEAR_TOP

設置了這個flag的Activity在啓動的時候會判斷Task內是否已經有目標Activity的實例存在,如果存在,就將目標Activity之上的Activity全部移除。我看很多博客和一些資料書上都說目標Activity如果存在就不會重新創建,Intent通過目標實例onNewIntent方法傳遞。這樣理解其實是不對的,這裏要分兩種情況:
1、目標Activity的啓動模式是標準模式,並且在同一個Intent中沒有加上FLAG_ACTIVITY_SINGLE_TOP,這樣目標Activity就會重新創建,走onCreate方法。
2、目標Activity的啓動模式是標準之外的另三種啓動模式之一,或者同一個Intent加上了FLAG_ACTIVITY_SINGLE_TOP,這樣目標Activity不會重新創建,onNewIntent方法會被調用。

#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

在API21之後都被替換成FLAG_ACTIVITY_NEW_DOCUMENT。

#FLAG_ACTIVITY_FORWARD_RESULT

一般情況下我們在銷燬一個Activity時想要帶點數據到上一個界面會採用startActivityForResult的方式來啓動這個Activity,而這個flag能夠達到跨級傳遞的效果。A->B->C,B啓動C時設置FLAG_ACTIVITY_FORWARD_RESULT,A的onActivityResult能收到C的Intent數據。當然A啓動B的時候必須要用startActivityForResult來啓動,同時因爲B不能在接收result了,所以B要用startActivity啓動B,或者在啓動B時的requestCode小於0。


#FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

這個flag一般不是由應用的代碼來設置的,當Activity是從歷史記錄中被拉起的,那麼系統會默認設置該flag。(一般長按home鍵等能夠調出歷史記錄)

#FLAG_ACTIVITY_MULTIPLE_TASK

這個flag必須與FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK搭配使用,Activity在啓動的時候總是會重新創建一個Task。


#FLAG_ACTIVITY_NEW_DOCUMENT

佔坑


#FLAG_ACTIVITY_NEW_TASK

設置這個flag,目標Activity會成爲一個Task的根Activity,但是單獨設置沒有效果,具體的搭配在文中很多地方都有提到。


#FLAG_ACTIVITY_NO_ANIMATION

設置了這個flag的Activity在啓動的時候不會出現轉場動畫。


#FLAG_ACTIVITY_NO_HISTORY

設置了這個flag的Activity不會保留在Stack中。A->B->C,其中B設置了FLAG_ACTIVITY_NO_HISTORY,那麼此時的棧內只有A、C。因爲B不會在棧內存活,所以B的onActivityForResult也永遠不會被調用到。這個效果等同於在AndroidManifest中設置android:noHistory="true"。


#FLAG_ACTIVITY_NO_USER_ACTION

當Activity進行跳轉時會調用到onUserLeaveHint方法,但是我們如果想要在一些非主觀意識跳轉的時候(比如鬧鐘、電話等)不回調到這個方法,那麼就可以設置這個flag。

#FLAG_ACTIVITY_PREVIOUS_IS_TOP

佔坑


#FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

佔坑


#FLAG_ACTIVITY_REORDER_TO_FRONT

設置了這個flag的Activity在啓動時候會判斷棧內是否已經有該Activity的實例存在,如果存在,就把該Activity移到棧頂,並且不改變其它Activity的先後順序。A->B->C->D,其中B設置了這個flag,D再啓動B,這時候棧內的情況是A、C、D、B。


#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

    /**
     * If set, the new activity is not kept in the list of recently launched
     * activities.
     */
    public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000;
從源碼註釋中的意思是如果設置了這個flag的話,目標Activity不會保留在最近啓動的Activity棧內,但是我自己調試的時候發現設置了FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS的Activity依然存在於棧內,所以我也不是很明白這個flag到底什麼作用。


#FLAG_ACTIVITY_SINGLE_TOP

等同於android:launchMode="singleTop"

#FLAG_ACTIVITY_TASK_ON_HOME

設置這個flag的Activity在啓動的時候會移動到當前Home Activity的Task的頂部,然後再按返回鍵回退就能看到Home界面了。但是這個flag必須搭配FLAG_ACTIVITY_NEW_TASK來使用,並且taskAffinity要單獨設置不一樣的值。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章