細談Activity四種啓動模式

    嗨大家好,又和大家見面了,上一次我們一起搞清楚了Handler的源碼機制(現在回想起來是不是感覺也就那麼回事,當時看的頭皮發麻-。+!!),今天我們談一談我們在Android開發中必不可缺少的一個組件——Activity:

    Activity作爲四大組件之一,也可以說是四大組件中最重要的一個組件,它負責App的視圖,還負責用戶交互,而且有時候還經常其他組件綁定使用,可以說非常的重要。

    雖然說我們天天都在使用Activity,但是你真的對Activity的生命機制爛熟於心,完全瞭解了嗎?的確,Activity的生命週期方法只有七個(自己數-。+),但是其實那只是最平常的情況,或者說是默認的情況。也就是說在其他情況下,Activity的生命週期可能不會是按照我們以前所知道的流程,這就要說到我們今天的重點了——Activity的啓動模式:我們的Activity會根據自身不同的啓動模式,自身的生命週期方法會進行不同的調用。

一、在將啓動模式之前必須瞭解的一些知識:

    在正式的介紹Activity的啓動模式之前,我們首先要了解一些旁邊的知識,這些知識如果說模糊不清,那麼在討論啓動模式的時候會一頭霧水(筆者親身感悟)。

1.一個應用程序通常會有多個Activity,這些Activity都有一個對應的action(如MainActivity的action),我們可以通過action來啓動對應Activity(隱式啓動)。

<action android:name="android.intent.action.MAIN" />

2.一個應用程序可以說由一系列組件組成,這些組件以進程爲載體,相互協作實現App功能。

3.任務棧(Task Stack)或者叫退回棧(Back Stack)介紹:

3.1.任務棧用來存放用戶開啓的Activity。

3.2.在應用程序創建之初,系統會默認分配給其一個任務棧(默認一個),並存儲根Activity。

3.3.同一個Task Stack,只要不在棧頂,就是onStop狀態:


3.4.任務棧的id自增長型,是Integer類型。

3.5.新創建Activity會被壓入棧頂。點擊back會將棧頂Activity彈出,併產生新的棧頂元素作爲顯示界面(onResume狀態)。

3.6.當Task最後一個Activity被銷燬時,對應的應用程序被關閉,清除Task棧,但是還會保留應用程序進程(狂點Back退出到Home界面後點擊Menu會發現還有這個App的框框。個人理解應該是這個意思),再次點擊進入應用會創建新的Task棧。

4.Activity的affinity:

4.1.affinity是Activity內的一個屬性(在ManiFest中對應屬性爲taskAffinity)。默認情況下,擁有相同affinity的Activity屬於同一個Task中。

4.2.Task也有affinity屬性,它的affinity屬性由根Activity(創建Task時第一個被壓入棧的Activity)決定。

4.3.在默認情況下(我們什麼都不設置),所有的Activity的affinity都從Application繼承。也就是說Application同樣有taskAffinity屬性。

<application
        android:taskAffinity="gf.zy"

4.4.Application默認的affinity屬性爲Manifest的包名。


暫時就是這麼多了,如果還有不妥的地方我會補充的。接下來我們來正式看Activity的啓動模式:

二、Activity啓動模式:

1.默認啓動模式standard:

    該模式可以被設定,不在manifest設定時候,Activity的默認模式就是standard。在該模式下,啓動的Activity會依照啓動順序被依次壓入Task中:


上面這張圖講的已經很清楚了,我想應該不用做什麼實驗來論證了吧,這個是最簡單的一個,我們過。

2.棧頂複用模式singleTop:

    在該模式下,如果棧頂Activity爲我們要新建的Activity(目標Activity),那麼就不會重複創建新的Activity。



這次我來用代碼舉例:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zy.pers.activitytext">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:taskAffinity="gf.zy"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".TwoActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="ONETEXT_TWOACTIVITY" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity android:name=".ThreeActivity">
            <intent-filter>
                <action android:name="ONETEXT_THREEACTIVITY" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

</manifest>

這是我的第一個應用OneText的Mainfest結構,裏面創建了三個Activity,我們把第二個Activity的模式設置爲singleTop。

每個Activity界面都只有一個顯示當前界面名稱的TextView和一個用來組跳轉的Button,所以應用OneText的功能就是從活動1跳轉到活動2,活動2繼續跳轉活動2,代碼就不給大家展示了,都能寫出來。


我們發現在我們跳轉到TwoActivity之後,點擊跳轉新的TwoActivity時候,他沒有響應。

爲了作對比,我們再把TwoActivity設置爲standard,看一看效果:


我們發現創建了很多的TwoActivity。

同時我們打印上task的Id(我沒有把所有周期方法都打印log):


發現他們全部都是來自一個Task。這個可以過。

應用場景:

開啓渠道多,適合多應用開啓調用的Activity:通過這種設置可以避免已經創建過的Activity被重複創建(多數通過動態設置使用,關於動態設置下面會詳細介紹)

