詳解Activity啟動模式(二)Activity的四種啟動模式——LaunchMode

四種啟動模式

Standard

標準模式,每當有一次Intent請求,就會創建一個新的Activity實例。

  • Android 5.0 之前

    1. 同一應用內

    新生成的Activity,放入發送Intent者Task的棧頂。

      ```
    

TaskRecord{537925a8 #42 A com.zlq.lmt U 0}
Run #3: ActivityRecord{538314d0 com.zlq.lmt/.StandardActivity}
Run #2: ActivityRecord{5385a7c4 com.zlq.lmt/.StandardActivity}
Run #1: ActivityRecord{53760908 com.zlq.lmt/.MainActivity}


    2. 跨應用啟動
    
    新生成的Activity,放入發送Intent者Task的棧的棧頂(盡管他們屬于不同的程序,還是會放入調用者程序的棧內)。

      ```
TaskRecord{537df318 #52 A com.zlq.bbb U 0}
      Run #2: ActivityRecord{537a889c com.zlq.lmt/.StandardActivity}
      Run #1: ActivityRecord{537a4a5c com.zlq.bbb/.MainActivityB}

這時,我們打開任務管理器(最近任務按鈕)。會發現最近任務中現實的應用名為B應用,展示的界面卻是A應用的StandardActivity(因為其位于Task棧頂)。


跨應用啟動Standard Activity
跨應用啟動Standard Activity
  • Android 5.0 之后

    1. 同一應用內
      與Android 5.0之前保持一致
    2. 跨應用啟動
      經檢驗與Android5.0之前保持一致。Android6.0上也依然沒改變。參考資料深入講解Android中Activity launchMode內容或許有誤 。
  • 使用場景
    standard這種啟動模式適合于撰寫郵件Activity或者社交網絡消息發布Activity。如果你想為每一個intent創建一個Activity處理,那么就是用standard這種模式。

SingleTop

棧頂復用模式. SingleTop其實和Standard幾乎一樣,使用SingleTop的Activity也可以創建很多個實例。唯一不同的就是,如果調用的目標Activity已經位于調用者的Task的棧頂,則不創建新實例,而是使用當前的這個Activity實例,并調用這個實例的onNewIntent方法。

在singleTop這種模式下,我們需要處理應用這個模式的Activity的onCreate和onNewIntent兩個方法,確保邏輯正常。

  TaskRecord{537925a8 #42 A com.zlq.lmt U 0}
      Run #4: ActivityRecord{537e3114 com.zlq.lmt/.SingleTopActivity}
      Run #3: ActivityRecord{537dfe7c com.zlq.lmt/.StandardActivity}
      Run #2: ActivityRecord{53770808 com.zlq.lmt/.SingleTopActivity}
      Run #1: ActivityRecord{53760908 com.zlq.lmt/.MainActivity}

棧頂無法像Standard模式一樣,同事存在兩個,但是整個Task列表中間隔存在多個是可以的。

SingleTask

棧內復用模式.使用singleTask啟動模式的Activity在一個應用Task中只會存在一個實例。如果這個實例已經存在,intent就會通過onNewIntent傳遞到這個Activity,即多次調用不會創建新實例。否則新的Activity實例被創建。
情況包含以下幾種:

  • 同一應用內
    1. 任務棧不存在, 初次啟動SingleTask實例, 會創建任務棧和實例.
      Google在singleTask的文檔有這樣一段描述:

The system creates a new task and instantiates the activity at the root of the new task.

意思為 系統會創建一個新的Task,并創建Activity實例放入這個新的Task的底部。然而實際并非如此,在我的例子中,singleTask Activity并創建并放入了調用者所在的Task,而不是放入新的Task:
TaskRecord{5378ff88 #44 A com.zlq.lmt U 0}
      Run #3: ActivityRecord{537e2ff0 com.zlq.lmt/.SingleTaskActivity}
      Run #2: ActivityRecord{537de2ec com.zlq.lmt/.StandardActivity}
      Run #1: ActivityRecord{53788be0 com.zlq.lmt/.MainActivity}
怎樣才能符合文檔中所描述的情況呢?那就是 `taskAffinity`屬性和singleTask啟動模式配合使用.

```xml
    <activity
        android:name=".SingleTaskWithTaskAffinityActivity"
        android:label="SingleTaskWithTaskAffinityActivity"
        android:launchMode="singleTask"
        android:taskAffinity="com.zlq.new">
    </activity>
    
```

此時再執行同樣的操作,棧內的情況:

TaskRecord{53778428 #45 A com.zlq.new U 0}
      Run #3: ActivityRecord{537db410 com.zlq.lmt/.SingleTaskWithTaskAffinityActivity}
TaskRecord{5378ff88 #44 A com.zlq.lmt U 0}
      Run #2: ActivityRecord{53760908 com.zlq.lmt/.StandardActivity}
      Run #1: ActivityRecord{53788be0 com.zlq.lmt/.MainActivity}
其實,**把啟動模式設置為singleTask,framework在啟動該activity時只會把它標示為可在一個新任務中啟動,至于是否在一個新任務中啟動,還要受其他條件的限制。**使用`taskAffinity`屬性會指定新的Activity所屬棧,可與SingleTask配合使用, 對Standard模式無效.新任務棧是com.zlq.new.
  1. 任務棧存在, 初次啟動SingleTask實例, Task棧中不存在singleTask Activity的實例。那么就需要創建這個Activity的實例,并且將這個實例放入和調用者相同的Task中并位于棧頂。與Standard模式相同.
  2. 任務棧相同,如果singleTask Activity實例已然存在,再次啟動SingleTask實例, 那么在Activity回退棧中,所有位于該Activity上面的Activity實例都將被銷毀掉(銷毀過程會調用Activity生命周期回調),這樣使得singleTask Activity實例位于棧頂(具有clearTop的效果)。與此同時,Intent會通過onNewIntent傳遞到這個SingleTask Activity實例。 并清除其上面實例, 具有clearTop的效果.最終,singleTask Activity實例會位于棧頂。
  3. 任務棧不同, 再次啟動SingleTask實例, 會導致任務棧切換, 后臺置于前臺.
  • 跨應用之間:
    1. 任務棧不存在, 初次啟動SingleTask實例, 會創建一個新的任務棧,然后創建SingleTask Activity的實例,將其放入新的Task中。Task變化如下。
TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #0: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}

變為:

TaskRecord{5c70a93 #17 A=com.zlq.lmt U=0 sz=1}
        Run #1: ActivityRecord{4cd8b0f u0 com.zlq.lmt/.SingleTaskActivity t17}
TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #0: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}

最近任務變化:


此處輸入圖片的描述
此處輸入圖片的描述

變為:


此處輸入圖片的描述
此處輸入圖片的描述
  1. 任務棧存在, 初次啟動SingleTask實例, Task棧中不存在singleTask Activity的實例。
    如果singleTask Activity所在的應用進程存在,但是singleTask Activity實例不存在,那么從別的應用啟動這個Activity,新的Activity實例會被創建,并放入到所屬進程所在的Task中,并位于棧頂位置。
Running activities (most recent first):
TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #1: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}
TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=1}
        Run #0: ActivityRecord{f0eba63 u0 com.zlq.lmt/.MainActivity t18}

↓變為↓:

Running activities (most recent first):
    TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}
        Run #2: ActivityRecord{73d091c u0 com.zlq.lmt/.SingleTaskActivity t18}
    TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #1: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}
    TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}
        Run #0: ActivityRecord{f0eba63 u0 com.zlq.lmt/.MainActivity t18}
  1. 如果singleTask Activity實例存在,從其他程序被啟動,那么這個Activity所在的Task會被移到頂部,并且在這個Task中,位于singleTask Activity實例之上的所有Activity將會被正常銷毀掉。如果我們按返回鍵,那么我們首先會回退到這個Task中的其他Activity,直到當前Task的Activity回退棧為空時,才會返回到調用者的Task。
Running activities (most recent first):
      TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #4: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}
      TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=4}
        Run #3: ActivityRecord{fa7aae9 u0 com.zlq.lmt/.StandardActivity t18}
        Run #2: ActivityRecord{dfd9a3b u0 com.zlq.lmt/.StandardActivity t18}
        Run #1: ActivityRecord{660ce3c u0 com.zlq.lmt/.SingleTaskActivity t18}
        Run #0: ActivityRecord{f0eba63 u0 com.zlq.lmt/.MainActivity t18}

↓變為↓:

Running activities (most recent first):
      TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}
        Run #2: ActivityRecord{660ce3c u0 com.zlq.lmt/.SingleTaskActivity t18}
      TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #1: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}
      TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}
        Run #0: ActivityRecord{f0eba63 u0 com.zlq.lmt/.MainActivity t18}

