写点什么

Activity 的任务栈 Task 以及启动模式与 Intent 的 Flag 详解(经典博文,值得收藏

发布于: 2021 年 11 月 08 日

# 如何查看当前系统的任务栈 > 手机中 --> 长按 home 或者多任务键会进到 **概览屏幕** 的一个界面 > 命令行中 --> adb shell dumpsys activity # 概览屏幕(Overview Screen) > 概览屏幕(也称为最新动态屏幕、最近任务列表或最近使用的应用)是一个系统级别 UI,其中列出了最近访问过的 Activity 和任务。 用户可以浏览该列表并选择要恢复的任务,也可以通过滑动清除任务将其从列表中删除。 对于 Android 5.0 版本(API 级别 21),包含多个文档的同一 Activity 的多个实例可能会以任务的形式显示在概览屏幕中。例如,Google Drive 可能对多个 Google 文档中的每个文档均执行一个任务。每个文档均以任务的形式显示在概览屏幕中。 # Task 中 activity 的特点: 1. 可以来自不同的 app 2. 可以运行在不同进程 # 影响 Task 的 activity 的属性和 Intent 标识 > Activity 的属性: 1. launchMode 2. taskAffinity 3. allowTaskReparenting 4. clearTaskOnLaunch 5. alwaysRetainTaskState 6. finishOnTaskLaunch > Intent 的标识(四个与 task 直接关系的): 1. FLAG_ACTIVITY_NEW_TASK 2. FLAG_ACTIVITY_CLEAR_TOP 3. FLAG_ACTIVITY_SINGLE_TOP 4. FLAG_ACTIVITY_RESET_TASK_IF_NEEDED ... # 什么是 Activity 的启动模式(LaunchMode) > `启动模式简单地说就是 Activity 启动时的策略,在 AndroidManifest.xml 中的标签的 android:launchMode 属性设置` > 启动模式有 4 种,分别为 standard、singleTop、singleTask、singleInstance; #### 这四种模式影响了 Activity 所在的任务栈. 使用方式:在清单文件中 activity 的节点加入 launchMode 属性 1. ### standard 默认模式,当 Intent 发送的时候,每次打开都会创建一个新的 Activity 实例。 如果 app1 启动了 app2 的 activity,则会将 APP2 的 activity 自动加入到 app1 的 activity 所在的 task **在 5.0 中,也没有出现跨应用会在新的 task 中启动 activity 的的情况 与该文章[Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance]( )描述的并不太一样** 2. ### singleTop 几乎和 standard 模式一模一样,一个 singleTop 的 Activity 的实例可以无限多,唯一的区别是如果当前 activity 已经在栈顶的话,则不会再创建一个新的 activity,通过**onNewIntent()**将 intent 发送给现有的 Activity。 1. singleTop 模式,只在当前任务栈中生效. 2. 如果通过 startActivityForResult 启动一个设置了 singleTop 的 activity,singleTop 模式将无效(不知道为什么网上很多人说该设置该 singleTop 也会导致立即在 onActivityResult 中返回一个为 cancel 的 resultCode,实测下来 4.x,5.x 的版本都没问题) #### onNewIntent()使用 Tips 1. 方法体中需手动调用 setIntent(intent),否则之后的 getIntent()获取的都是旧的 intent 对象; 2. 被 onNewIntent 方式打开的 activity,对生命周期的影响. 1. 之前 activity 是 resume 状态,onNewIntent()后只会调用 onResume()方法 2. 否则按照 `onNewIntent->onRestart->onStart->onResume->.` #### 应用场景 > 这种启动模式的用例之一就是搜索功能。假设我们创建了一个搜索框,点击搜索的时候将导航到一个显示搜索结果列表的 SearchActivity 中,为了更好的用户体验,这个搜索框一般也会被放到 SearchActivity 中,这样用户想要再次搜索就不需要按返回键。 > 想像一下,如果每次显示搜索结果的时候我们都启动一个新的 activity,10 次搜索 10 个 activity,那样当我们想返回最初的那个 activity 的时候需要按 10 次返回。 > 所以我们应该这样,如果栈顶已经有一个 SearchActivity,我们将 Intent 发送给现有的 activity,让它来更新搜索结果。这样就只会有一个在栈顶的 SearchActivity,只需点一次 back 就可以回到之前的 activity。 > 不管怎样,singleTop 和它的调用者处在一个任务中。如果你想要让 intent 发送给另一个任务中处于栈顶的 Activity,是不行的。 > 而当 Intent 来自于另外一个应用的时候,新的 Activity 的启动方式和 standard 模式是一致的。 3. ### singleTask 首先要引出**taskAffinity**这个 activity 的属性. > 把 TASK 比作一个班级,affinity 则更像是这个班级的班级名称,学校比做系统,Activity 更像是班级里的学生 *如果没有对 activity 设置该属性的话,默认为 application 的**taskAffinity**,如果 application 也没有设置,则为 app 的包名.* 启动一个 singleTask 模式的 activity,会首先在系统中找与它的**taskAffinity**属性一致的任务栈, 1. #### 先找 task 1. 没有特别指定 taskAffinity,则为当前的 task 2. 如果指定了 taskAffinity,先在系统中查找 task,如果找不到则创建一个新的 task,将 activity 作为 root 放置其中. 2. #### 启动 Activity 如果第一步中的 task 中已经有了这个 activity 的实例,则将其显示(**将 task 中该 activity 上层的 activity 都 pop 出任务栈**),同时 intent 将被通过 onNewIntent()发送. ### 对设置为 singleTask 的 activity 的总结 1. `并不是一定会在新的任务栈中打开.(具体要根据 taskAffinity(班级名称)看系统(学校)中是否已经有这个任务栈(班级)了).` 2. `如果需要在新的任务栈中启动,就需要为 activity 设置独立的 taskAffinity.` 3. `如果任务栈中已存在该 activity,那么会将上层的所有 activity 弹出.` 4. `如果当前 activity 是在新的任务栈中打开的话,那么之后在该 activity 中通过默认方式启动的 activity 都在这个新的任务栈(这个跟我们接下里要讲的 singleInstance 有区别)` 5. `如果是在新的任务栈中启动的话,最近任务列表(android 的多任务键按下后)会有两个,可选择返回至相应的任务栈` 6. 当作为 startActivityForResult 启动的目标时 1. 4.x 版本.会立刻在上个 activity 中 onActivityResult 中返回一个为 cancel 的 resultCode.(不管新的 activity 是否是在新的任务栈中启动) 2. 5.x 版本.不管是否定义了 taskAffinity,都会把将要被启动的 activity 的启动模式忽略,onActivityResult 方法会正常回调 #### 应用场景 > 该模式的使用场景多类似于邮件客户端的收件箱或者社交应用的时间线 Activity(朋友圈) 4. ### singleInstance > 与 "singleTask" 基本相同,总是该 Activity 始终是其所在 task 中唯一仅有的成员;之后在该 activity 中启动的 activity 都不会在其所在的 task 中. ## 总结 1. 当作为 startActivityForResult 启动的目标时(下文中的它都是指被启动的 activity) 1. 4.x 版本.在新的任务栈中启动,并立刻在启动它的 activity 中的 onActivityResult 中返回一个为 cancel 的 resultCode.**singleInstance 的特点还在** 2. 5.x 版本.并不会在新的任务栈中启动,而是直接在当前任务栈启动(会出现多个实例),启动它的 activity 的 onActivityResult 方法会在它关闭后,正常回调.**重点是被它开启的 activity 将运行在另外一个新的任务栈中**. #### 应用场景 > 1. 呼叫来电界面 InCallScreen # 常用的 Intent Flag 1. ### FLAG_ACTIVITY_NEW_TASK > 文档摘录: When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.`当使用这个 flag 时,如果 task 中已经有了你要启动的 activity 的话,就不再启动一个新的 activity 了,当前**task**会被带到前台(不管这个 activity 是否在前台,有可能 activity 上边还压有别的 activity).如果不想要这种行为,可以用 FLAG_ACTIVITY_MULTIPLE_TASK. > 比如说原来栈中情况是`A,B,C`,在`C`中启动`D`,如果在 Manifest.xml 文件中给`D`添加了 Affinity 的值和`C`所在的 Task 中的不一样的话,则会在新标记的 Affinity 所存在的 Task 中看是否这个 activity 已经启动,如果没启动,则直接将 activity 启动.如果启动了,直接将`D`所在的 task 带入到前台;如果是默认的或者指定的 Affinity 和 Task 一样的话,就和标准模式一样了启动一个新的 Activity. 2. ### FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_DOCUMENT (API21) > FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 在 API 21 的时候,被 FLAG_ACTIVITY_NEW_DOCUMENT 代替 如果一个 Intent 中包含此属性,则它转向的那个 Activity 以及在那个 Activity 其上的所有 Activity 都会在 task 重置时被清除出 task。当我们将一个后台的 task 重新回到前台时,系统会在特定情况下为这个动作附带一个 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标记,意味着必要时重置 task,这时 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 就会生效。经过测试发现,对于一个处于后台的应用,如果在 launcher 中点击应用,这个动作中含有 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标记,长按 Home 键,然后点击最近记录,这个动作不含 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标记,所以前者会清除,后者不会. 应用场景: 比如我们在应用主界面要选择一个图片,然后我们启动了图片浏览界面,但是把这个应用从后台恢复到前台时,为了避免让用户感到困惑,我们希望用户仍然看到主界面,而不是图片浏览界面,这个时候我们就要在转到图片浏览界面时的 Intent 中加入此标记 5.0 之前,Activity1 用该 flag 启动 Activity2 在 OverviewScreen 中是没有分开的.也就是说如果 back 到后台后,再通过 launcher 中点击 app 的 icon 进入,将直接进入 Activity1,并且无法回到 activity2 的界面. 5.0 之后,OverviewScreen 中,会将两个 activity 分开.可以返回指定想要的 activity. 3. ### FLAG_ACTIVITY_MULTIPLE_TASK > 不建议使用此标记,除非你自己实现了应用程序的启动器。结合 FLAG_ACTIVITY_NEW_TASK 这个标记,即使要启动的 activity 已经存在一个 task 在运行,也会新启动一个 task 来运行要启动的 activity 系统缺省是不带任务管理器的,所以当你使用这个标签的时候,你必须确保你能从你启动的 task 中返回回来。 如果没有设置 FLAG_ACTIVITY_NEW_TASK,这个标记被忽略 4. ### FLAG_ACTIVITY_CLEAR_TASK > 文档原文:`If set in an Intent passed to Context.startActivity(), this flag will cause any existing task that would be associated with the activity to be cleared before the activity is started. That is, the activity becomes the new root of an otherwise empty task, and any old activities are finished. This can only be used in conjunction with FLAG_ACTIVITY_NEW_TASK.` 个人翻译:`这个 flag 会导致,在这个 activity 启动之前,任何与该 activity 相关的 task 都会被清除.也就是说,这个 activity 将会是一个空 task 的最底部的 activity,之前所有的 activity 都会被 finish 掉.这个 flag 只能和 FLAG_ACTIVITY_NEW_TASK 结合使用.` 比如说原来栈中情况是`A,B,C,D`,在 D 中启动 B(加入该 flag),中间过程是`A,B,C`依次 destory,D 先 onPause,随后 BonCreate,onSta ``` 《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》 浏览器打开:qq.cn.hn/FTe 免费领取 ``` rt,onResume.D 再 onStop,onDestory.最后只有一个 B 在栈底.(无论 taskAffinity..?) 5. ### FLAG_ACTIVITY_SINGLE_TOP > 相当于 launchMode 中的 singleTop,比如说原来栈中情况是`A,B,C,D`,在 D 中启动 D(加入该 flag),栈中的情况还是`A,B,C,D`. 6. ### FLAG_ACTIVITY_CLEAR_TOP > 不同于 launchMode 中的 singleTask,比如说原来栈中情况是`A,B,C,D`,在 D 中启动 B(加入该 flag), 栈中的情况将为`A,B`.但是 B 会重新 onCreate()...,并没有执行 onNewIntent().如果希望与 singleTask 效果相同,可以加入`FLAG_ACTIVITY_SINGLE_TOP`. 7. ### FLAG_ACTIVITY_REORDER_TO_FRONT > 这个跟上边 FLAG_ACTIVITY_BROUGHT_TO_FRONT 的是容易混淆的.比如说原来栈中情况是`A,B,C,D`,在 D 中启动 B(加入该 flag),栈中的情况会是`A,C,D,B`.(调用 onNewIntent()) 8. ### FLAG_ACTIVITY_BROUGHT_TO_FRONT > 这个是最容易让人误解的 flag 了.跟 FLAG_ACTIVITY_REORDER_TO_FRONT 是不一样的.不是由我们一般开发者使用的 flag. 文档中解释:This flag is not normally set by application code, but set for you by the system as described in the launchMode documentation for the singleTask mode. 9. ### FLAG_ACTIVITY_NO_HISTORY > A 启动 B(加入该 Flag),B 启动 C.在 C 返回,将直接返回到 A.B 在 A 正常 onResume 后,才会调用`onStop,onDestory...` 而且被这个 flag 启动的 activity,它的 onActivityResult()永远不会被调用 10. ### FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS > 我所理解的,加了这个 flag 启动的 activity 所在的 task(必须是该 task 中最底部的 activity)将不会在多任务界面出现.一般配合 FLAG_ACTIVITY_NEW_TASK 使用,这样新的任务栈,在最近使用列表中,就不会出现. 11. ### FLAG_ACTIVITY_FORWARD_RESULT > 多个 Activity 的值传递。A 通过 startActivityForResult 启动 B,B 启动 C,但 B 为过渡页可以 finish 了,A 在期望 C 把结果返回.这种情况,B 可以在启动 C 的时候加入该 flag. 12. ### FLAG_ACTIVITY_NO_USER_ACTION > 禁止 activity 调用 onUserLeaveHint()。 onUserLeaveHint()作为 activity 周期的一部分,它在 activity 因为用户要跳转到别的 activity 而退到 background 时使用。比如,在用户按下 Home 键(用户的操作),它将被调用。比如有电话进来(不属于用户的操作),它就不会被调用。注意:通过调用 finish()时该 activity 销毁时不会调用该函数。 13. ### FLAG_ACTIVITY_RETAIN_IN_RECENTS (API21) > 与 activity 设置 autoRemoveFromRecents = false 属性效果一样.是指当前 activity 销毁后,是否还在概览屏幕中显示.(5.0 之后生效) 14. ### FLAG_ACTIVITY_RESET_TASK_IF_NEEDED > 一般为系统使用,比如要把一个应用从后台移到前台,有两种方式:从多任务列表中恢复(不包含该 flag);从启动器中点击 icon 恢复(包含该 flag);需结合` FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_DOCUMENT (API21)`理解 15. ### FLAG_ACTIVITY_PREVIOUS_IS_TOP > 即 A---> B --->C,若 B 启动 C 时用了这个标志位,那在启动时 B 并不会被当作栈顶的 Activity,而是用 A 做栈顶来启动 C。此过程中 B 充当一个跳转页面。 典型的场景是在应用选择页面,如果在文本中点击一个网址要跳转到浏览器,而系统中又装了不止一个浏览器应用,此时会弹出应用选择页面。在应用选择页面选择某一款浏览器启动时,就会用到这个 Flag。然后应用选择页面将自己 finish,以保证从浏览器返回时不会在回到选择页面。 `经常与 FLAG_ACTIVITY_FORWARD_RESULT 一起使用。` 16. ### FLAG_ACTIVITY_TASK_ON_HOME > 该 flag 启动的 activity,点击返回键会回到 launcher.需要与`FLAG_ACTIVITY_NEW_TASK`一起使用,并且`FLAG_ACTIVITY_NEW_TASK`模式生效(参考该属性)后,该 flag 才会起作用. 17. ### FLAG_EXCLUDE_STOPPED_PACKAGES > 设置之后,Intent 就不会再匹配那些当前被停止的包里的组件。如果没有设置,默认的匹配行为会包含这些被停止的包。 18. ### FLAG_DEBUG_LOG_RESOLUTION > debug 模式可以打印 log

评论

发布
暂无评论
Activity的任务栈Task以及启动模式与Intent的Flag详解(经典博文,值得收藏