Activity 啓動模式和 taskAffinity 屬性詳解

歡迎訪問我的個人博客 傳送門

任務和返回棧

應用通常包含多個 Activity ,每個 Activity 均應圍繞用戶可以執行的特定操作設計,並且能夠啓動其他 Activity,一個 Activity 可以啓動設備上其他應用中的 Activity,即使兩個 Activity 可能來自不同的應用,但是 Android 仍會將 Activity 保留在相同的任務中,以維護這種無縫的用戶體驗。這裏所說的任務就是指在執行特定作業時與用戶交互的一系列 Activity,這些 Activity 按照各自的打開順序排列在堆棧(即返回棧)中。返回棧以“後進先出”對象結構運行,如下圖

Activity 啓動模式和 taskAffinity 屬性詳解

如果要查看 Activity Task棧的情況,可以在命令行用 adb 命令查看

adb shell dumpsys activity activities

執行命令會出現很長一段詳細信息 找到 Running activities 即可查看,如下圖
Activity 啓動模式和 taskAffinity 屬性詳解

啓動模式

在瞭解了任務和返回棧後,我們來說說啓動模式,上圖的堆棧是比較常規的,如果我們一直啓動同一個 Activity 系統會重複創建多個實例,但這不是我們想要的結果。這時候爲了滿足我們的需求就需要使用 Android 提供的啓動模式來修改系統的默認行爲。目前有四種啓動模式:standard、singleTop、singleTask 和 singleInstance。在 AndroidManifest.xml 中配置即可,如下:

   <activity 
        android:name="com.will.testdemo.launchmode.A"
        android:launchMode="standard">
    </activity>

standard 默認模式

系統在啓動 Activity 的任務中創建 Activity 的新實例並向其傳送 Intent。Activity 可以多次實例化,不管這個實例是否已經存在,而每個實例均可屬於不同的任務,並且一個任務可以擁有多個實例。這種模式的 Activity 被創建時它的 onCreate、onStart 都會被調用。這是一種典型的多實例實現,一個任務棧中可以有多個實例,每個實例也可以屬於不同的任務棧。在這種模式下,誰啓動了這個 Activity,那麼這個 Activity 就運行在啓動它的那個 Activity 所在的棧中。

這裏通過簡單的代碼來驗證,先實現方法來打印 Activity 的生命週期調用過程和 Taskid

fun printTaskInfo(activity: Activity, methodName: String) {
    log("${activity.localClassName} $methodName taskId = ${activity.taskId}")
}

fun log(message: String, tag: String = "debugLog") {
    Log.i(tag, message)
}

/**
 * @param T 目標 Activity
 */
inline fun <reified T : Activity> Context.toActivity() {
    startActivity(Intent(this, T::class.java))
}

界面很簡單就一個按鈕,就不截圖了,Activity 代碼

class A : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_a)
        printTaskInfo(this,"onCreate")
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        printTaskInfo(this,"onNewIntent")
    }

    override fun onStart() {
        super.onStart()
        printTaskInfo(this,"onStart")
    }

    fun click(view: View?) {
        toActivity<A>()
    }
}

啓動 A 然後點擊兩下按鈕,日誌如下:

01-02 22:07:00.330 28281-28281/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 44
01-02 22:07:00.332 28281-28281/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 44
01-02 22:07:01.580 28281-28281/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 44
01-02 22:07:01.582 28281-28281/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 44
01-02 22:07:02.325 28281-28281/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 44
01-02 22:07:02.327 28281-28281/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 44

使用 adb 命令查看 Activity Task 棧,可以看出每啓動一次 A 都會創建一次實例,不管這個實例是否已經存在
Activity 啓動模式和 taskAffinity 屬性詳解

singleTop 棧頂複用模式

在這種模式下,如果當前任務的頂部已存在 Activity 的一個實例,則系統會通過調用該實例的 onNewIntent() 方法向其傳送 Intent,而不是創建 Activity 的新實例。Activity 可以多次實例化,而每個實例均可屬於不同的任務,並且一個任務可以擁有多個實例(但前提是位於返回棧頂部的 Activity 並不是 Activity 的現有實例)。這個 Activity 的 onCreate、onStart 不會被系統調用,因爲它並沒有發生改變。

image.png

這裏我們新建一個 Activity B ,調用 Activity 流程 :A - A - B - A

  <activity
       android:name="com.will.testdemo.launchmode.A"
       android:launchMode="singleTop">
   </activity>
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_a)
        printTaskInfo(this, "onCreate")
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        printTaskInfo(this, "onNewIntent")
    }

    override fun onStart() {
        super.onStart()
        printTaskInfo(this, "onStart")
    }

     fun click(view: View?) {
        when (view?.id) {
            R.id.bt_toA -> toActivity<A>()
            R.id.bt_toB -> toActivity<B>()
            else -> { }
        }
    }

打印日誌:

01-02 22:15:18.399 28530-28530/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 45
01-02 22:15:18.400 28530-28530/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 45
01-02 22:15:21.229 28530-28530/com.will.testdemo I/debugLog: launchmode.A onNewIntent taskId = 45
01-02 22:15:24.927 28530-28530/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 45
01-02 22:15:24.929 28530-28530/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 45
01-02 22:15:26.449 28530-28530/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 45
01-02 22:15:26.450 28530-28530/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 45

Activity Task 棧
Activity 啓動模式和 taskAffinity 屬性詳解

可以看出當 A 在當前棧頂的時候沒有創建新的實例,並調用 onNewIntent 方法,沒有調用 onCreate 和 onStart 方法

singleTask 棧內複用模式

