Android Activity的任务栈 & 四种加载模式

本文深入探讨Android中的任务栈概念,解释ActivityManagerService如何管理任务栈,并详细阐述Activity的四种启动模式:Standard、SingleTop、SingleTask和SingleInstance。通过实例解析Activity的目标任务栈选择逻辑,同时补充了Activity启动时的常用标记位及其作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#.首先,四种加载模式的简单概括:
1.Standard(标准模式):每次加载,都会重新创建一个目标Activity实例,并放在栈顶。
2.SingleTop(单顶模式):
        若栈顶已经存在目标Activity实例,则复用;否则,创建新的实例,放在栈顶。
3.SingleTask(单栈模式):
        若任务栈中已存在目标Activity实例,则清空其上面的Activity,复用该Activity实例;否则,创建新的实例,放在栈顶。
4.SingleInstance(单例模式):整个系统中只存在一个目标Activity实例,它独自占一个任务栈。
        加载时,若目标Activity实例已存在,则复用该Activity实例;否则,创建新的任务栈,创建新的目标实例放在该任务栈中。 

##1.任务栈介绍

    在Android中,任何Activity都被放在某一个任务栈中,Android通过任务栈来管理Activity的启动和关闭次序。例如:在关闭当前Activity后,系统可以根据任务栈中的记录来显示前一个Activity;通过启动模式和任务栈,可以判断是复用旧的Activity实例还是创建新的实例。

    同一个任务栈中可以包含不同APP的Activity,任务栈更像是从用户使用角度来组织管理Activity的一种方式,从用户角度来看,他按顺序打开一堆APP的页面,这些页面互相跳转,他点back键返回,就回到上一个页面,用户并不关心这些页面在底层到底属于哪些APP。

    虽然同一个任务栈中可以包含不同APP的Activity,但是这些Activity仍然运行在各自APP对应的进程中。Activity运行在哪个进程是一个维度,任务栈是另一个维度,任务栈主要针对的是这些Activity的使用流程顺序。 

    用户最初是从桌面(启动器页面)开始,点击启动某个应用,启动了其对应任务栈,然后各种启动、关闭、切换操作,实质上对应的是各种任务栈以及其中Activity的创建、销毁和切换。点击Home键时,切换到桌面,当前Activity切换到后台,并未被销毁。

    从桌面打开一个应用时,会开启新的任务栈,默认与当前包名相同,把APP首个打开的Activity放入其中。

    当从一个App使用Standard或SingleTop模式调用另一个APP的Activity时,并不会新建任务栈,而是会把Activity放入当前任务栈。

    每当点击“Home”键时,就会切换到桌面Activity(启动器launcher Activity),这是系统提供的一个Activity,它独立占据着一个任务栈,效果比较像像单例模式。每次从桌面启动App都会打开新的任务栈,每次点击Home键都会跳转回桌面Activity,而原来的前台任务栈切换到后台。

##2.在ActivityManagerService中对应的管理机制

    在Android中,实际是由ActivityManagerService(AMS)来实现和管理任务栈相关功能,它的真实实现逻辑在native层的。

    

     每个Activity在AMS中都对应一个ActivityRecord,ActivityRecord就像是Activity的一个映像,记录着该Activity的信息,例如: 所在进程名称,App包名,所在任务栈的taskAffinity等。

     一个TaskRecord对应一个任务栈,管理一组ActivityRecord,它是栈式结构,后进先出。AMS中记录着所有TaskRecord的最近使用顺序,而TaskRecord中又记录着Activity的入栈顺序。

    当前正在显示的Activity所在的任务栈是前台任务栈,其它都是后台任务栈。默认情况下,每次点击back键,此刻的前台任务栈会弹出栈顶Activity,恢复显示下面的Activity,当一个任务栈为空时,就会被系统回收。系统按照最近使用顺序,将前一个TaskRecord对应的任务栈切换到前台。切换任务栈前后台时,会更改这些TaskRecord的最近使用顺序,从而影响Activity回退时的顺序。

