Activity为什么需要启动模式?在默认的情况下,当我们多次启动同一个Activity的时候,系统会创建多个实例并把他们一一放入任务栈中。但是发现一个问题:多次启动同一个Activity会创建多个实例,所以他提供了启动模式来修改系统的默认行为,目前有四种启动模式:standard、singleTop、singleTask、singleInstance。
(1)标准模式,也是系统的默认模式,每次启动一个Activity都会重新创建一个实例,无论是否这个实例已经存在。
(2)假设栈内是A1->A2,再打开A2,则栈内是A1->A2->A2,如下图:
(1)栈顶复用模式,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被调用,通过此方法的参数们可以取出当前请求的信息。如果新Activity已存在但不是在栈顶,那么新Activity则会重新创建。
(2)假设栈内的情况为ABCD,A位于栈底,D位于栈顶,再启动D,如果D的启动模式为singleTop,栈内的情况是ABCD。如果D的启动模式是standard,那么D会被重新创建,栈内的情况就是ABCDD。如下图:
(3)应用场景:singleTop适合接收消息后显示的页面,例如:QQ接收倒消息后弹出Activity,如果10条消息,总不能一次弹10个Activity,只要复用栈顶显示的页面。例如:某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次激活同一个新闻内容页面,返回时立刻返回前一个页面。
(1)栈内复用模式,这是一种单实例模式。当一个具有singleTask模式的Activity请求启动后,比如Activity A,首先去寻找是否存在A需要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放进栈中。如果存在A所需要的栈,就要看A是否在栈中有实例存在,如果实例存在,就会把A调到栈顶并调用它的onNewIntent方法,把A上面的Activity全部出栈;如果实例不存在,就创建A的实例并且把A压入栈中。
(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)假设目前有两个任务栈,前台任务栈(默认包名的栈)的情况为AB,而后台任务栈(栈名:com.task)的情况是CD,这里假设CD的启动模式都是singleTask,启动流程如下:
然后,A–>C,C–>D,D–>B,整个后退列表变成了CDAB,adb shell dumpsys activity,如图:
(2)现在B–>D(对应后台栈顶),那么整个后台任务站都会被切换到前台,这个时候整个后退列表变成了ABCD,当用户按back键的时候,列表中的Activity会一一出栈,如图:
(3)如果B不是启动D,而是启动C(对应后台栈底),后台任务栈C上面的D出栈,整个后退列表变成了ABC,如图:
(4)验证(3)的原理,singtleTask模式的Activity切换到栈顶会到导致在他之上的栈内activity出栈,启动流程如下:
A启动B的时候,要为B重新创建一个任务栈,B再启动C,由于C所需要的任务栈已经被B给创建了,所以无需再创建新的任务栈,只是创建C的实例放进任务栈。接着C再启动A,A是标准模式,所以系统会为他创建一个新的实例并将他加入到启动的任务栈中,由于是C启动了A,所以A会进入C的栈内并处于栈顶,这个时候已经有两个任务栈了,接着A再启动B,由于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)应用场景:singleTask适合作为程序入口点,例如:浏览器的首页导航。多个应用程序启动浏览器的首页导航,只会启动一次,其它情况都会走onNewIntent,并且会清空主界面上面的其他页面。
(1)singleInstance,这是一种加强的singleTask的模式,他除了具有singleTask的所有属性之外,还加强了一点,那就是具有此模式下的Activity只能单独的处于一个任务栈中。换句话说,比如Activity A是singleInstance模式,当A启动的时候,系统会为A创建一个新的任务栈,然后A独立在这个任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。
然后,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,那么当闹铃响了的时候,按返回键应该进入闹铃设置界面。
(1)TaskAffinity可以翻译成任务相关性,这个参数标示了一个Activity所需要的任务栈的名字,默认情况下,所有的Activity所需要的任务栈的名字为应用的包名。当然,我们可以为每个Activity都单独指定TaskAffinity,这个属性值必须必须不能和包名相同,否则就相当于没有指定,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配合使用,在其他状况下没有意义。
(2)另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调为前台。当TaskAffinity和singleTask启动模式配对使用的时候,他是具有该模式Activity目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。
(3)affinity在什么场合应用呢?
①根据affinity重新为Activity选择任务栈,也可以与allowTaskReparenting属性配合工作;
②启动一个Activity过程中Intent使用了FLAG_ACTIVITY_NEW_TASK标记,根据affinity查找或创建一个新的具有对应affinity的task;
③在其他情况下没有意义。
Activity指定启动模式有两种,第一种是通过manifest指定模式,第二种是在Intent中设置标志位为Activity指定启动模式。区别在于:
(1)优先级上标志位方式的优先级比manifest指定模式要高,同时存在,以标志位方式为准。
(2)限定范围不同,第一种不能直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法为Activity指定singleInstance模式。
(1)当Intent对象包含这个标记时,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如下图:
// 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);
(1)同一个任务栈中,已经启动了3个Activity:ABC。C以标志位“CLEAR_TOP”启动A,ABC都会出栈,再创建一个新的A,A重新执行onCreate。
// A的启动模式是标准模式的
// 在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。
// A的启动模式是singleTop的
// 在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)。
此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);
使用singleTo启动Activity,使用效果与指定android:launchMode="singleTop"相同。
如果一个Intent中包含此属性,则它转向的那个Activity以及在那个Activity其上的所有Activity都会在task重置时被清除出task。当我们将一个后台的task重新回到前台时,系统会在特定情况下为这个动作附带一个FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,意味着必要时重置task,这时FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就会生效。
图片来源于:基础总结篇之三:Activity的task相关
这个标记对于应用存在分割点的情况会非常有用。比如我们在应用主界面要选择一个图片,然后我们启动了图片浏览界面,但是把这个应用从后台恢复到前台时,为了避免让用户感到困惑,我们希望用户仍然看到主界面,而不是图片浏览界面,这个时候我们就要在转到图片浏览界面时的Intent中加入此标记。
这个标记在以下情况下会生效:1.启动Activity时创建新的task来放置Activity实例;2.已存在的task被放置于前台。系统会根据affinity对指定的task进行重置操作,task会压入某些Activity实例或移除某些Activity实例。我们结合上面的CLEAR_WHEN_TASK_RESET可以加深理解。
使用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。
这个属性用来标记一个Activity实例在当前应用退居后台后,是否能从启动它的那个task移动到有共同affinity的task,“true”表示可以移动,“false”表示它必须呆在当前应用的task中,默认值为false。实例在5.1中。
这个属性用来标记应用的task是否保持原来的状态,“true”表示总是保持,“false”表示不能够保证,默认为“false”。此属性只对task的根Activity起作用,其他的Activity都会被忽略。
默认情况下,如果一个应用在后台呆的太久例如30分钟,用户从主选单再次选择该应用时,系统就会对该应用的task进行清理,除了根Activity,其他Activity都会被清除出栈,但是如果在根Activity中设置了此属性之后,用户再次启动应用时,仍然可以看到上一次操作的界面。
这个属性对于一些应用非常有用,例如Browser应用程序,有很多状态,比如打开很多的tab,用户不想丢失这些状态,使用这个属性就极为恰当。
这个属性用来标记是否从task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默认为“false”。同样,这个属性也只对根Activity起作用,其他的Activity都会被忽略。
如果设置了这个属性为“true”,每次用户重新启动这个应用时,都只会看到根Activity,task中的其他Activity都会被清除出栈。如果我们的应用中引用到了其他应用的Activity,这些Activity设置了allowTaskReparenting属性为“true”,则它们会被重新宿主到有共同affinity的task中。
图片来源于:基础总结篇之三:Activity的task相关
这个属性和android:allowReparenting属性相似,不同之处在于allowReparenting属性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch属性是销毁实例。如果这个属性和android:allowReparenting都设定为“true”,则这个属性胜出。
Android基础:最易懂的Activity启动模式详解
基础总结篇之三:Activity的task相关
Activity的启动模式
Android activity onNewIntent触发时机