1. 程式人生 > >Activity 的啟動模式、應用場景、Intent.FLAG_ 與 taskAffinity

Activity 的啟動模式、應用場景、Intent.FLAG_ 與 taskAffinity

前段時間去面試的時候,有被問到 Activity 的啟動模式。雖然這些東西都瞭解過,但是實際開發中並沒有怎麼應用過。所以被問到應用場景的時候,答的並不好。趁著有空,打算複習鞏固一下。

四種啟動模式與應用場景

standard

活動預設的標準啟動模式。

每當啟動一個新的活動,無論活動的例項是否存在,都會建立一個新的活動的例項,在返回棧中入棧,並處於棧頂的位置。

這個沒什麼好說的,一般的活動都是這個啟動模式。

singleTop

棧頂複用模式。

當要啟動的活動在返回棧中沒有例項,或者有例項但不位於棧頂位置,則與 standard 模式相同,建立一個新的活動例項併入棧;

如果要啟動的活動在返回棧中有例項並且恰好位於棧頂位置,則複用該例項,這個時候,依次執行的方法為:onPause()

-> onNewIntent(Intent intent) -> onResume()

可以看到 singleTop 和 standard 模式最主要的區別就是 singleTop 模式下,如果活動例項存在並恰好位於棧頂則複用,而 standard 則是無論什麼情況,都會建立新的例項。

使用場景:推送頁,比如優酷的推薦視訊、電商的推薦商品等。

singleTask

棧內複用模式。

啟動活動時,系統首先會檢查返回棧中是否存在該活動的例項,如果存在,則複用該例項。如果該例項處在非棧頂位置,將它上面所有的活動例項出棧,從而位於棧頂位置。這個時候就有兩種情況,分別呼叫的方法也不同,以下執行的方法只針對頁面 A 。

(1)要啟動的活動恰好已經在棧頂,比如 A -> A

onPause() -> onNewIntent(Intent intent) -> onResume()

(2)要啟動的活動不在棧頂,比如 A -> B -> A

onNewIntent(Intent intent) -> onRestart() -> onStart() -> onResume()

使用場景:最常見的應該就是 app 的主頁,當我們打開了很多頁面需要返回主頁時,可以利用這種方法。

singleInstance

單例項模式。

如果將一個活動的 launchMode 設定成了 singleInstance,那麼啟動它的時候,系統會單獨給它建立一個新的任務棧,並且該棧只允許這一個活動在其中執行。如果再反覆啟動該活動,不會建立新的任務棧或者例項,但是會呼叫 onPause()

-> onNewIntent(Intent intent) -> onResume() 方法。

這裡還有一個需要注意的地方,假如有三個活動 A B C,其中 A B 是預設的啟動模式,C 的啟動模式是 singleInstance,如果當前的頁面啟動順序為 A -> B -> C -> B,那麼按下系統返回鍵,出棧的順序為 B -> B -> A -> C。這是因為 A B B 在一個任務棧中,而 C 在單獨的任務棧中,按照一個棧中先進後出的規則,需要 B B A 全部出棧完畢,才會輪到 C 所在的棧中的元素出棧。

使用場景:一般很少用到,比如說鬧鐘的提示頁、鎖屏頁等。

使用方式

1. 在 AndroidManifest.xml 檔案中配置,比如:

<activity
        android:name=".ThirdActivity"
        android:launchMode="singleInstance">
</activity>

這是最常用的配置方式。

2. 使用 intent.addFlags 動態設定,比如:

Intent it = new Intent(ThirdActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(it);

注意:兩種設定方式還是有區別的。

(1)優先順序:第 2 種方式的優先順序要比第一種高。我曾經遇到過,在清單檔案給活動設定了 singleTop ,但是在快速重複啟動活動的時候,發現還是會開啟兩個頁面,如果遇到了這種情況,下文中有解決辦法。

(2)區別:第 1 種方式只能給活動設定 4 種基本的啟動模式,而第二種方式更加靈活。

Intent.FLAG_

FLAG 不僅能夠設定活動的啟動模式,還能夠改變獲得的執行狀態。關於活動的標誌位大概有 20 多個,這裡只講幾個常用的。

1. FLAG_ACTIVITY_NEW_TASK

Intent it = new Intent(SecondActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(it);

從字面上的意思來看,它會建立一個新的任務棧來存放要啟動的活動例項,但是僅僅單獨設定了這個標誌位的話是沒有任何效果的,並不會建立一個新的任務棧,僅僅只是建立了一個新的活動例項,和預設的啟動模式的表現上完全一致,你們可以自己去試試。

2. FLAG_ACTIVITY_CLEAR_TASK

Intent it = new Intent(SecondActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(it);

同樣的,從字面上的意思來看,它的作用是清空任務棧,但是僅僅單獨設定了這個標誌位的話,也是沒有任何效果的,棧不會被清空,僅僅只是建立了一個新的活動併入棧。

3. FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK

Intent it = new Intent(SecondActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(it);

雖然他們兩個單獨使用沒有效果,但是放在一起使用就能看到明顯的效果了。

假設當前的任務棧的 id 是 2,並且我們將要啟動一個新的活動,無論這個活動例項是否已經存在於棧內,都會清空任務棧中的所有元素,建立一個新的活動例項並放到一個新的任務棧中。但是我發現這個新的任務棧的 id 還是 2,不太清楚是直接用了之前的任務棧,還是銷燬了之前的任務棧,恰好新的任務棧的 id “剛好排到了 2 號”。看手機上頁面的切換效果,應該是後者。有了解的同學,希望能科普一下。

適用場景:適用於需要重新登入的場景,可以直接銷燬所有活動然後建立一個新的登入頁。

4. FLAG_ACTIVITY_SINGLE_TOP

Intent it = new Intent(ThirdActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(it);

與 singleTop 完全相同,不再多說。

5. FLAG_ACTIVITY_CLEAR_TOP

intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

這裡我們分幾種情況討論,以下每次啟動頁面時都添加了 FLAG_ACTIVITY_CLEAR_TOP

(1)A -> B

AonPause -> BonCreate -> BonStart -> BonResume -> AonSaveInstanceState -> AonStop

可以看到,當要啟動活動在棧中不存在例項時,和預設啟動模式的效果一致。

(2)A -> A

A1onPause -> A2onCreate -> A2onStart -> A2onResume -> A1onStop -> A1onDestroy

用 A1 和 A2 方便區分,在這種情況下,在 活動 A 裡面再啟動一個 活動 A ,會建立一個新的活動例項,並且當新的活動啟動完成後,將之前的活動銷燬。

(3)A -> B -> A

從活動 B 要啟動活動 A 開始依次執行的方法:

BonPause -> A1onDestroy -> A2onCreate -> A2onStart -> A2onResume -> BonStop -> BonDestroy

我測試了更多的頁面,比如 A -> B -> C -> D -> E -> B,發現系統建立新的 B 的例項,並且銷燬 B C D E。從而可以總結得出 FLAG_ACTIVITY_CLEAR_TOP 的作用:當要啟動的活動例項不存在時,建立新的活動例項入棧;當要啟動的活動例項已經存在時,建立新的活動例項入棧的同時,會把已存在的活動例項以及棧中它上面所有的活動都銷燬掉。

下面是我在開發的時候遇到的一個問題,大家可以參考一下:

<activity
        android:name=".ThirdActivity"
        android:launchMode="singleTop">
</activity>

在清單檔案中給活動設定了 singleTop 的模式,但是實際啟動的時候,由於某些原因非常快速的啟動了兩次,結果發現居然是啟動了兩個活動出現了兩個頁面,完全不是想要的結果。那麼如果保證只出現一個頁面呢?
不要再清單檔案設定啟動模式了,動態設定如下:

intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

如果 SINGLE_TOP 生效,那麼就會複用例項執行 onNewIntent 方法;如果 SINGLE_TOP 沒有生效,那麼會再建立一個新的頁面執行 onCreate 方法,在 CLEAR_TOP 的作用下,會將第一個頁面銷燬,從而保證了只有一個頁面顯示給使用者。所以,要同時在 onNewIntentonCreate 方法裡面做處理。

taskAffinity

<activity
            ...
            android:taskAffinity=".taskname">
</activity>

taskAffinity 的屬性是設定活動和棧的依附關係。一般情況下,一個 app 的所有活動都在一個棧中,但是我們也可以通過設定 singleInstance 來建立新的任務棧。利用 taskAffinity 屬性也可以實現這一功能。

每個活動都可以設定自己的 taskAffinity 屬性,這個屬性指出了它希望進入的棧。如果一個活動沒有指明該活動的 taskAffinity 屬性,那麼它的這個屬性就等於 Application 指明的 taskAffinity,如果 Application 也沒有指明,那麼該 taskAffinity 的值就等於包名。而棧也有自己的 affinity 屬性,它的值等於它的根活動的 taskAffinity的值。所以,設定該屬性的時候,要按照包名的格式來設定,用 . 號分隔。

下面我們通過幾個具體的例子來看看它的效果。

1.我們在清單檔案裡面給 B 設定如下:

<activity
        android:name=".BActivity"
        android:launchMode="singleTask"
        android:taskAffinity=".second"
</activity>

(1)A -> B -> B

由 A 到 B 時,由於 B 的例項還不存在,會建立一個新的任務棧和 B 的例項,然後再由 B 到 B,由於 B 已經存在了,這個時候會執行 B 的 onPause() -> onNewIntent(Intent intent) -> onResume() 方法。

(2)A -> B -> C -> A

這種情況下,我們也是隻給 B 設定了啟動模式, A1 C A2 都是未進行任何設定的。這個時候會發現,由 A 到 B 時建立了一個新的任務棧,之後的 C 和 A2 都和 B 在一個任務棧中,A2 和 A1 是不同棧中的兩個活動例項。

(3)A -> B -> C -> B

和上面的設定相同,由 A 到 B 時建立了一個新的任務棧,但是當 C 再到 B 時會發生什麼呢?C 會被銷燬,然後 B1 會執行 onNewIntent -> onRestart -> onStart -> onResume 方法。

2.下面的幾種情況的設定方法和上面不同了,我們在清單檔案裡面只設置了 taskAffinity 而沒有設定 launchMode

 <activity
        android:name=".SecondActivity"
        android:taskAffinity=".second">
 </activity>

(1)A -> B -> C -> B -> A

除了 taskAffinity 什麼都沒設定,這個時候沒有任何效果,只會不停地建立例項然後入棧。

(2)A -> B -> C

在 A -> B 的時候,動態設定了

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

A -> B 建立一個新的任務棧,並且之後的 C 和 B 在一個棧中。和上面個的第(2)情況是一致的。

(3)A -> B -> B

在 A -> B 和 B -> B 的時候,都設定了

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

A -> B 建立一個新的任務棧,B -> B 的時候,不會建立新的任務棧,並且不會建立新的 B 的例項,也不會執行 B 的任何方法!這點需要注意了,和上面的第(1)種情況並不一樣。

後記

在寫這篇部落格的時候,我發現啟動模式原來有這麼多的東西。我這裡也只是寫了一部分,更深層次的東西也沒有研究到,但寫了上面的這些感覺收穫也蠻大的。如果你們有興趣可以自己去研究研究。

歡迎關注我的微信公眾號
歡迎關注我的微信公眾號