*补充:AMS中任务栈相应的数据结构说明:

    AMS中ActivityStackSupervisor管理多个ActivityStack,一个ActivityStack管理多个任务栈(TaskRecord),一个任务栈管理多个Activity的信息(ActivityRecord)。

    其中ActivityStack名字有欺骗性,它并不是栈式结构的,内部是用列表来组织TaskRecord,当任务栈前后台切换时,列表中的顺序可能发生变化。APP与ActivityStack之间并没有确定的对应关系,有可能一对多,也有可能多对一,不同的系统版本上不同。

可以用adb shell dumpsys activity activities命令,来查看一个调试中的Android手机中的当前任务栈信息。

##3.Activity的四种启动模式:

    通过Activity的启动模式,可以充分地复用Activity实例。

1.标准模式(Standard): 

    每次都重新创建一个实例,放到目标任务栈的栈顶;

2.单顶模式(SingleTop):

    若目标任务栈的栈顶已经是该Activity的实例A,则直接跳转到A,不再创建新的实例。跳转到A时,会触发onNewIntent()回调方法。

    即,当前任务栈的顶部只会有一个该Activity的实例。

3.单栈模式(SingleTask):

    若目标任务栈中已存在该Activity的实例A,则直接跳转到A,此过程中任务栈中A上面的Activity都会出栈,最后A位于栈顶,同样,会触发A的onNewIntent()回调方法。若当前任务栈中没有A的实例,则会新创建一个任务栈,放在栈顶。

    但特殊情况是,若在AndroidManifest.xml中通过为Activity设置taskAffinity属性指定了目标任务栈,且与当前任务栈不同,则新创建的实例会放到目标任务栈中。

    即,目标任务栈中只会有一个当前Activity的实例。

4.单例模式(SingleInstance):

    若系统中已经存在该Activity的实例A,则直接跳转到A,触发其onNewIntent()方法;否则创建该Activity的实例B,放到一个单独的新任务栈中,并且跳转到实例B。

    即,整个系统中只会有一个当前Activity的实例,它位于一个单独的任务栈中。

  (新的任务栈中只会包含B一个Activity,当从B以Standard或SingleTop启动别的Activity实例C时,也不会把新的Activity放入该任务栈的栈顶,而是会新创建一个任务栈把C放入其中。)

##4.Activity的目标任务栈:

    Activity会被放入哪个任务栈?

    每个任务栈都有对应的taskAffinity值,可以翻译为任务相关性,该值等于栈底Activity的taskAffinity值。

    Activity在AndroidManifest.xml中可以配置taskAffinity属性值,若不配置,则默认与App包名相同。这个taskAffinity值决定了Activity的意向放入的任务栈,但并非一定会放入taskAffinity值相同的任务栈,还要根据具体的情况来看:

1.当Activity以SingleInstance模式启动时,会新建一个任务栈,并将Activity放入其中。

(通过adb shell dumpsys activity activities指令查看任务栈结构发现,这个任务栈的taskAffinity与该Activity的taskAffinity相同,当然有可能就是包名本身。但因为该栈只能放这一个Activity,所以只会有一个栈的taskAffinity与App包名相同能放多个Activity。)    

排除掉情况1,以下都是非SingleInstance模式启动时的情况:

2. 当Activity以SingleTask或带有FLAG_ACTIVITY_NEW_TASK标记启动时,会被放入taskAffinity值相同的任务栈。

(系统会根据taskAffinity值来寻找有没有可放入的目标任务栈,没有该任务栈就创建新的。)   

3.当从桌面首次打开APP时,若App中的Activity之前没有被其它APP调用打开过,也会被放入taskAffinity值相同的任务栈。

   当调用方Activity是以SingleInstance模式启动时,因为当前任务栈只会放着唯一的Activity,故新启动的Activity 也会被放入taskAffinity值相同的任务栈。(实际验证,例如:当以默认包名为taskAffinity,会被放到默认包名任务栈中。)

(相当去从调用方Activity所在栈来划分的情况)

    

4.其它情况下,Activity会被放入调用方Activity当前所在的任务栈。例如从App A调用App B的Activity时。

5.特殊情况,android:allowTaskReparenting=“true”时,该属性就像其字面意思一样,允许Activity回归其本来意向的任务栈。

主要使用场景:App A调用App B的Activity C,若非SingleInstance模式启动C,则C会放入App A的任务栈。如果Activity C配置了allowTaskReparenting=“true”,此时回到桌面,点击App B的图标启动B的任务栈时,系统会将C从App A的任务栈移动到App B的任务栈,此时会显示Activity C,而非App B原本应启动的初始页面Activity。