這是一種單實例模式,在這種模式下,只要 Activity 在一個棧中存在,那麼多次啓動此 Activity 都不會重新創建實例,和 singleTop一樣,系統也會回調其 onNewIntent。當一個具有 singleTask 模式的Activity請求啓動後,比如 Activity A,系統首先會尋找是否存在 A 想要的任務棧,如果不存在,就重新創建一個任務棧,然後創建 A 的實例後把 A 放到棧中。如果存在 A 所需的任務棧,這時要看 A 是否在棧中有實例存在,如果有實例存在,那麼系統就會把 A 調到棧頂並調用它的 onNewIntent 方法,如果實例不存在,就創建 A 的實例並把 A 壓入棧中 。

image.png

關於上文中所說的想要的任務棧,指的是 taskAffinity 屬性,手動設置所需的任務棧,這個後面會具體介紹

調用 Activity 流程 依舊是:A - A - B - A
打印日誌:

01-02 22:25:59.608 24498-24498/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 54
01-02 22:25:59.611 24498-24498/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 54
01-02 22:26:02.844 24498-24498/com.will.testdemo I/debugLog: launchmode.A onNewIntent taskId = 54
01-02 22:26:05.753 24498-24498/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 54
01-02 22:26:05.758 24498-24498/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 54
01-02 22:26:07.040 24498-24498/com.will.testdemo I/debugLog: launchmode.A onNewIntent taskId = 54
01-02 22:26:07.047 24498-24498/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 54

Activity Task 棧
Activity 啓動模式和 taskAffinity 屬性詳解

納尼 Activity B 呢 怎麼不見了,這是什麼鬼操作,原來是 singleTask 默認有 clearTop 的效果,會導致棧內所有在它上面的 Activity 全部出棧,這點一定不要忽略了

singleInstance 單實例模式

與 singleTask 相同,只是系統不會將任何其他 Activity 啓動到包含實例的任務中。該 Activity 始終是其任務唯一僅有的成員;由此 Activity 啓動的任何 Activity 均在單獨的任務中打開。也就是有此種模式的 Activity 只能單獨地位於一個任務棧中

調用 Activity 流程 :A - B - B - A,這次把 B 的啓動模式設置爲 singleInstance

打印日誌:

01-02 22:41:59.069 305-305/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 57
01-02 22:41:59.071 305-305/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 57
01-02 22:42:00.280 305-305/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 58
01-02 22:42:00.283 305-305/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 58
01-02 22:42:02.340 305-305/com.will.testdemo I/debugLog: launchmode.B onNewIntent taskId = 58
01-02 22:42:03.658 305-305/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 57
01-02 22:42:03.659 305-305/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 57

Activity Task 棧
Activity 啓動模式和 taskAffinity 屬性詳解

結合打印日誌和 Activity Task 棧可以看出,有此種模式的 Activity 只能單獨地位於一個任務棧中,如果已經創建過,則調用 onNewIntent 方法 不會調用 onCreate 和 onStart

taskAffinity 屬性

taskAffinity,可以翻譯爲任務相關性。這個參數標識了一個 Activity 所需要的任務棧的名字,默認情況下,所有 Activity 所需的任務棧的名字爲應用的包名,當 Activity 設置了 taskAffinity 屬性,那麼這個 Activity 在被創建時就會運行在和 taskAffinity 名字相同的任務棧中,如果沒有,則新建 taskAffinity 指定的任務棧,並將 Activity 放入該棧中。另外,taskAffinity 屬性主要和 singleTask 或者 allowTaskReparenting 屬性配對使用,在其他情況下沒有意義。

與 singleTask 結合使用,調用 Activity 流程:A - B - B - A - B,設置 B 的啓動模式爲 singleTask ,並設置 taskAffinity

    <activity
            android:name="com.will.testdemo.launchmode.A">
        </activity>

     <activity
            android:name="com.will.testdemo.launchmode.B"
            android:launchMode="singleTask"
            android:taskAffinity="com.will.testdemo.task1">
        </activity>

打印日誌:

01-02 23:13:29.179 16793-16793/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 59
01-02 23:13:29.180 16793-16793/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 59
01-02 23:13:31.800 16793-16793/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 60
01-02 23:13:31.801 16793-16793/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 60
01-02 23:13:33.740 16793-16793/com.will.testdemo I/debugLog: launchmode.B onNewIntent taskId = 60
01-02 23:13:34.928 16793-16793/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 60
01-02 23:13:34.931 16793-16793/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 60
01-02 23:13:36.203 16793-16793/com.will.testdemo I/debugLog: launchmode.B onNewIntent taskId = 60
01-02 23:13:36.204 16793-16793/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 60

Activity Task 棧
Activity 啓動模式和 taskAffinity 屬性詳解

B 被創建時,因沒有 com.will.testdemo.task1 的任務棧,於是新建任務棧,並把 B 放入棧內。繼續創建 A,由於 A 沒有設置啓動模式,則放入 com.will.testdemo.task1 棧中。再一次啓動 B,因棧內有 B 實例,所以系統就把 B 調到棧頂,由於 singleTask 默認有 clearTop 的效果,導致棧內所有在它上面的 Activity 全部出棧,所以最後 com.will.testdemo.task1 棧內只有 B 一個實例

總結

上面廢話了那麼多,那麼這些啓動模式到底什麼時候使用呢,這裏列出部分使用場景以供參考。

launchMode 使用場景
singleTop 適合啓動同類型的 Activity,例如接收通知啓動的內容顯示頁面
singleTask 適合作爲程序入口
singleInstance 適合需要與程序分離開的頁面,例如鬧鈴的響鈴界面
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章