3.棧內複用模式singleTask:

    與singleTop模式相似,只不過singleTop模式是隻是針對棧頂的元素,而singleTask模式下,如果task棧內存在目標Activity實例,則:

  1. 將task內的對應Activity實例之上的所有Activity彈出棧。
  2. 將對應Activity置於棧頂,獲得焦點。

同樣我們也用代碼來實現一下這個過程:

還是剛纔的那一坨代碼,只是我們修改一下Activity1的模式爲singleTask,然後讓Activity2跳轉到Activity3,讓Activity3跳轉到Activity1:


在跳回MainActivity之後點擊back鍵發現直接退出引用了,這說明此時的MainActivity爲task內的最後一個Activity。所以這個模式過。

應用場景:

程序主界面,我們肯定不希望主界面被多創建,而且在主界面退出的時候退出整個App是最好的設想。

耗費系統資源的Activity:對於那些及其耗費系統資源的Activity,我們可以考慮將其設爲singleTask模式,減少資源耗費(在創建階段耗費資源的情況,個人理解-。+)。

4.全局唯一模式singleInstance:

    這是我們最後的一種啓動模式,也是我們最噁心的一種模式:在該模式下,我們會爲目標Activity分配一個新的affinity,並創建一個新的Task棧,將目標Activity放入新的Task,並讓目標Activity獲得焦點。新的Task有且只有這一個Activity實例。       如果已經創建過目標Activity實例,則不會創建新的Task,而是將以前創建過的Activity喚醒(對應Task設爲Foreground狀態)


我們爲了看的更明確,這次不按照上圖的步驟設計程序了(沒錯,這幾張圖都不是我畫的-。+!)。

我們先指定一下這次的程序:還是這三個Activity,這次Activity3設置爲singleInstance,1和2默認(standard)。

然後我們看一下這個效果:


說一下我們做了什麼操作:

首先由1創建2,2創建3,然後又由3創建2,2創建3,3創建2,然後一直back,圖如下:


還請各位別嫌棄我-。+,圖雖然不好看,但是很生動形象。。。。具體說一下:這張圖對應着我們上面的程序流程,黃色的代表Background的Task,藍色的代表Foreground的Task。

我們發現back的時候會先把Foreground的Task中的Activity彈出,直到Task銷燬,然後纔將Background的Task喚到前臺,所以最後將Activity3銷燬之後,會直接退出應用。

但是有沒有想過這樣會出現一個問題,什麼問題我們直接看圖就好:


我簡單說一下這個案例:1,2,3三個Activity,2是singleInstance模式,然後1->2,2->3,之後狂點back,在回到Home界面後點擊菜單鍵,發現首先啓動的是2Activity。

簡單解釋一下:1和3是一個task,2是單獨的一個task,在我們2->3後,前臺的task又從2的回到了1和3的。所以最後退出的task是2的線程,而如果不是重新啓動App。上一次最後關閉的Task是2的,所以。。

所以說singleInstance設置的Activity最好不要設置成中間界面。


以上表示我們關於四種模式的最基本理解,其實有了前面的知識瞭解之後,我們發現這些其實也不是很難對吧。。。真正比較繞的在後面-。+,注意前方高能:

三、動態設置啓動模式

    在上述所有情況,都是我們在Manifest中設置的(通過launchMode屬性設置),這個被稱爲靜態設置(我們寫程序寫多了會發現有靜態就有動態,而且靜態多數在xml設置,動態在java代碼設置-。+),接下來我們來看一下如何動態的設置Activity啓動方式。

注):如果同時有動態和靜態設置,那麼動態的優先級更高。

1.關於動態設置與靜態設置的理解:

    關於這個理解我是看過一篇文章,比較認同裏面的思想,所以在這裏也總結一下:

    靜態設置,可以理解爲通知別人:就是當我被創建的時候,我告訴你我是通過這種模式啓動的。

    動態設置,可以理解爲別人的要求:別人給我設一個Flag,我就以這種Flag的方式啓動。

    可能這個沒什麼用哈,但是仔細想一下這種對程序的思想理解應該是正確的。

2.幾種常見的Flag:

    我們說的動態設置,其實是通過Intent。對與Intent這個類大家應該不陌生吧,我們剛纔在啓動Activity的時候就用到這個類了。

如果我們要設置要啓動的Activity的啓動模式的話,只需要這樣:

intent.setFlags(、、、、、);

然後在裏面添加對應的Flag就好,那麼接下來我們介紹幾個常見的Flag(他的Flag太多了,頭皮發麻。):

2.1._NEW_TASK

他對應的Flag如下:

Intent.FLAG_ACTIVITY_NEW_TASK

這個Flag跟我們的singleInstance很相似:在給目標Activity設立此Flag後,會根據目標Activity的affinity進行匹配:如果已經存在與其affinity相同的task,則將目標Activity壓入此Task。    反之沒有的話,則新建一個task,新建的task的affinity值與目標Activity相同。然後將目標Activity壓入此棧。

其實簡單來說,就是先看看需不需要創建一個新的Task,根據就是有沒有相同的affinity。然後把Activity放進去。

