欢迎光临
专注android技术,聚焦行业精粹,传扬中国传统文化,我们一直在努力

Android Activity 任务和返回栈 总结篇

1. 内容目录

2. 介绍

写在文章前面的话:本文针对的是对Activity任务栈,以及LaunchMode有一定基础的同学写的。不了解的同学可以先在官网的这篇文档学习一下。

虽然说很多人都知道并已经在使用launchMode,但实际应用中我们会经常遇到各种各样的问题,而不得不去再翻文档或者自己写demo验证。也许很多有自己对launchmode的总结了,但可能不系统或不够全面,希望通过这篇文章总结一下android的任务栈管理,让自己能有个清晰的认识,以后不再被这样的问题困扰。

 

2.1 任务

任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。

任务是一个有机整体,当用户开始新任务或通过“主页”按钮转到主屏幕时,可以移动到“后台”。 尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已,如图 1 中所示。然后,任务可以返回到“前台”,用户就能够回到离开时的状态。

图 1. 两个任务:任务 B 在前台接收用户交互,而任务 A 则在后台等待恢复。

2.2 返回栈

Task是Android系统中的概念,它不同于进程Process的概念。简单地说,一个Task是一系列Activity的集合,这个集合是以堆栈的形式来组织的,遵循后进先出的原则。

返回栈以“后进先出”对象结构运行。 图 2 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。

 

图 2. 显示任务中的每个新 Activity 如何向返回栈添加项目。 用户按“返回”按钮时,当前 Activity 随即被销毁,而前一个 Activity 恢复执行。

在Android中每个Activity有自己的生命周期,这些Activity在返回栈中进出时,它们会有一些默认的行为,总结如下:

  • 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。
  • 用户通过按“主页”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。 系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。
  • 如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。 堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统不会保留该 Activity 的状态。
  • 即使来自其他任务,Activity 也可以多次实例化。

 

3. 任务栈管理

从文章第一节我们看出,可以通过这5个点来控制任务栈,包括:

任务管理类型 属性 描述
activity启动模式 standard 清单文件中的launchMode。

用于控制Activity在栈里面的存在形式

singleTop
singleTask
singleInstance
FLAG_ACTIVITY_NEW_TASK startActivity时的flag。

同清单里面的launchMode属性,不过当某个Activity这两个属性都有设置时,这个Flag的优先级会更高

FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
activity关联任务 taskAffinity 设置该Activity的任务关联。例如:是在一个新的任务栈里面,还是Activity在任务之间转移
allowTaskReparenting
返回栈清理 clearTaskOnLaunch 如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。

您可以使用这几个 Activity 属性修改此默认行为

alwaysRetainTaskState
finishOnTaskLaunch
程序主入口任务 android.intent.category.LAUNCHER
android.intent.action.MAIN
 通过这个过滤器将 Activity 设置为任务的入口点

 

3.1 介绍一个分析工具

此时我们需要一个工具来帮助我们更直观的分析这些参数对Activity的影响,这里用的是github里面的一个开源的demo:github地址 。这个demo比较直观,用图形的方式让你更清晰的看到Activity当前所处栈的情况,以及是否起了一个新的栈。应用截图如图3所示:

图3:这个应用模拟了4中launchMode和不同的flag启动方式,并用图形的方式展现当前栈的情况

它的核心原理是使用下面的方法:

该方法在Actiivty onCreate的时候拿到当前所处的task id,该task id标识了一个任务栈,于是就可以了解到该Activity是在一个新的栈里面还是在已有的栈里面。该工具的类结构图如下:

  • BaseApplication:定义了获取当前栈ID,压栈,出栈等栈的操作
  • TaskInfoDisplayer:用图形化的方式展示栈的详细信息
  • Standard,SingleTop,SingleTask,SingleInstance:顾名思义就是在清单文件中以4种不同的启动模式定义的Activity

接下来我们从以下几个场景分析下Activity的栈管理:

  • 在主入口Activity的manifest添加launchMode属性

 

3.2 在主入口Activity的manifest添加launchMode属性

