Android进阶之Activity四种启动模式和task相关

1 启动模式介绍

Activity为什么需要启动模式?在默认的情况下,当我们多次启动同一个Activity的时候,系统会创建多个实例并把他们一一放入任务栈中。但是发现一个问题:多次启动同一个Activity会创建多个实例,所以他提供了启动模式来修改系统的默认行为,目前有四种启动模式:standard、singleTop、singleTask、singleInstance。

1.1 standard–标准模式

(1)标准模式,也是系统的默认模式,每次启动一个Activity都会重新创建一个实例,无论是否这个实例已经存在。

(2)假设栈内是A1->A2,再打开A2,则栈内是A1->A2->A2,如下图:
在这里插入图片描述

1.2 singleTop–栈顶复用模式

(1)栈顶复用模式,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被调用,通过此方法的参数们可以取出当前请求的信息。如果新Activity已存在但不是在栈顶,那么新Activity则会重新创建。

(2)假设栈内的情况为ABCD,A位于栈底,D位于栈顶,再启动D,如果D的启动模式为singleTop,栈内的情况是ABCD。如果D的启动模式是standard,那么D会被重新创建,栈内的情况就是ABCDD。如下图:
在这里插入图片描述
(3)应用场景:适合接收消息后显示的页面,例如:QQ接收倒消息后弹出Activity,如果10条消息,总不能一次弹10个Activity,只要复用栈顶显示的页面。例如:某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次激活同一个新闻内容页面,返回时立刻返回前一个页面。

1.3 singleTask–栈内复用模式

1.3.1 栈内复用模式介绍

(1)栈内复用模式,这是一种单实例模式。当一个具有singleTask模式的Activity请求启动后,比如Activity A,首先去寻找是否存在A需要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放进栈中。如果存在A所需要的栈,就要看A是否在栈中有实例存在,如果实例存在,就会把A调到栈顶并调用它的onNewIntent方法,把A上面的Activity全部出栈;如果实例不存在,就创建A的实例并且把A压入栈中。

1.3.2 以singleTask模式请求启动分析

(1)假设任务栈S1中的情况为ABC,这时候D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例都不存在,所以系统会先创建任务栈S2,然后创建D的实例将其入栈到S2。
在这里插入图片描述
(2)假设任务栈S1中的情况为ABC,这时候D以singleTask模式请求启动,其所需的任务栈为S1,由于S1已经存在,所以系统会直接创建D的实例并将其引入到S1中。
在这里插入图片描述
(3)假设D所需要的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会被重新创建,系统会把D切换到栈顶并且调用其oNNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。
在这里插入图片描述

1.3.3 指出一种情况:将 singleTask 模式的 Activity 切换到栈顶,会导致在它之上的栈内 activity 出栈

(1)假设目前有两个任务栈,前台任务栈(默认包名的栈)是 A B,而后台任务栈(栈名:com.task)是 C D,假设 C D 的启动模式都是 singleTask,启动流程如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="contactsdome.adan.com.demo2">
    <application
        <activity android:name=".ActivityA">
        
        <activity android:name=".ActivityB"
            android:launchMode="singleTask"/>
            
        <activity android:name=".ActivityC"
            android:launchMode="singleTask"
            android:taskAffinity="com.task"/>
            
        <activity android:name=".ActivityD"
            android:launchMode="singleTask"
            android:taskAffinity="com.task"/>
    </application>
</manifest>

然后,A–>C,C–>D,D–>B,整个后退列表变成了 CDAB,

adb shell dumpsys activity,如图:
在这里插入图片描述
(2)现在 B 启动 D(对应后台栈顶),那么整个后台任务栈都会被切换到前台,这个时候整个后退列表变成了 ABCD,当用户按back键的时候,列表中的Activity会一一出栈,如图:
在这里插入图片描述
(3)如果 B 不是启动 D,而是 B 启动 C(对应后台栈底)后台任务栈 C 上面的 D 出栈,整个后退列表变成了ABC,如图:
在这里插入图片描述
(4)验证(3)的原理:将 singleTask 模式的 Activity 切换到栈顶,会导致在它之上的栈内 activity 出栈。启动流程如下