<activity

        ... ... ... ...

        android:taskAffinity=“per.sample.task”        //如果设置该参数,则会在指定的任务栈中启动。

                                                            默认不用设置,运行在包名对应的任务栈中。

                                                     必须配合singleTask、FALG_ACTIVITY_NEW_TASK、allowTaskReparenting

                                                     至少之一使用才有效   

        android:allowTaskReparenting=“true”        

        ... ... ... ...

</activity>    

##5.补充:

1.通过Java代码和AndroidManfest.xml都可以设置一个Activity启动时的启动模式。若二者同时存在,则在执行中第二种方式的优先级更高。

    但注意,java代码模式无法设置SingleTask、SingleInstance模式,xml方式无法设置出FLAG_ACTIVITY_CLEAR_TOP等一些标记位对等的效果。

    xml中设置方法:android:launchMode=“xxx"

    Java中设置方法:Intent.setFlags(xxx|xxx);

    个人的理解:

    AndroidManfest.xml静态设置的参数,与Activity绑定,表示每次都以什么方式启动;而java代码中设置Intent标记位参数只跟“本次启动操作”绑定。

    所以xml可以设置任意启动模式,而java方式无法设置SingleTask、SingleInstance模式,因为xml方式设置成任何模式,每次启动都是自洽的; 而java方式同一个Activity的不同实例启动时,所用的启动模式有可能不同,使用SingleInstance、SingleTask模式启动时可能与其它模式启动时产生冲突。

2.SingleTop模式只是去检测栈顶是否有相应实例,并做相应操作。

3.当启动Activity时,若创建了新的A实例,则触发onCreate()->onStart()流程;若未创建新的A实例,而是SingleTop、SingleTask、SingleInstance等模式,则触发onNewIntent()->onStart()流程。 

4.java代码中设置FLAG_ACTIVITY_NEW_TASK,作用并不等同xml中设置android:lanchMode=singleTask,实际运行的时候发现单独设置该标记位,什么特殊作用都没发生。

    当设置FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP时,作用才类似xml中设置singleTask。不过这种方式不能保证一个栈中只有一个目标示例,它只是在栈中找到最上面的那个实例,并把上面的其它Activity实例都弹出。

    在xml中设置android:lanchMode=singleTask, 才能保证栈中单实例,因为这样每次启动该Activity时,都是以singleTask方式启动的。

5.点击back键回退,关闭Activity A恢复Activity B时,生命周期流程为:

A:onPause() -> B:onRestart() -> B: onStart() -> B: onResume() -> A : onStop()-> A : onDestroy()

(根据前面所讲可以很容易理解)

##6.Activity启动时常用标记位(Flags)

    启动Activity时,在Intent中可添加的标记位种类很多。

常用标记位:

1.FLAG_ACTIVITY_SINGLE_TOP:

    指定为SingleTop模式,该标记位的效果与单顶模式相同。

2.FALG_ACTIVITY_NEW_TASK:

    一般配合android:taskAffinity属性一起用,把Activity放入taskAffinity对应的任务栈中,若不存在对应任务栈,则创建新的任务栈。

    单独使用FALG_ACTIVITY_NEW_TASK,不会有什么效果。

3.FLAG_ACTIVITY_CLEAR_TOP:

    若任务栈中尚无Activity A的实例,不会有特别处理,只是放到顶部;

    若任务栈中已存在Activity A的实例,当A不位于栈顶时,弹出A所属任务栈中A之上的所有Activity,触发onNewIntent();    

            当A位于栈顶时,分两种情况:若以默认方式启动A,则弹出A,创建一个新的实例放入栈顶,所以会触发onCreate();若附加FLAG_ACTIVITY_SINGLE_TOP标记位来启动A(可理解为以单顶模式启动A),则A不弹出,触发onNewIntent()方法。

    另外,SingleTask的Activity实例,默认是带有FLAG_ACTIVITY_CLEAR_TOP标记位的。

4.FLAG_ACTIVITY_EXCLUDE_FROM_RECENT:

    带有该标记位的Activity,不会显示在历史记录列表中(就是点击底部菜单键弹出的那一堆选择列表)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值