Activity 启动模式以及常见的启动Flag
Posted Jason_Lee155
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Activity 启动模式以及常见的启动Flag相关的知识,希望对你有一定的参考价值。
提前致谢:感谢广大互联网大佬记录知识借鉴。
一、Activity的4种启动模式:
1、android:launchMode=”standard” 标准启动模式。这种启动模式也是Activity默认的,一个栈中可以有多个相同的Activity。不会考虑是否栈中是否有相同的Activity。比如设置A为standard模式,那么可以运行A-B-A-A-A;
2、android:launchMode=”singleTop” 单一栈顶启动模式, 又叫栈顶复用模式,栈顶只能存在一个相同的Activity。比如你栈顶是MainActivity.java,你想启动新的MainACtivity.java是无法实现的。但是栈中可以存在多个MainActivity.java实例。 比如设置A为singleTop模式,运行 A-B-A 这里栈中可以同时存在了两个A,但是如果A已经在栈顶了就不能在A之上再启动一个A,这时候虽然系统不会调用A的onCreate(),但会调用onNewIntent(),即位于栈顶的activity被复用。
当一个Activity已经在栈顶,但依然有可能启动它,而你又不想产生新的Activity实例,此时就可以用singleTop模式。
3、android:launchMode=”singleTask” 单一栈启动模式,又叫栈内复用模式,当使用这种启动模式的时候,栈中只能存在一个相同Activity的实例。比如设置A为singleTask模式,那么执行 A-B-C-D ,此时D在栈顶,你想启动一个A,此时会直接启动在栈底层的A,而不会去新建一个A,并且此时A已经成为了栈顶了,所以B-C和D都是被移出栈,也就是被销毁了,同理系统不会调用A的onCreate(),但会调用onNewIntent(),即栈内的activity被复用。
singleTask模式和前面两种模式的最大区别就是singleTask模式是任务内单例的,所以我们是否设定Activity为singleTask模式,就是看我们activity是否需要单例,例如你的某个Activity里面有一个列表,如果有多个实例,有可能导致用户看到的列表不一致,有的Activity需要经常启动,如果每次都创建实例,会导致占用资源过多,这些情况都可以使用singleTask模式(例如Launcher应用的activity就是singleTask模式的),但启动singleTask模式的Activity会导致任务栈内它上面的Activity被销毁,有可能会影响用户体验,使用时要注意。
4、android:launchMode=”singleInstance” 单例启动模式,singleInstance模式也是单例的,但和singleTask不同,singleTask只是任务栈内单例,系统里是可以有多个singleTask Activity实例的,而singleInstance Activity在整个系统里只有一个实例,启动一singleInstanceActivity时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity。比如设置B为这种模式, 那么当依次启动A-B-C-D(此时D在栈顶),此时原始的栈中就有A、C、D这三个Activity,而B在一个新的栈中。此时当你按back键,你会发现是这样子的,D-C-A–B,这是因为DCA在先创建的(也就是主栈)中,所以当主栈中的Activity全部都移除栈外后, 才会轮到次栈,这个次栈中只有一个B。
把Activity独立于一个栈中,是为了别的程序访问此Activity,可以方便多个应用程序共享这个栈中的Activity。SingleInstance模式并不常用,如果我们把一个Activity设置为singleInstance模式,你会发现它启动时会慢一些,切换效果不好,影响用户体验。它往往用于多个应用之间,例如一个电视launcher里的Activity,通过遥控器某个键在任何情况可以启动,这个Activity就可以设置为singleInstance模式,当在某应用中按键启动这个Activity,处理完后按返回键,就会回到之前启动它的应用,不影响用户体验。
二、几种常见的启动Activity的FLAG
(1) FLAG_ACTIVITY_NEW_TASK
设置此状态,记住以下原则,首先会查找是否存在和被启动的Activity具有相同的亲和性的任务栈(即taskAffinity,注意同一个应用程序中的activity的亲和性一样,所以下面的a情况会在同一个栈中),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的activity顺序不变,如果没有,则新建一个栈来存放被启动的activity。
a. 前提: Activity A和Activity B在同一个应用中.
操作: Activity A启动时开僻Task堆栈(堆栈状态: A), 在Activity A中启动Activity B, 启动Activity B的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK, Activity B被压入Activity A所在堆栈(堆栈状态: A-B).原因: 默认情况下同一个应用中的所有Activity拥有相同的关系(taskAffinity).
b. 前提: Activity A在名称为"TaskOne应用"的应用中,
Activity C和Activity D在名称为"TaskTwo应用"的应用中.操作1: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C, 启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,android系统会为Activity C开僻一个新的Task, 命名为TaskB(TaskB堆栈状态: C), 长按任务键, 然后选择TaskA, Activity A回到前台, 再次启动Activity C(两种情况1.从桌面启动;2.从Activity A启动,两种情况一样), 这时TaskB回到前台, Activity C显示, 供用户使用, 即:包含FLAG_ACTIVITY_NEW_TASK的Intent启动Activity的Task正在运行, 则不会为该Activity创建新的Task,而是将原有的Task返回到前台显示.
操作2: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A), 在Activity A中启动Activity C,启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK, Android系统会为Activity C开僻一个新的Task, 命名为TaskB(TaskB堆栈状态: C), 在Activity C中启动Activity D(TaskB的状态: CD) 长按Home键, 选择TaskA, Activity A回到前台, 再次启动Activity C(从桌面或者ActivityA启动,也是一样的),这时TaskB回到前台, Activity D显示,供用户使用.说明了在此种情况下设置FLAG_ACTIVITY_NEW_TASK后,会先查找是不是有Activity C存在的栈,根据亲和性(taskAffinity),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的顺序不变
(2) FLAG_ACTIVITY_CLEAR_TOP:
从字面意思上看是要将栈中所启动activity之上的所有activity清除,通过此flag启动时还是按照默认在本栈上启动,清除上面的activity,但是即使所启动的activity已存在,仍会销毁重新创建,如果结合single top 的flag则不会重新创建。
前提: Activity A, Activity B, Activity C和Activity D在同一个应用中.
操作: Activity A启动开僻Task堆栈(堆栈状态: A), 在Activity A中启动Activity B(堆栈状态: AB), 在Activity B中启动Activity C(堆栈状态: ABC), 在Activity C中启动Activity D(堆栈状态: ABCD), 在Activity D中启动Activity B,启动Activity B的Intent的Flag设置为FLAG_ACTIVITY_CLEAR_TOP, (堆栈状态: AB).
(3) FLAG_ACTIVITY_CLEAR_TASK:
必须配合new task使用,表示新启动的activity作为新栈的根activity。
(4) FLAG_ACTIVITY_BROUGHT_TO_FRONT:
前提: Activity A在名称为"TaskOne应用"的应用中, Activity C和Activity D在名称为"TaskTwo应用"的应用中.
操作: 在Launcher中单击"TaskOne应用"图标, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A), 在Activity A中启动Activity C,启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK, Android系统会为Activity C开僻一个新的Task, 命名为TaskB(TaskB堆栈状态: C), 在Activity C中启动Activity D(TaskB的堆栈状态: CD), 长按Home键, 选择TaskA, Activity A回到前台, 在Activity A中再次启动Activity C, 在启动Activity C的Intent中设置Flag为FLAG_ACTIVITY_BROUGHT_TO_FRONT, TaskB回到前台, Activity C显示, (TaskB的堆栈状态: C).
(5) FLAG_ACTIVITY_MULTIPLE_TASK:
与FLAG_ACTIVITY_NEW_TASK结合使用, 首先在Intent中设置FLAG_ACTIVITY_NEW_TASK, 打开Activity,则启动一个新Task, 接着在Intent中设置FLAG_ACTIVITY_MULTIPLE_TASK, 再次打开同一个Activity,则还会新启动一个Task.
(6) FLAG_ACTIVITY_SINGLE_TOP:
当前Task堆栈中存在ABCD四个Activity, A是栈顶Activity, D为栈底Activity, 存在打开A的Inten中设置了FLAG_ACTIVITY_SINGLE_TOP标志, 则会使用栈顶A, 而不会从新New A.
(7) FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:
一般与FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET结合使用,如果设置该属性,这个activity将在一个新的task中启动或者或者被带到一个已经存在的task的顶部,这时这个activity将会作为这个task的首个页面加载。将会导致与这个应用具有相同亲和力的task处于一个合适的状态(移动activity到这个task或者从中移出),或者简单的重置这个task到它的初始状态
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:在当前的Task堆栈中设置一个还原点,当带有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED的Intent请求启动这个堆栈时(典型的例子是用户从桌面再次启动这个应用),还原点之上包括这个应用将会被清除。应用场景:在email程序中预览图片时,会启动图片观览的actvity,当用户离开email处理其他事情,然后下次再次从home进入email时,我们呈现给用户的应该是上次email的会话,而不是图片观览,这样才不会给用户造成困惑。
例: 存在Activity A, Activity B, Activity C, Activity A启动开僻Task堆栈, 命名为TaskA(TaskA堆栈状态: A), 在Activity A中启动Activity B(TaskA堆栈状态: AB), 接着Activity B启动Activity C(TaskA堆栈状态: ABC), 启动Activity C的Intent中设置FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET标题, 这样TaskA中有一个还原点, 当有包含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED的Intent请求TaskA堆栈时(比如请求Activity A)系统就会将还原点以上的Activity清除, TaskA堆栈中只剩下了AB.
三、taskAffinity属性:
(1) taskAffinity属性应和FLAG_ACTIVITY_NEW_TASK标志及allowTaskReparenting属性结合使用, 如果只使用taskAffinity属性,请参考上面Task默认的行为.
(2) 与FLAG_ACTIVITY_NEW_TASK标志结合:
a. 前题: Activity A和Activity B在同一个应用中, Activity A与Activity B设置不同的taskAffinity属性.
操作: Activity A启动开僻Task堆栈,命名为TaskA(TaskA堆栈状态: A), 在Activity A中启动Activity B, 启动Activity B的Intent中设置FLAG_ACTIVITY_NEW_TASK标志,这时系统会新开僻一个Task堆栈,TaskB(TaskB堆栈状态: B).
b. 前题: Activity A,Activity C在同一个应用中, Activity A和ActivityC设置了相同的taskAffinity属性.
操作: Activity A启动开僻Task堆栈,命名为TaskA(TaskA堆栈状态: A), 在Activity A中启动Activity C, 启动Activity C的Intent中设置FLAG_ACTIVITY_NEW_TASK标志,这时Activity C会压入与Activity A堆栈相同的TaskA堆栈(TaskA堆栈状态: AC).
(3) 与allowTaskReparenting属性:
例: 在"TaskOne应用"中有一个天气预报Activity A, Activity A与"TaskOne应用"中的其它Activity有默认的关系(taskAffinity属性都没有设置), 并且allowTaskReparenting属性设置为true, 现在存在一个"TaskTwo应用"启动了"TaskOne应用"中的天气预报Activity A, 这时Activity A与"TaskTwo应用"中的Activity在同一个Task,命名这个Task堆栈为TaskA, 这时"TaskOne应用"启动, 并且又打开发天气预报Activity A, 这时Activity A会从TaskA堆栈中转移到"TaskOne应用"所在的堆栈, 即Activity A可以在多个堆栈中来回转移.
四、 alwaysRetainTaskState属性:
如果Task堆栈中的Root Activity设置了此属性值为true, 不管出现任何情况, 一直会保留Task栈中Activity的状态.
五、 clearTaskOnLaunch属性:
如果Task堆栈中的Root Activity设置了此属性值为true, 只要你一离开这个Task栈, 则系统会马上清理除了Root Activity的全部Activity.
六、 finishOnTaskLaunch属性:
如果某Activity设置了finishOnTaskLaunch属性, 只要你一离开这个Task栈, 则系统会马上清除这个Activity,不管这个Activity在堆栈的任何位置.
七、startActivityForResult相关的一些坑
正因为activity的很多启动模式或flag具有清除栈内已有activity的效果,清除实际是调用的系统的remove task方法,该方法会使得被清除的activity 执行onDestory等方法销毁,同时如果被销毁的activity是被其他activity 用startActivityForResult方法启动的,销毁时会给它的启动activity传递回去result cancel 事件,启动activity 的onActivityResult方法会被调用,这个时候容易出现我们意料之外的问题。
例如:A是一个桌面应用的入口 activity,并设为了singleTask 启动模式的,我在A中用startActivityForResult方法启动了B,其中有个逻辑是如果在A的的onActivityResult方法中回调了 Result cancel事件——A activity调用finish销毁。首次点击A,然后在A中用startActivityForResult启动B,这里没问题,然后home键回桌面,再次点击应用图标启动,此时发现没有反应什么界面都没出现,看现象还以为应用异常闪退。实际是由于A是singleTask 启动模式,点图标启动A时要清除A所在栈上面的所有activity,栈中B被remove task 然后onDestory, 栈中原来的A收到onActivityResult事件,然后执行finish也销毁了,然后就因为没有activity存在了不显示任何界面。
从Task的角度看,Android认为不同Task之间的Activity是不能传递数据的,因此如果启动用startActivityForResult方式启动新activity,而新启动的activity和当前activity不在同一个栈时,当前activity的onActivityResult方法回马上被回调,并且传递回result cancel事件,可以从AMS打出的log上看到有这么一句:
“WARN/ActivityManager(67): Activity is launching as a new task, so cancelling activity result.” 就是这个意思,下面startActivityForResult的注释也进行了说明。
因此对于启动模式是singleInstance或singleTask,或者是new task flag标志启动的情况要尤其注意下此问题。
以上是关于Activity 启动模式以及常见的启动Flag的主要内容,如果未能解决你的问题,请参考以下文章
Android: Activity启动模式 FLAG_ACTIVITY_NEW_TASK 详细探索