<manifest 
    package="contactsdome.adan.com.demo2">
    <application
        <activity android:name=".ActivityA">
       
        <activity android:name=".ActivityB"
            android:launchMode="singleTask"
            android:taskAffinity="com.task"/>
            
        <activity android:name=".ActivityC"
            android:launchMode="singleTask"
            android:taskAffinity="com.task"/>
    </application>
</manifest>

A(默认) 启动 B(com.task) 时,要为 B 重新创建一个任务栈,B 再启动 C(com.task),由于 C 所需要的任务栈已经被 B 给创建了,所以无需再创建新的任务栈,只是创建 C 的实例放进 B 所在的任务栈。

接着 C 再启动 A(标准模式),系统会为它创建一个新的实例并将它加入到启动的任务栈中,所以 A 会进入 C 所在的栈内并处于栈顶。这时候已经有两个任务栈了,接着 A 再启动 B(singleTask),B 需要回到任务栈的栈顶,由于栈的模式为 ‘先进先出’,B 想要回到栈顶,就只能是 CA 出栈。

所以按 back 键,B 就出栈了,然后这个任务栈就是空的,被系统回收了,就只能回到后台任务栈把 A 显示出来。注意这个 A 是后台任务栈的 A,不是 BC 栈的 A(BC上面的A已经出栈),接着再按back就回到了桌面。
在这里插入图片描述
(5)singleTask、TaskAffinity和allowTaskReparentiing结合

当TaskAffinity和allowTaskReparentiing结合的时候,这种情况比较复杂,会产生特殊的效果,当一个应用A启动了应用B的某一个Activity后,如果这个Activity会直接从应用A的任务栈转移到应用B的任务栈中。再具体点,比如现在有2个应用A和B,A启动了B的一个Activity C ,然后按Home键回到桌面,然后再单击B的桌面图标,这个时候并不是启动 B的启动Activity,而是重新显示了已经被应用A启动的Activity C,或者说C从A的任务栈转移到了B的任务栈中。

可以这么理解,由于A启动了C,这个时候C只能运行在A的任务栈中,但是C属于B应用,正常情况下,他的TaskAffinity值肯定不可能和A的任务栈相同(因为包名不同),所以,当B启动后,B会创建自己的任务栈,这个时候系统发现C原本所想要的任务栈已经被创建出来了,所以就把C从A的任务栈中转移过来。

1.3.4 应用场景

(1)应用场景:singleTask适合作为程序入口点,例如:浏览器的首页导航。多个应用程序启动浏览器的首页导航,只会启动一次,其它情况都会走onNewIntent,并且会清空主界面上面的其他页面。

1.4 singleInstance–单例模式

(1)singleInstance,这是一种加强的singleTask的模式,他除了具有singleTask的所有属性之外,还加强了一点,那就是具有此模式下的Activity只能单独的处于一个任务栈中。换句话说,比如Activity A是singleInstance模式,当A启动的时候,系统会为A创建一个新的任务栈,然后A独立在这个任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。

<manifest 
    package="contactsdome.adan.com.demo2">
    <application
        <activity android:name=".ActivityA">
       
        <activity android:name=".ActivityC"
            android:launchMode="singleInstance" />
            
        <activity android:name=".ActivityD" />
    </application>
</manifest>

然后,A–>C,C–>D,整个后退列表变成了CAD(C单独一个栈),返回是路径是DAC,adb shell dumpsys activity,如图:
在这里插入图片描述
在这里插入图片描述
(2)应用场景:闹铃的响铃界面。 你以前设置了一个闹铃:上午6点。在上午5点58分,你启动了闹铃设置界面,并按Home键回桌面;在上午5点59分时,你在微信和朋友聊天;在6点时,闹铃响了,并且弹出了一个对话框形式的Activity(AlarmAlertActivity) 提示你到6点了(这个 Activity 就是以SingleInstance加载模式打开的),你按返回键,回到的是微信的聊天界面,这是因为AlarmAlertActivity所在的Task的栈只有他一个元素, 因此退出之后这个Task的栈空了。如果是以SingleTask打开AlarmAlertActivity,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面。
这里写图片描述

3 taskAffinity(亲和力)