我们通常会用下面的方式定义app中的主入口Activity,在没有设置launchMode时,系统会以标准(Standard,或者称为ActivityInfo.LAUNCH_MULTIPLE)的方式来启动这个Activity:

如果在主入口Activity中添加launchMode属性会有什么表现呢?我们看一组数据:

1)Manifest配置:

2)操作步骤:

场景模拟:打开app(此时默认启动Standard这个入口Activity),然后再依次启动Standard,SingleTop,SingleTask,SingleInstance这四个Activity。此时按Home键切到后台,再通过主屏幕icon点击拉起。

入口Activity的启动模式修改:以下是以Standard这个主入口activity 分别设置为4种启动模式的栈对比:

standard Activity的
launchMode
栈的情况(顺序从栈底到栈顶) 按Home键再通过桌面图标进入
(显示在前台的任务)
无/standard task1: Standard(launchMode为standard)-> Standard-SingleTop
task2: SingleTask
task3: SingleInstance
task1: Standard(launchMode为standard)-Standard-SingleTop
singleTask task1: Standard(launchMode为singleTask)-> SingleTop
task2: SingleTask
task3: SingleInstance
task1: Standard(launchMode为singleTask)
singleInstance task1: Standard(launchMode为singleInstance)
task2: SingleTop
task3: SingleTask
task4: SingleInstance
task1: Standard(launchMode为singleInstance)
singleTop task1: Standard(launchMode为singleTop) -> SingleTop
task2: SingleTask
task3: SingleInstance
task1: Standard(launchMode为singleTop) -SingleTop

3)现象分析:从现象中我们发现,在主入口的Activity设置不同的launchMode属性后,首次启动并按Home键回到后台,再从桌面图标启动后,最终显示给用户的Activity设置了singleTask或singleInstane启动模式的显示在前台的任务的表现和之前切后台之前会不同。这个会导致我们在实际应用过程中遇到一些异常情况,我们看下面两个场景:

场景一:A activity 为主入口activity(intent-filter的category为LAUNCHER),且它的launchMode属性为singleTop;B activity 为另一个activity,它的属性没有特别要求

操作步骤

  1. 桌面点击图标启动A activity
  2. A activity 启动 B activity,然后finish A activity自己

代码如下

此时生命周期输出如下

继续上面的操作步骤:接下来按Home键回到桌面,再点击桌面图标启动该应用。此时生命周期打印如下:

现象:可以看得出来虽然A activity在启动B activity后finish掉了自己,此时Activity任务栈里面只有B activity;接着按Home键把任务栈切换到后台,再点击桌面图标启动该应用。虽然图标对应的入口Activity A已经被自己finish,但最终启动的结果是该后台的栈原封不动的搬回到前台,栈里面的B activity执行了onStart生命周期。

分析:桌面图标点击启动的逻辑是桌面应用找到这个应用的LAUNCHER Activity的Intent,然后发送intent启动该应用。很显然launchMode为singleTop的入口Activity对从桌面启动的后台应用的Activity 栈的顶层 activity状态不会重新创建,有效的恢复了用户的上一次操作界面。接下来举一个相反的例子,见场景二

 

场景二:将场景一的A activity的launchMode设置为singleTask,重复场景一的步骤,生命周期打印又会是什么样呢?

首次启动仍然和场景一的一样

按Home切换到后台,再从桌面图标启动

现象:此时发现activity A和B的hash值都改变了,可见两个activity都重新创建了,且它们的生命周期都走了onCreate;这个现象和场景一就完全相反了。这就是为什么有时我们不了解launchMode和LAUNCHER的category的关系时把launchMode设置了singleTask后会出现我们的B activity会重新创建和初始化,对于用户的表现就是我从后台把应用切回来恢复不到B activity之前的页面状态了,这个体验是相当不好的。

这个是什么原因呢?我们看下android源码的实现:

通过桌面启动Activity流程中处理launchMode和flag的逻辑在ActivityStack.startActivityUncheckedLocked方法里面(这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中(详细的Activity启动流程分析,参考老罗的这篇文章):

代码里面的r.launchMode是在AndroidManifest.xml里面配置的launchMode值,从逻辑中我们发现当r.launchMode为singleTask或者singleInstance时,会把task重置到初始状态,并把它上面的所有activity全部清除。注释描述如下:

4)总结:当在app主入口Activity设置launchMode时需要关注它的以上表现行为,避免使用不当导致在以上场景表现异常的问题。