可以看到,TASK ID為#18 的任務棧已經從原來的4個變為最終的1+1個。

  • 使用場景:
    該模式的使用場景多類似于郵件客戶端的收件箱或者社交應用的時間線Activity。上述兩種場景需要對應的Activity只保持一個實例即可,但是也要謹慎使用這種模式,因為它可以在用戶未感知的情況下銷毀掉其他Activity。

SingleInstance

單實例模式啟動時, 系統會為其創造一個單獨的任務棧, 以后每次使用, 都會使用這個單例, 直到其被銷毀, 屬于真正的單例模式.singleTask差不多,唯一不同的就是存放singleInstance Activity實例的Task只能存放一個該模式的Activity實例,不能有任何其他的Activity。
雖然是兩個task,但是在系統的任務管理器中,卻始終顯示一個,即位于頂部的Task中。

相關知識點

查看當前任務棧:

adb shell dumpsys activity | sed -n -e '/Stack #/p' -e '/Running activities/,/Run #0/p'

輸出的結果如:

Running activities (most recent first):
      TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}
        Run #2: ActivityRecord{660ce3c u0 com.zlq.lmt/.SingleTaskActivity t18}
      TaskRecord{5bf28 #16 A=com.zlq.bbb U=0 sz=1}
        Run #1: ActivityRecord{f4a1b15 u0 com.zlq.bbb/.MainActivityB t16}
      TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}
        Run #0: ActivityRecord{f0eba63 u0 com.zlq.lmt/.MainActivity t18}

