Activity 啓動模式和 taskAffinity 屬性詳解

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

任務和返回棧

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

Activity 啓動模式和 taskAffinity 屬性詳解

若是要查看 Activity Task棧的狀況,能夠在命令行用 adb 命令查看android

adb shell dumpsys activity activities

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

啓動模式

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

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

standard 默認模式

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

這裏經過簡單的代碼來驗證,先實現方法來打印 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 而後點擊兩下按鈕,日誌以下:debug

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 適合須要與程序分離開的頁面,例如鬧鈴的響鈴界面
相關文章
相關標籤/搜索