(1)TaskAffinity可以翻译成任务相关性,这个参数标示了一个Activity所需要的任务栈的名字,默认情况下,所有的Activity所需要的任务栈的名字为应用的包名。当然,我们可以为每个Activity都单独指定TaskAffinity,这个属性值必须必须不能和包名相同,否则就相当于没有指定,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配合使用,在其他状况下没有意义。

<activity 
	android:name=".ActivityD"
	android:launchMode="singleTask"
	android:taskAffinity="com.task"/>

(2)另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调为前台。当TaskAffinity和singleTask启动模式配对使用的时候,他是具有该模式Activity目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。

(3)affinity在什么场合应用呢?
①根据affinity重新为Activity选择任务栈,也可以与allowTaskReparenting属性配合工作
②启动一个Activity过程中Intent使用了FLAG_ACTIVITY_NEW_TASK标记,根据affinity查找或创建一个新的具有对应affinity的task
③在其他情况下没有意义。

4 Intent几种常见的启动标志(flags)

Activity指定启动模式有两种,第一种是通过manifest指定模式,第二种是在Intent中设置标志位为Activity指定启动模式。区别在于:

(1)优先级上标志位方式的优先级比manifest指定模式要高,同时存在,以标志位方式为准。

(2)限定范围不同,第一种不能直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法为Activity指定singleInstance模式。

4.1 FLAG_ACTIVITY_NEW_TASK,效果与"singleTask"启动模式相同(常用)

(1)当Intent对象包含这个标记时,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如下图:

<activity android:name=".flag.ActivityFlagA" />
<activity android:name=".flag.ActivityFlagB" />
<activity android:name=".flag.ActivityFlagC"
          android:taskAffinity="com.flag.test"/>