TaskRecord{65dfdf0 #18 A=com.zlq.lmt U=0 sz=2}為例
以TaskRecord開頭的(如)為一組 TaskRecord記錄,#18為Task的ID,A=包名,sz為該Task的Activity數量。
以Run #開頭的為一個ActivityRecord記錄。其中也包含了包名、類名、TASK ID等信息。

其中以Task ID為一個任務棧的唯一標識,ID相同的TaskRecord屬于同一個任務棧(可以理解為同一應用)。

對startActivityForResult的影響:

startActivityForResult 不同于 startActivity, 在使用 startActivityForResult 時不管LaunchMode設置為哪種模式,都會在調用者Task棧中新建實例以正確地返回數據。在棧中的展現形式均與Standard相同(可生成多份連續的實例)。

  • SingleTop,當其使用startActivityForResult時表現和Standard啟動模式時完全相同
  • SingleTask,當不定義 taskAffinity 屬性時使用startActivityForResult和Standard啟動模式時表現完全相同。當定義了taskAffinity 屬性后,變現將和下面第3條表現一致。
  • SingleInstance,無法通過startActivity創建自己(無論當前所屬哪個棧)。startActivityForResult 隨意在當前棧(傳入者所在棧)新建實例。
 Running activities (most recent first):
      TaskRecord{ef9f20b #33 A=com.zlq.lmt U=0 sz=4}
        Run #3: ActivityRecord{34f60b1 u0 com.zlq.lmt/.SingleTaskActivity t33} *
        Run #2: ActivityRecord{914547d u0 com.zlq.lmt/.SingleTaskActivity t33} *
        Run #1: ActivityRecord{a1f3e09 u0 com.zlq.lmt/.SingleTaskActivity t33}
        Run #0: ActivityRecord{f7db1c u0 com.zlq.lmt/.MainActivity t33}

上面代碼片段中,加了*標的表示使用startActivity無法建立,是 使用startActivityForResult 建立的Activity。
由此可知, 因為startActivityForResult需要返回值, 會保留實例, 部分覆蓋單例效果.

注意: 4.x版本通過startActivityForResult啟動singleTask, 無法正常獲取返回值, 參考.
5.x以上版本修復此問題, 考慮兼容性, 不推薦使用startActivityForResult和singleTask.

Demo源碼

以上均為參考資料和自己實踐驗證所得結果。有描述不清楚的地方,大家可去下載我的代碼自行驗證各種情況:GitHub

參考資料鏈接

深入講解Android中Activity launchMode
分析 Activity 的啟動模式
Android中Activity四種啟動模式和taskAffinity屬性詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容