但是此情況和singleInstance有不同,有兩點注意的地方:

  1. 新的Task沒有說只能存放一個目標Activity。只是說決定是否新建一個Task。而singleInstance模式下新的Task只能放置一個目標Activity。
  2. 在同一應用下,如果Activity都是默認的affinity,那麼此Flag無效。而singleInstance默認情況也會創建新的Task。

這個東西理解起來可能有一些抽象,我們通過一個實例來證明他:

在之前的一些例子中,我們都是在同一應用之間進行跳轉,而現在我們進行不同App的Activity相互跳轉(其實就是創造一個不同taskAffinity的情況。。。忘了的話見一、4)。

首先,我們需要創建一個新的App——TwoApp,這個App目前只需要一個MainActivity就夠了,我們在MainActivity放置一個button,讓他跳轉到OneApp的TwoActivity。

public void onClick(View v) {
    Intent intent = new Intent("ONETEXT_TWOACTIVITY");
    
    startActivity(intent);
}

這是跳轉的代碼。

現在我們先概述一下我們的流程:我們先打開TwoApp,然後在TwoApp的MainActivity界面跳轉到OneApp的TwoActivity。

對於OneApp的設定,我們已經將三個Activity都設置成了standard模式。還是1->2,2->3,3->2。

代碼就不上了,這麼簡單,大家自己也能寫出來。效果如下:

爲了看的清清楚楚,最開始清空了所有的進程。

現在我們點開TwoApp,

現在只有TwoApp一個進程,

現在我們點開了 OneApp的TwoActivity,但是我們發現他還是隻有一個進程,


現在我們在TwoApp的MainActivity跳轉到OneApp的TwoActivity,添加_NEW_TASK的Flag。

public void onClick(View v) {
    Intent intent = new Intent("ONETEXT_TWOACTIVITY");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

我們再看一下效果,還是跟剛纔一樣先清空所有的進程,這次效果直接連起來看了:


與上面不同的地方在於,我們新的界面創建在了新的進程中——其實就是OneApp被喚醒了,我們來分析一下爲什麼會這樣:

首先我們會想一下我們上面所學過的一個東西,affinity:我們說這個東西在默認情況下就是App的包名packageName,而OneApp中的TwoActivity默認的affinity就是OneApp的包名。這裏能夠理解吧。

然後我們說_NEW_TASK情況下,會先查找是否有對應的affinity的task,如果有就不在創建,直接將其放入,反之新建task,所以新建的task就是我們的OneApp的task,我們可以再做一個測試,我們先喚醒OneApp,然後再讓TwoApp跳轉到OneApp的TwoActivity(有點繞啊。。。),我們看是什麼情況:

首先啓動OneApp,並跳轉到ThreeActivity。

然後啓動TwoApp,並跳轉到OneApp的TwoActivity。

然後一直點擊Back,

我們發現在Two中喚醒One的TwoActivity,同樣是被放入了OneApp的默認Task中。

關於_NEW_TASK我們就說這麼多吧。

2.2._SINGLE_TOP

該模式比較簡單,對應Flag如下:

Intent.FLAG_ACTIVITY_SINGLE_TOP

次Flag與靜態設置中的singleTop效果相同,所以請見二、2.

2.3._CLEAR_TOP

這個模式對應的Flag如下:

Intent.FLAG_ACTIVITY_CLEAR_TOP

當設置此Flag時,目標Activity會檢查Task中是否存在此實例,如果沒有則添加壓入棧,

如果有,就將位於Task中的對應Activity其上的所有Activity彈出棧,此時有以下兩種情況:

  • 如果同時設置Flag_ACTIVITY_SINGLE_TOP,則直接使用棧內的對應Activity,
  • 沒有設置。。。。。。。,則將棧內的對應Activity銷燬重新創建。

關於這個Flag,我們發現他和singleTask很像,準確的說,是在_CLEAR_TOP和_SINGLE_TOP同時設置的情況下,就是singleTask模式。

而唯一不同的一點就在於:他會銷燬已存在的目標實例,再重新創建。這個我們通過打印一下生命週期就好。

這次我們只用OneApp就好了,還是1->2,2->3,3->2,這次我們將2的Flag設置爲_CLEAR_TOP,看一下TwoActivity的生命週期。


我們的流程如下:1->2       2->3    3->2     2->3    3->2    back    back   然後就退出了,這說明在Task內2上面的3的確被彈出棧了。

然後我們再看一下2的日誌:


我想在日誌圖片上面標註的很清楚了,我只截取了一部分日誌,我們質疑3->2時候先銷燬,後創建。


好,現在我們同時加上_SINGLE_TOP的Flag。

效果相同,我們只看log:


很明顯,在3->2的時候,TwoActivity調用了onRestart方法,也就是棧頂複用了。

這個Flag過。


其實還有一點點東西想提一下的,但是感覺 篇幅已經很噁心了。。。。再寫下去可能更沒人看了吧/笑着哭,所以決定找機會穿插到下一篇文章中:Activity標籤中task相關屬性

喜歡的話可以關注一波,有建議和不同觀點的歡迎下方留言!感謝大家的支持!

發佈了85 篇原創文章 · 獲贊 98 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章