Android 启动模式和 taskAffinity 属性详解

任务和返回栈

应用通常包含多个 Activity ,每个 Activity 均应围绕用户可以执行的特定操作设计,并且能够启动其他 Activity,一个 Activity 可以启动设备上其他应用中的 Activity,即使两个 Activity 可能来自不同的应用,但是 Android 仍会将 Activity 保留在相同的任务中,以维护这种无缝的用户体验。这里所说的任务就是指在执行特定作业时与用户交互的一系列 Activity,这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。返回栈以“后进先出”对象结构运行,如下图

Android 启动模式和 taskAffinity 属性详解_第1张图片
image.png

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

adb shell dumpsys activity activities

执行命令会出现很长一段详细信息 找到 Running activities 即可查看,如下图

Android 启动模式和 taskAffinity 属性详解_第2张图片
image.png

启动模式

在了解了任务和返回栈后,我们来说说启动模式,上图的堆栈是比较常规的,如果我们一直启动同一个 Activity 系统会重复创建多个实例,但这不是我们想要的结果。这时候为了满足我们的需求就需要使用 Android 提供的启动模式来修改系统的默认行为。目前有四种启动模式:standard、singleTop、singleTask 和 singleInstance。在 AndroidManifest.xml 中配置即可,如下:

   
    

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  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 然后点击两下按钮,日志如下:

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 都会创建一次实例,不管这个实例是否已经存在


image.png

singleTop 栈顶复用模式

在这种模式下,如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。这个 Activity 的 onCreate、onStart 不会被系统调用,因为它并没有发生改变。

Android 启动模式和 taskAffinity 属性详解_第4张图片
image.png

这里我们新建一个 Activity B ,调用 Activity 流程 :A -> A -> B -> A

  
   
   
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()
            R.id.bt_toB -> toActivity()
            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 栈


image.png

可以看出当 A 在当前栈顶的时候没有创建新的实例,并调用 onNewIntent 方法,没有调用 onCreate 和 onStart 方法

singleTask 栈内复用模式

这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例,和 singleTop一样,系统也会回调其 onNewIntent。当一个具有 singleTask 模式的Activity请求启动后,比如 Activity A,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建 A 的实例后把 A 放到栈中。如果存在 A 所需的任务栈,这时要看 A 是否在栈中有实例存在,如果有实例存在,那么系统就会把 A 调到栈顶并调用它的 onNewIntent 方法,如果实例不存在,就创建 A 的实例并把 A 压入栈中 。

Android 启动模式和 taskAffinity 属性详解_第6张图片
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 栈


image.png

纳尼 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 栈


Android 启动模式和 taskAffinity 属性详解_第7张图片
image.png

结合打印日志和 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

    
        
        
     
        

打印日志:

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 栈


Android 启动模式和 taskAffinity 属性详解_第8张图片
image.png

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 适合需要与程序分离开的页面,例如闹铃的响铃界面

你可能感兴趣的:(Android 启动模式和 taskAffinity 属性详解)