// A以标志位“NEW_TASK”启动B
Intent intent = new Intent(this, ActivityFlagB.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
startActivity(intent);

在这里插入图片描述
(2)如果找不到,则创建一个新的task,并将该task的taskAffinity设置为目标Activity的taskActivity,将目标Activity压入此task中,如下图:

// B再以标志位“NEW_TASK”启动C
Intent intent = new Intent(this, ActivityFlagC.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
startActivity(intent);

在这里插入图片描述

4.2 FLAG_ACTIVITY_CLEAR_TOP(常用)

(1)同一个任务栈中,已经启动了3个Activity:ABC。C以标志位“CLEAR_TOP”启动A,ABC都会出栈,再创建一个新的A,A重新执行onCreate

<activity android:name=".flag.ActivityFlagA" />  // A的启动模式是标准模式的
<activity android:name=".flag.ActivityFlagB" />
<activity android:name=".flag.ActivityFlagC" />
// 在ActivityFlagC
Intent intent = new Intent(this, ActivityFlagA.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
startActivity(intent);

在这里插入图片描述
在这里插入图片描述这里写图片描述
(2)如果不重新创建一个A,则A的Manifest.xml配置成android:launchMode=“singleTop” 或者 FLAG_ACTIVITY_SINGLE_TOP使用,则A不会出栈,A以上的BC出栈,然后A执行onNewIntent–>onReStart–>onStart

<activity android:name=".flag.ActivityFlagA"
	android:launchMode="singleTop" />            // A的启动模式是singleTop的
<activity android:name=".flag.ActivityFlagB" />
<activity android:name=".flag.ActivityFlagC" />
// 在ActivityFlagC
Intent intent = new Intent(this, ActivityFlagA.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);  
startActivity(intent);

这里写图片描述
(3)配合FLAG_ACTIVITY_NEW_TASK使用,和(1)效果一样,B/C会出栈,则A会销毁,再创建一个新的A,A重新执行onCreate

// 在ActivityFlagC
Intent intent = new Intent(this, ActivityFlagA.class);   
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); 
startActivity(intent);

(4)Android 关闭多个视图Intent.FLAG_ACTIVITY_CLEAR_TOP用法

(5)利用此原理退出整个程序:启动到A,在onNewIntent()中销毁A,
参考:采用FLAG_ACTIVITY_CLEAR_TOP退出整个程序(多activity)

4.3 FLAG_ACTIVITY_CLEAR_TASK(常用)

此Activity将变成一个新Task中新的最底端的Activity,成为根Activity,所有的之前此Activity实例都会被关闭,这个标识仅仅和FLAG_ACTIVITY_NEW_TASK联合起来才有效果,单独使用和标准模式的效果。

Intent intent = new Intent(this, ActivityFlagA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);

4.4 FLAG_ACTIVITY_SINGLE_TOP,效果与"singleTop"启动模式相同(常用)

使用singleTo启动Activity,使用效果与指定android:launchMode="singleTop"相同。

4.5 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

如果一个Intent中包含此属性,则它转向的那个Activity以及在那个Activity其上的所有Activity都会在task重置时被清除出task。当我们将一个后台的task重新回到前台时,系统会在特定情况下为这个动作附带一个FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,意味着必要时重置task,这时FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就会生效。
这里写图片描述
图片来源于:基础总结篇之三:Activity的task相关
这个标记对于应用存在分割点的情况会非常有用。比如我们在应用主界面要选择一个图片,然后我们启动了图片浏览界面,但是把这个应用从后台恢复到前台时,为了避免让用户感到困惑,我们希望用户仍然看到主界面,而不是图片浏览界面,这个时候我们就要在转到图片浏览界面时的Intent中加入此标记。

4.6 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

这个标记在以下情况下会生效:1.启动Activity时创建新的task来放置Activity实例;2.已存在的task被放置于前台。系统会根据affinity对指定的task进行重置操作,task会压入某些Activity实例或移除某些Activity实例。我们结合上面的CLEAR_WHEN_TASK_RESET可以加深理解。

4.7 FLAG_ACTIVITY_NO_HISTORY

使用NO_HISTOR启动Activity,当该Activity启动新的Activity后,新的Activity会消失,不会保留在栈中。例如:A–>B,B使用这种模式启动C,C再启动D,D在返回时会finish C,返回到B。经过测试:

(1)A–>B,B使用这种模式启动C,C–>D,栈中是A–>B–>C–>D,返回是:DBA。

(2)A使用这种模式启动B,B–>C,C–>D,栈中是A–>C–>D(离栈顶隔了一个实例,会清除前一个),返回是:DCA。

5 task相关属性

5.1 android:allowTaskReparenting(常用)

这个属性用来标记一个Activity实例在当前应用退居后台后,是否能从启动它的那个task移动到有共同affinity的task,“true”表示可以移动,“false”表示它必须呆在当前应用的task中,默认值为false。实例在5.1中。

5.2 android:alwaysRetainTaskState

这个属性用来标记应用的task是否保持原来的状态,“true”表示总是保持,“false”表示不能够保证,默认为“false”。此属性只对task的根Activity起作用,其他的Activity都会被忽略。

默认情况下,如果一个应用在后台呆的太久例如30分钟,用户从主选单再次选择该应用时,系统就会对该应用的task进行清理,除了根Activity,其他Activity都会被清除出栈,但是如果在根Activity中设置了此属性之后,用户再次启动应用时,仍然可以看到上一次操作的界面。

这个属性对于一些应用非常有用,例如Browser应用程序,有很多状态,比如打开很多的tab,用户不想丢失这些状态,使用这个属性就极为恰当。

5.3 android:clearTaskOnLaunch

这个属性用来标记是否从task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默认为“false”。同样,这个属性也只对根Activity起作用,其他的Activity都会被忽略。

如果设置了这个属性为“true”,每次用户重新启动这个应用时,都只会看到根Activity,task中的其他Activity都会被清除出栈。如果我们的应用中引用到了其他应用的Activity,这些Activity设置了allowTaskReparenting属性为“true”,则它们会被重新宿主到有共同affinity的task中。
这里写图片描述
图片来源于:基础总结篇之三:Activity的task相关

5.4 android:finishOnTaskLaunch

这个属性和android:allowReparenting属性相似,不同之处在于allowReparenting属性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch属性是销毁实例。如果这个属性和android:allowReparenting都设定为“true”,则这个属性胜出。

6 onNewIntent触发时机

6.1 位于栈顶:activity的launchMode为singleTop 、singleTask或者singleInstance

这里写图片描述

6.2 清理其他Activity,被onRestart:activity的launchMode为singleTask或者singleInstance

这里写图片描述

7 参考博客

Android基础:最易懂的Activity启动模式详解
  
基础总结篇之三:Activity的task相关

Activity的启动模式

Android activity onNewIntent触发时机

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值