5)使用建议:我们经常会遇到这样的场景,一个App会有一个闪屏Activity做为该应用的主入口Activity,显示完闪屏Activity后会finish掉然后启动主页面Activity,主Activity可能会出现实例化两次的情况,如何只保证1个实例呢,有下面两个方法:

注意,也许你会使用isTaskRoot来判断,isTaskRoot在4.4的手机会返回false,而4.4以上的手机会返回true。这样isTaskRoot判断会不准确,我们可以用下面2个方法来替代

方法1:

判断activity是否已经存在,如果存在则关闭当前正在启动的重复activity。

方法2:

自己模拟isTaskRoot

 

3.3、几种启动场景分析

从手机桌面启动应用涉及到以下3个flag:

  • FLAG_ACTIVITY_NEW_TASK = 0x10000000
  • FLAG_ACTIVITY_BROUGHT_TO_FRONT = 0x00400000
  • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000

1. 安装场景
a. 安装打开的拉起intent

  • flags: FLAG_ACTIVITY_NEW_TASK ,
  • action:android.intent.action.MAIN,
  • category:{android.intent.category.LAUNCHER

b. 安装打开切后台,从launcher点icon启动时的拉起intent

  • flags:FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_BROUGHT_TO_FRONT|FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  • action:android.intent.action.MAIN,
  • category:{android.intent.category.LAUNCHER

c. 再切后台启动时的拉起intent

  • flags:FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_BROUGHT_TO_FRONT|FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  • action:android.intent.action.MAIN
  • category:{android.intent.category.LAUNCHER

2. 杀进程从launcher启动场景

拉起intent:

  • flags: FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
  • action:android.intent.action.MAIN,
  • category:{android.intent.category.LAUNCHER

Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
这个Flag主要用来改变Task堆栈顺序,如果在ABCD的状态下,以该标识启动B,则会成为ACDB,且B不会重新创建

Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
这个标识主要用于创建一个还原点,再次启动该Task时会将还原点之上包括其本身都销毁掉,如果在一个程序中以该标识启动了另外一个程序的功能,如一个用于看图的软件,当退出桌面,再点击这个程序,看图软件会消失。

相关阅读:https://stackoverflow.com/questions/29321261/what-are-the-differences-between-flag-activity-reset-task-if-needed-and-flag-act

3.3.1、首次安装启动游戏稳定显示闪屏的问题

After I look into this problem for a while, I have a conclusion below:
1. why the splash activity always show after install and click from desktop ?
when launch game from desktop after install or just a new restart the game, the difference is the intent flag, it has a FLAG_ACTIVITY_BROUGHT_TO_FRONT flag. This flag shall bring the activity(current is splash activity) to the top of task, so it shall always show the splash activity. If we kill the game and launch from the desktop the intent doesn’t has the FLAG_ACTIVITY_BROUGHT_TO_FRONT, so it just bring the task from background to foreground.

  1. why the game stucked in above situation
    I think it’s game logic, so it’s difficult for us to find out why the game is stucked. but it has someting to do with the launching intent, For our other games doesn’t have this problem

Solutions:
1. try move 2 finish methods in I capture a screen shot above before supre.onCreate() methods and give us a apk try again.
2. I checked that the solution you mentioned above is OK, which is we do a check in our splash activity and just finish if the game activity is already there. But this solution shall affects all the channels that has a splash screen activity. If we modify as this, then we should check the online games if it’s ok with this change.
3. try to find an OK build version of game, find out why start up a new game activity and do nothing just finish very early in onCreate method that cause the game stucked, the other UI is still worked well in that situation.

待继续….

 

赞(0) 打赏
未经允许不得转载:花花鞋 » Android Activity 任务和返回栈

评论 抢沙发

国内精品Android技术社区

专注android技术,聚焦行业精粹,传扬中国传统文化,我们一直在努力

联系我们

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

登录

找回密码

注册