Android中的TaskStack及启动模式

前言

本文主要涉及android系统对于activity的组织管理。activity是死的,只有在系统的调度下,才在手机上呈现各种各样的界面,而有那么多的activity,系统是以什么样的规则去管理调度则是一个值得深入探究的问题。

首先介绍几个概念:

  • 什么是Task?
    task翻译过来就是任务,好比用户在使用不同的app是在做不同的任务,而一个app一般有多个activity,所以有必要根据不同的任务来进行activity的管理。
  • 什么是Stack?
    学过数据结构都知道Stack是一种先进后出的数据结构,用stack来管理activity是理所当然的。当切换到一个新的界面时,该activity被push到栈顶,当移除时新的栈顶则变成前一个activity。
  • 什么是ActivityStack?Android使用ActivityStack来管理task,一个ActivityStack由不同的任务组成;

如何观察ActivityStack?

可以通过adb shell dumpsys activity activities观察ActivityStack等活动状态,这儿可以看到实际的组织如前面所说,ActivityStack包含Task,而Task包含Activity;如果对应到AMS中的数据结构,那么就是ActivityStack、TaskRecord、ActivityRecord,下面将从代码层面讲述这几个关键类。

几个问题

  • 同一个应用的Activity一定在同一个task里吗?
  • 返回和startActivity一样吗?
  • 普通任务栈与Stack #0无关,为什么返回到最后退到了launcher?
  • android:launchMode和intent.flag有什么区别与联系?
  • 既然每一个taskrecord都有栈顶的ActivityRecord,那到底哪个task才是在前台的?
  • 各种模式最佳应用场景?为什么要区分这四种启动模式?
  • TaskAffinity是什么?对哪些模式有影响?

关键类介绍

Ams中关于Activity管理的几个关键类关系如下所示

  • ActivityStackSupervisor

在AMS构造的时候就会创建,全局唯一;管理所有的ActivityStack,同时在启动一个Activity的流程中也有重要作用,这个之后分析。

  • ActivityStack
    管理任务栈。有多个,其中一开机就会有Stack #0,Launcher所在的Stack
  • TaskRecord 记录任务,管理Activity
  • ActivityRecord 一个Activity实例在Ams中的记录,同一个Activity类可能有多个记录存在

ActivityStack的创建与种类

./frameworks/base/core/java/android/app/ActivityManager.java

        /** Invalid stack ID. */
        public static final int INVALID_STACK_ID = -1;

        /** First static stack ID. */
        public static final int FIRST_STATIC_STACK_ID = 0;

        /** Home activity stack ID. */
        public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;

        /** ID of stack where fullscreen activities are normally launched into. */
        public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;

        /** ID of stack where freeform/resized activities are normally launched into. */
        public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;

        /** ID of stack that occupies a dedicated region of the screen. */
        public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;

        /** ID of stack that always on top (always visible) when it exist. */
        public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;

        /** Last static stack stack ID. */
        public static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;

        /** Start of ID range used by stacks that are created dynamically. */

最常用的是HOME_STACK_ID即Stack #0和一般Stack #1。每次开机的时候即会创建Stack #0,这是launcher和systemui的RecentsActivity所在的Stack;当从launcher点击一个图标进入新的app后,则会创建stack #1以及相应的taskrecord

不同启动模式

不同的启动模式主要涉及到ActivityRecord的管理。Android并没有简单地对Activity进行栈式的“先入后出”管理,因为实际的使用各个应用间的Activity组件可以相互调用,同时根据实际的需求又衍生出了各种启动模式。有了上面背景知识的铺垫,可以更好的理解各种launchMode和Intent的flag具体含义。

launchMode

Standard

这也是默认模式,含义是每次startActivity时都会创建一个新的实例,并且走完onCreate、onStart和onResume 。值得注意的是两点:

  1. 该ActivityRecord会放在启动它的Activity所在的任务栈的栈顶。
  2. 可以在同一个任务栈出现多个实例
  3. 如果在没有任务栈的情况下启动 standard 模式的 Activity,比如在 Service 中,此时新的 Activity 没有任务栈可入,会出现异常:
Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

此时应该为这个 Activity 指定 FLAG_ACTIVITY_NEW_TASK,这样就会新建一个任务栈。

SingleTop

顾名思义,SingleTop的含义是在栈顶只存在一个同样的Activity实例,即当startActivity自身的时候,如果该Activity已经存在在栈顶,那么并不重新创建一个实例,而是调用其onNewIntent回调。注意这仅是SingleTop,是允许栈中间存在相同实例的。

前面两种情况其实都是针对同一个任务栈来说的,并且要么是该任务栈不存在需要创建,要么就是前一个Activity所在的任务栈。接下来将说一下可能涉及到不同任务栈的情况

SingleTask

SingleTask是一种栈内复用模式,比前面两种复杂一些。首先寻找需要的任务栈,需要的含义是通过TaskAffinity指定的任务栈名字,默认就是包名。如果不存在,则创建相应的任务栈并将Activity放入栈顶;如果存在需要的任务栈,再看是不是位于栈顶,如果位于栈顶则只需要调用onNewIntent,如果不位于栈顶则pop出在此Activity之上的活动,让此Activity到栈顶。所以SingleTask可能会影响到其他Activity的生命周期。

SingleInstance

在一个新栈中放入该Activity,并且这个栈只能存放这一个Activity;一旦SingleInstance模式的Activity已经存在,则会一直复用这个Activity实例。

回顾问题

我们在前言中提到了几个问题,现在再回过头来看一下。

  • 同一个应用的Activity一定在同一个task里吗?
显然不是。一个Activity组件可以存在于调用者的任务栈中,也可以通过TaskAffinity去设置需要的任务栈,也可以设置SingleInstance单独存在。
  • 返回和startActivity的区别?
onBackPressed最终调用了finish,是会将当前Activity从栈顶移除的;而startActivity则根据启动模式的设置,依照规则去操作任务栈。
  • 普通任务栈与Stack #0无关,为什么返回到最后退到了launcher?
  • android:launchMode和intent.flag有什么区别与联系?
launchMode是规定你自己的Activity启动的行为模式,而Intent.Flag是你期望由你启动的其他的Activity是什么样的行为模式。
所以这两者都能参与任务栈的管理,只是负责的对象不同。但是组合起来就很多种情况了,比较复杂。
  • 既然每一个taskrecord都有栈顶的ActivityRecord,那到底哪个task才是在前台的?
ActivityStackSupervisor中有变量mFocusedStack来标识哪个任务栈是当前使用的。
Ams中有mFocusedActivity来记录当前的focus Activity。
  • 各种模式最佳应用场景?为什么要区分这四种启动模式?
各种模式的存在肯定是有必要的,因为android组件化的思想使得弱化单独进程的概念。各个app内的组件可以相互调用,更多的规则组合可以兼顾需求和性能。
singleTop:适合启动同类型的 Activity,例如接收通知启动的内容显示页面
singleTask:适合作为程序入口
singleInstance:适合需要共享出去的界面
  • TaskAffinity是什么?对哪些模式有影响?
设置任务亲和性,上文已说主要是用于指定Activity需要的任务栈。
实际使用中一般配合SingleTask或SingleInstance使用。

posted on 2021-03-16 17:42  Sultans  阅读(787)  评论(0)    收藏  举报

导航