!!任务栈和Activity启动模式!!
https://blog.csdn.net/ws6013480777777/article/details/83829789
https://blog.csdn.net/infsafe/article/details/5666964
https://developer.android.com/guide/components/activities/tasks-and-back-stack
一直觉得官方和网上很多对于启动模式和intent的flag的介绍是有问题(或者是我理解的有问题),于是翻了翻源码、看了几篇文章、自己又测试的很多次,总结出下边的文章。
任务栈
栈root,栈顶。
设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的"主"Activity 将作为堆栈中的根 Activity 打开。
当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按"返回"按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。
如果用户继续按"返回",堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。 当所有 Activity 均从堆栈中移除后,任务即不复存在。
安卓系统管理着不同模式下的多个ActivityStack(比如在home launcher界面需要有一个ActivityStack,画中画模式,分屏模式等),一个ActivityStack可以包含很多个TaskRecord,一个TaskRecord又可以包含很多个ActivityRecord。
每一个ActivityRecord都会有一个Activity与之对应,一个Activity可能会有多个ActivityRecord,因为Activity可以被多次实例化,取决于其launchmode。一系列相关的ActivityRecord组成了一个TaskRecord,TaskRecord是存在于ActivityStack中,ActivityStackSupervisor是用来管理这些ActivityStack的。
下面是一个简单的关系图:
launcher也有自己的task,
系统是根据task进行管理的,而不是ActivityStack。
task又分为前台task和后台task,前台task(也叫当前task)就是栈顶是和用户交互的activity的task,后台task就是非前台task。
当一直按back按键回退时,如果发现已经回退到launcher时就不会再继续回退。
Stack #110: type=standard mode=fullscreen Task id #110 * TaskRecord{d1ba6f #110 A=com.example.puppet.customviewdemo U=0 StackId=110 sz=4} affinity=com.example.puppet.customviewdemo intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.puppet.customviewdemo/.MainActivity} realActivity=com.example.puppet.customviewdemo/.MainActivity Running activities (most recent first): TaskRecord{d1ba6f #110 A=com.example.puppet.customviewdemo U=0 StackId=110 sz=4} Run #3: ActivityRecord{89b7460 u0 com.example.puppet.customviewdemo/.MainActivity t110} Run #2: ActivityRecord{aec1bf7 u0 com.example.puppet.customviewdemo/.launchmodedemo.ActivityA t110} Run #1: ActivityRecord{2569d21 u0 com.example.puppet.customviewdemo/.launchmodedemo.LaunchModeDemoActivity t110} Run #0: ActivityRecord{99fe14d u0 com.example.puppet.customviewdemo/.MainActivity t110} mResumedActivity: ActivityRecord{89b7460 u0 com.example.puppet.customviewdemo/.MainActivity t110} Stack #111: type=standard mode=fullscreen Task id #111 * TaskRecord{f4637c #111 A=launchmodedemo.SingleInstanceActivity U=0 StackId=111 sz=1} affinity=launchmodedemo.SingleInstanceActivity intent={flg=0x10000000 cmp=com.example.puppet.customviewdemo/.launchmodedemo.SingleInstanceActivity} realActivity=com.example.puppet.customviewdemo/.launchmodedemo.SingleInstanceActivity Running activities (most recent first): TaskRecord{f4637c #111 A=launchmodedemo.SingleInstanceActivity U=0 StackId=111 sz=1} Run #0: ActivityRecord{625f0e7 u0 com.example.puppet.customviewdemo/.launchmodedemo.SingleInstanceActivity t111} Stack #0: type=home mode=fullscreen Task id #1 * TaskRecord{b92c4d9 #1 I=com.miui.home/.launcher.Launcher U=0 StackId=0 sz=1} intent={act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800100 cmp=com.miui.home/.launcher.Launcher} realActivity=com.miui.home/.launcher.Launcher Running activities (most recent first): TaskRecord{b92c4d9 #1 I=com.miui.home/.launcher.Launcher U=0 StackId=0 sz=1} Run #0: ActivityRecord{d9cd64f u0 com.miui.home/.launcher.Launcher t1} Stack #72: type=standard mode=fullscreen Task id #72 * TaskRecord{e4e1e9e #72 A=com.android.settings.root U=0 StackId=72 sz=3} affinity=com.android.settings.root intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.settings/.MiuiSettings} origActivity=com.android.settings/.MainSettings realActivity=com.android.settings/.MiuiSettings Running activities (most recent first): TaskRecord{e4e1e9e #72 A=com.android.settings.root U=0 StackId=72 sz=3} Run #2: ActivityRecord{8d7549b u0 com.android.settings/.SubSettings t72} Run #1: ActivityRecord{e7b38fd u0 com.android.settings/.SubSettings t72} Run #0: ActivityRecord{13c6e3 u0 com.android.settings/.MainSettings t72} Stack #3: type=recents mode=fullscreen Task id #4 * TaskRecord{cb5d7f #4 A=com.android.systemui U=0 StackId=3 sz=1} affinity=com.android.systemui intent={flg=0x10804000 cmp=com.android.systemui/.recents.RecentsActivity} realActivity=com.android.systemui/.recents.RecentsActivity Running activities (most recent first): TaskRecord{cb5d7f #4 A=com.android.systemui U=0 StackId=3 sz=1} Run #0: ActivityRecord{8ebf219 u0 com.android.systemui/.recents.RecentsActivity t4} Stack #35: type=standard mode=fullscreen Task id #36 * TaskRecord{c4db14c #36 A=com.tencent.mobileqq U=0 StackId=35 sz=1} affinity=com.tencent.mobileqq intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x91200000 cmp=com.tencent.mobileqq/.activity.SplashActivity} realActivity=com.tencent.mobileqq/.activity.SplashActivity Activities=[ActivityRecord{ccf6b44 u0 com.tencent.mobileqq/.activity.SplashActivity t36}]
taskAffinity
清单文件中Application和Activity标签都可以使用android:taskAffinity,值为String类型。
它代表这个Activity所希望归属的Task,在默认情况下,同一个app中的所有Activity拥有共同的Affinity,即manifest中定义的package。
另外task的affinity取决于它的根部Activity。
如果使用SingleInstance模式启动的Activity,如果没有指定affinity的话,创建的新task的affinity还是app的包名。
taskAffinity在两种情况下起作用:
- 当启动Activity的Intent中带有FLAG_ACTIVITY_NEW_TASK标志时。
在默认情况下,目标Activity将与startActivity的调用者处于同一task中。但如果用户特别指定了FLAG_ACTIVITY_NEW_TASK,表明它希望为Activity重新开设一个Task。这时就有两种情况:
- 假如当前已经有一个Task,它的affinity与新Activity是一样的,那么系统会直接用此Task来完成操作,而不是另外创建一个Task;
- 否则系统需要创建一个Task。
- 当Activity中的allowTaskReparenting属性设置为true时。
在这种情况下,Activity具有"动态转移"的能力。举个前面的"短信"例子,在默认情况下,该应用程序中的所有Activity具有相同的affinity。
当另一个程序启动了"短信编辑"时,一开始这个Activity和启动它的Activity处于同样的Task中。但如果"短信编辑"Activity指定了allowTaskReparenting,且后期"短信"程序的Task转为前台,此时"短信编辑"这一Activity会被"挪"到与它更亲近的"短信"Task中。
启动模式
在Android中每个界面都是一个Activity,切换界面操作其实是多个不同Activity之间的实例化操作。在Android中Activity的启动模式决定了Activity的启动运行方式。
有两种方式来声明指定启动模式:
- 在清单文件中
- 在java代码中
如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。
如果这两个方式均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(Intent 中所定义)优先级要高于 Activity B 的请求(其清单文件中所定义)。
在清单文件中
不管何种方式启动Activity,被启动的Activity通常都要位于ActivityStack的栈顶。
Activity启动方式launchMode,在清单文件中配置。
<activity android:name=".MainActivity"
android:launchMode="xxx" />
您可以分配给 launchMode 属性的启动模式共有四种:
"standard"(默认模式)
默认。系统在启动这个 Activity 的任务中创建 此Activity 的新实例,并向其传送 Intent。
Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个该activity实例。
"singleTop"
单独使用时,只有当 当前任务栈的顶部已存在这个 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。如果当前任务栈顶不是这个Activity则会创建新的实例。
例如,假设任务堆栈是 A-B-C-D;D 位于顶部。收到针对 D 类 Activity 的 Intent,
-
如果 D 具有默认的 "standard" 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。
-
如果 D 的启动模式是 "singleTop",则 D 的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。
-
如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop" 也是如此。
注:为某个 Activity 创建新实例时,用户可以按"返回"按钮返回到前一个 Activity。 但是当一个已经存在的Activity通过onNewIntent()在处理新的intent时,用户是不能通过返回键 回到这个Activity传入新intent之前的状态的。
"singleTask"
希望创建新任务并实例化Activity,当然会受到taskAffinity的影响,即如果已经有一个和要启动的Activity的taskAffinity相同的task,那么就在这个task中创建实例,相反就是创建一个新任务。
但是,如果该 Activity 的一个实例已存在于一个的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。此Activity只能存在一个实例。
注:尽管 Activity 在新任务中启动,但是用户按"返回"按钮仍会返回到前一个 Activity。
另外无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按"返回"按钮始终会转到前一个 Activity。 但是,如果启动指定 singleTask 启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。图中显示了这种情况。
"singleInstance".
与 "singleTask" 类似,只是系统不会将任何其他 Activity 启动到此Activity实例所在的任务栈中。该 Activity 始终是其任务栈唯一仅有的成员;由此 Activity 启动的任何 Activity 均在其他的任务栈中打开。
一旦该模式的Activity的实例存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
singleTask/singleInstance的特别说明
从ActivityStack源码可以看出,singleTask/singleInstance模式会clearTop的效果,
} else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
从ActivityStack源码可以看出,singleTask/singleInstance模式会自动添加FLAG_ACTIVITY_NEW_TASK
} else if (mLaunchSingleInstance || mLaunchSingleTask) { // The activity being started is a single instance... it always // gets launched into its own task. mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; }
使用 Intent 标志
启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括:
和"singleTask"类似,只不过单独使用是没有clearTop效果的。
与启动的那个activity的Affinity相同的任务不存在的话,就创建一个task并移到前台,并把启动的那个activity的新实例加入栈顶(此时应该是该task的root activity)。
与启动的那个activity的Affinity相同的任务已存在,会把此任务提到前台,但是分几种情况:
-
启动的那个activity是默认模式,会把此任务提到前台,然后在此任务栈顶加入此activity的新实例。
-
启动的那个activity是singleTop模式,会把此任务提到前台,然后如果任务栈顶是此activity的话就直接复用并调用onNewIntent,否则在此任务栈顶加入此activity的新实例。
-
启动的那个activity的Intent加入FLAG_ACTIVITY_CLEAR_TOP,会把此任务提到前台,然后如果任务栈中有此activity实例的话就执行FLAG_ACTIVITY_CLEAR_TOP操作,否则在此任务栈顶加入此activity的新实例
当调用者startActivityForResult时,不能使用此标志。
有关禁用此行为的标志,请参见FLAG_ACTIVITY_MULTIPLE_TASK。
这会产生与 "singleTop"launchMode 值相同的行为。
If set, the activity will not be launched if it is already running at the top of the history stack.
单独使用时,只有当正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已resumed 的实例(现在位于顶部),而不是启动该 Activity 的新实例。
FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。
注:如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中移除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例。
-
FLAG_ACTIVITY_CLEAR_TASK
如果在传递给Context.startActivity()的意图中设置了该标志,则会导致在启动activity 之前清除与该activity关联的任何现有任务。也就是说,activity成为一个空任务的新根,任何旧activity都finish了。
这只能与FLAG_ACTIVITY_NEW_TASK一起使用。
启动模式与startActivityForResult
if (sourceRecord == null) { // This activity is not being started from another... in this // case we -always- start a new task. if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // The original activity who is starting us is running as a single // instance... this new activity it is starting must go on its // own task. launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { // The activity being started is a single instance... it always // gets launched into its own task. launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { // For whatever reason this activity is being launched into a new // task... yet the caller has requested a result back. Well, that // is pretty messed up, so instead immediately send back a cancel // and let the new task continue launched as normal without a // dependency on its originator. Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); sendActivityResultLocked(-1, r.resultTo, r.resultWho, r.requestCode, Activity.RESULT_CANCELED, null); r.resultTo = null; }
也就是说startActivityForResult启动的activity有FLAG_ACTIVITY_NEW_TASK,那么就不能返回结果。
onNewIntent
当通过singleTop/singleTask启动activity时,如果满足复用条件,则不会创建新的activity实例,生命周期就变为onNewIntent()---->onResart()------>onStart()----->onResume()。
Activity第一启动的时候执行onCreate()---->onStart()---->onResume()等后续生命周期函数,也就时说第一次启动Activity并不会执行到onNewIntent().;
而后面如果再有想启动Activity的时候,那就是执行onNewIntent()---->onResart()------>onStart()----->onResume();
如果android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate()---->onStart()---->onResume()等。
注意:当调用到onNewIntent(intent)的时候,需要在onNewIntent() 中使用setIntent(intent)赋值给Activity的Intent.否则,后续的getIntent()都是得到老的Intent。
清理返回栈
如果用户长时间离开一个任务,则系统会清除该任务中除了根 Activity以外的所有 Activity 。当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
您可以使用下列几个 Activity 属性修改此行为:
如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。
如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。
此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,不管是否是root Activity都有可能被销毁。 设置为 "true" 时,Activity 仍是任务的一部分, 如果用户离开然后返回任务,则它不再存在。
从桌面图标和最近任务列表进入的行为不一致的问题
入口ActivityA设置了singleTask,ActivityB模式为standard,
开始点击桌面图标,ActivityA显示出来,此时在ActivityA中启动ActivityB,然后按home键返回桌面,
此时再从桌面图标进入此app,由于ActivityA是singleTask模式,所以会把之前的任务中ActivityA之上的所有Activity都finish掉,所以再从桌面图标进入此app时显示的是ActivityA。
从最近任务列表中进入的话,是ActivityB 返回到桌面之前的状态。
为了解决此问题,需要把ActivityA的launchMode设置为standard即可。
查看Activity的返回栈
adb shell dumpsys activity
找
ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)