TaskRecord分析

gionee/alps/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

taskRecord

http://www.jianshu.com/p/c7889a533050

1、任务栈的分类:

任务栈有以下四种

  • standard 默认的启动模式,标准模式
  • singletop 单一顶部模式 (顶部不会重复)
  • singleTask 单一任务栈,干掉头上的其他Activity
  • singleInstance 单一实例(单例),任务栈里面自已自己一个人

一般来说用默认的就好,当我们程序感觉切换奇怪,或者某个activity占用开销太大之类的,才考虑使用其他的启动默认。

2、指定任务栈Activity的启动模式

待会我们再来详细解释这些不同的任务栈详细区别,现在,我们先看一下怎么指定一个Activity的任务栈模式,也就是启动模式。

默认的Activity都是standard模式的,那如果我们要把一个Activity指定为 singleTask 模式呢?

有两种启动方法:一种方法是manifest指定,另外一种方式是代码指定。

2.1 manifest指定

比如我们要指定为singleTask模式
manifest里面的Activity有个 launchMode属性来制定启动模式:

        <activity android:name=".SecondActivity"

            android:launchMode="singleTask"

            />

2.2 代码指定 intent.addFlag

比如我们要指定为singleTop模式

Intent intent  = new Intent();
intent.setClass(FirstActivity.this,SecondActivity.class);
// 通过Intent的addFlag指定
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);

2.3、两种方式的对比

1、从优先级来说,代码指定优先于manifest指定
2、两者各有局限,
manifest无法设定 FLAG_ACTIVITY_CLEAR_TOP 标识
代码指定无法指定为 singleInstance启动模式

3、怎么查看当前app的任务栈数量和任务栈里面的Activity

3.1 adb shell dumpsys activity

在终端键入这样的指令:
adb shell dumpsys activity

查看当前手机的任务栈运行情况

即可得到我们想要的信息,回车后会列出相当多的信息,我们需要找到如下的分类
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)

下面是一份示例log摘取:

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
  Stack #0:
    Task id #1
      TaskRecord{529322d8 #1 A=com.android.launcher U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10600000 cmp=com.android.launcher/com.android.launcher2.Launcher }
        Hist #0: ActivityRecord{5297586c u0 com.android.launcher/com.android.launcher2.Launcher t1}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000000 cmp=com.android.launcher/com.android.launcher2.Launcher }
          ProcessRecord{5293299c 17717:com.android.launcher/u0a8}

    Running activities (most recent first):
      TaskRecord{529322d8 #1 A=com.android.launcher U=0 sz=1}
        Run #0: ActivityRecord{5297586c u0 com.android.launcher/com.android.launcher2.Launcher t1}

  Stack #1:
    Task id #25
      TaskRecord{52a1682c #25 A=com.amqr.taskstack U=0 sz=3}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.amqr.taskstack/.FirstActivity }
        Hist #2: ActivityRecord{52980338 u0 com.amqr.taskstack/.ThirdActivity t25}
          Intent { cmp=com.amqr.taskstack/.ThirdActivity }
          ProcessRecord{5296870c 32225:com.amqr.taskstack/u0a85}
        Hist #1: ActivityRecord{5297ef0c u0 com.amqr.taskstack/.SecondActivity t25}
          Intent { flg=0x20000000 cmp=com.amqr.taskstack/.SecondActivity }
          ProcessRecord{5296870c 32225:com.amqr.taskstack/u0a85}
        Hist #0: ActivityRecord{529f1cc8 u0 com.amqr.taskstack/.FirstActivity t25}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.amqr.taskstack/.FirstActivity }
          ProcessRecord{5296870c 32225:com.amqr.taskstack/u0a85}

    Running activities (most recent first):
      TaskRecord{52a1682c #25 A=com.amqr.taskstack U=0 sz=3}
        Run #2: ActivityRecord{52980338 u0 com.amqr.taskstack/.ThirdActivity t25}
        Run #1: ActivityRecord{5297ef0c u0 com.amqr.taskstack/.SecondActivity t25}
        Run #0: ActivityRecord{529f1cc8 u0 com.amqr.taskstack/.FirstActivity t25}

    mResumedActivity: ActivityRecord{52980338 u0 com.amqr.taskstack/.ThirdActivity t25}

  mFocusedActivity: ActivityRecord{52980338 u0 com.amqr.taskstack/.ThirdActivity t25}
  mDismissKeyguardOnNextActivity=false
  mFocusedStack=ActivityStack{52a267fc stackId=10, 1 tasks} mStackState=STACK_STATE_HOME_IN_BACK
  mSleepTimeout=false
  mCurTaskId=25
  mUserStackInFront={}

  Recent tasks:
  * Recent #0: TaskRecord{52a1682c #25 A=com.amqr.taskstack U=0 sz=3}
  * Recent #1: TaskRecord{529322d8 #1 A=com.android.launcher U=0 sz=1}
  * Recent #2: TaskRecord{529890a0 #11 A=com.android.systemui U=0 sz=0}

3.2、 ACTIVITY MANAGER ACTIVITIES

在ACTIVITY MANAGER ACTIVITIES 大分类里,
找到Running activities (most recent first),可以看到最近正在操作的那个程序涉及到任务栈个数,以及每个任务栈的Activity数量,根据位置上下排序,最上面的是栈顶,最下面的是栈底。栈顶就是最近的操作界面

3.3、Running activities (most recent first)

我们把Running activities (most recent first)单独拿出来说。
下面这份信息,显示着最近运行的一个程序的只有一个任务栈,任务栈里面有三个activit,栈顶是ThirdActivity,栈底是FirstActivity。
这个任务栈的名称是com.amqr.taskstack。

    Running activities (most recent first):
      TaskRecord{52a1682c #25 A=com.amqr.taskstack U=0 sz=3}
        Run #2: ActivityRecord{52980338 u0 com.amqr.taskstack/.ThirdActivity t25}
        Run #1: ActivityRecord{5297ef0c u0 com.amqr.taskstack/.SecondActivity t25}
        Run #0: ActivityRecord{529f1cc8 u0 com.amqr.taskstack/.FirstActivity t25}

再来一份例子:

    Running activities (most recent first):
      TaskRecord{528f99b4 #12 A=com.taskstack.thirdtask U=0 sz=1}
        Run #1: ActivityRecord{529d828c u0 com.amqr.taskstack/.ThirdActivity t12}
      TaskRecord{529e4584 #11 A=com.amqr.taskstack U=0 sz=1}
        Run #0: ActivityRecord{529a8c5c u0 com.amqr.taskstack/.FirstActivity t11}

比如这份终端信息。就表示当前当前最近的运行的有两个任务栈的,(为什么一个程序的运行有两个任务栈?这就涉及到singleTask或者singleInstance启动模式了,后面会细说。)
其中,一个任务栈的是com.taskstack.thirdtask,(我们自己指定的名称),当前这个任务栈里面只有一个activity。
另外一个任务栈的名称是com.amqr.taskstack,这个是系统默认自带的任务栈,名字就是包名。

我们拿出一条来分析把

TaskRecord{528f99b4 #12 A=com.taskstack.thirdtask U=0 sz=1}
  Run #1: ActivityRecord{529d828c u0 com.amqr.taskstack/.ThirdActivity t12}

像这个,528f99b4 #12这么一串里面,这个#12可以说是这个任务栈的唯一标示(有时候会两个任务栈出现 任务栈A=“aaa.bbb.ccc”的aaa.bbb.ccc一样的情况,想要根本区分是否为同一个任务栈,用的就是这个 #号+num,如果两个人任务栈的 #+num 和 A="aaa.bbb.ccc" 两者都一致的话,那么这两个任务栈绝对会同一个任务栈)
至于最后面的 sz=num,这个num代表这个任务栈里面当前存放了多少个Activity。

什么时候出现两个任务栈 aaa..bbb.ccc 相同的但是 #num 不同:singleInstance
什么时候出现两个任务栈 aaa.bbb.ccc 和 #num 都相同:singleTask + taskAffinity

3.4、 Recent tasks 手机当前的运行的任务栈

(注意:Recent tasks代表的最近手机运行的程序的任务栈,不是对应正在运行的程序的个数(因为有的程序可能有多个任务栈),更加不是进程数。)

  Recent tasks:
  * Recent #0: TaskRecord{52a1682c #25 A=com.amqr.taskstack U=0 sz=3}
  * Recent #1: TaskRecord{529322d8 #1 A=com.android.launcher U=0 sz=1}
  * Recent #2: TaskRecord{529890a0 #11 A=com.android.systemui U=0 sz=0}

这个是记录手机当前的运行的任务栈数量的。
当我们长按home键,或者按下虚拟的菜单键,(因机型而异),反正就是列出正在运行的运行的任务栈的界面,假设我们的正在运行的是1个任务栈,一般来说,这时Rcent tasks就会显示记录着3个任务栈,如果我们手机显示正在运行两个2个,那么终端的Rcent tasks就会显示记录着4个任务栈,即为N+2个。(有时候会是N+1,只有com.android.launcher )
多出来的那个两个上面写的很清楚,那么就是com.android.launcher 和 com.android.systemui,即为系统启动器和系统UI界面。

我们在来个图文详细点的吧。

当前手机任务列表运行着3个任务栈(不要以为任务栈就是程序,一个程序可能有多个任务栈)

当前手机任务列表运行着3个任务栈.png

查看一下终端,会显示记录着5个。

  Recent tasks:
  * Recent #0: TaskRecord{52a0c9e4 #16 A=com.android.systemui U=0 sz=1}
  * Recent #1: TaskRecord{528ea064 #1 A=com.android.launcher U=0 sz=1}
  * Recent #2: TaskRecord{529e0c18 #18 A=android.task.mms U=0 sz=1}
  * Recent #3: TaskRecord{529b3f00 #17 A=android.task.contacts U=0 sz=1}
  * Recent #4: TaskRecord{529589e4 #15 A=android.task.browser U=0 sz=1}

.
.
再次说明,列表显示的是任务栈,不是程序

一个程序的两个任务栈.png

.
.

4、四种启动模式详解

4.1、 standard 默认的启动模式,标准模式

  结论:每开启一个Activity,就会在栈顶添加一个Activity实例。多次间隔或者直接启动一个甲Activity会添加多个甲的示例,可重复添加。(间隔 ABA, 直接 ACC或者AAA)
  这里我们需要明白一个事情,Service和ApplicationContext是没办法直接开启一个新的Activity,因为只有Activity类型的Context的Activity才能开启,但还是有解决办法的,那就是让我们要开的那个新的Activity设置为FLAG_ACTIVITY_NEW_TASK标识。

情景实测

  比如我们的程序里面有FirstActivity,SecondActivity、ThirdActivity和Fourth四个Activity,为了表示方便,我们就用A,B,C和D来分别指代吧。其中,A为启动页。(当前A,B,C,D都是standard模式)

  • 情况1: 启动后显示A,接着打开B,紧接着打开C。那么显而易见,这时候只有一个任务栈,假设为S1
    启动A:任务栈S1里面只有A
    接着打开B:任务栈里面变成BA,B在A上面,B为栈顶。
    接着打开C,任务栈里面变成了CBA,栈顶是C,栈底是A。

大概是这个样子

standard模式简单ABC手机界面.gif

standard模式简单ABC.gif

后面不会这么贴图了,第一个就图文说的清楚一些。

  • 情况2: 打开A,打开B,再次打开A
    此时任务栈: ABA
    那么任务栈里面的是 ABA , 其中第一次打开的A位于栈底,第二次打开的A为了栈顶。

利用adb shell dumpsys activity查看Running activities (most recent first)可以得到证实:

    Running activities (most recent first):
      TaskRecord{52995938 #35 A=com.amqr.taskstack U=0 sz=3}
        Run #2: ActivityRecord{52a02754 u0 com.amqr.taskstack/.FirstActivity t35}
        Run #1: ActivityRecord{529fdd68 u0 com.amqr.taskstack/.SecondActivity t35}
        Run #0: ActivityRecord{529ed754 u0 com.amqr.taskstack/.FirstActivity t35}
  • 情况3:打开A,打开C,再打开C
    任务栈 CCA,其中第二个C是栈顶,A位于栈底。
Running activities (most recent first):
      TaskRecord{529e1bac #4 A=com.amqr.taskstack U=0 sz=3}
        Run #2: ActivityRecord{529ae2d8 u0 com.amqr.taskstack/.ThirdActivity t4}
        Run #1: ActivityRecord{5299b11c u0 com.amqr.taskstack/.ThirdActivity t4}
        Run #0: ActivityRecord{529379d0 u0 com.amqr.taskstack/.FirstActivity t4}

.
顺便附上两次打开C的生命周期方法log,附上这个log是为了跟singleTask的做对比。

12-03 07:15:21.176 15445-15445/com.amqr.taskstack D/Cur: FirstActicity onCreate
12-03 07:15:21.176 15445-15445/com.amqr.taskstack D/Cur: FirstActivity onStart
12-03 07:15:21.176 15445-15445/com.amqr.taskstack D/Cur: FirstActivity onResume
第一次按下打开C
12-03 07:15:23.360 15445-15445/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 07:15:23.376 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-03 07:15:23.376 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-03 07:15:23.376 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-03 07:15:23.788 15445-15445/com.amqr.taskstack D/Cur: FirstActivity onStop
第二次按下打开C
12-03 07:15:25.668 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-03 07:15:25.688 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-03 07:15:25.688 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-03 07:15:25.688 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-03 07:15:26.068 15445-15445/com.amqr.taskstack D/Cur: ThirdActivity onStop

附上FirstActivity的代码,其他三个Activity代码类似。

public class FirstActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        Log.d("Cur", "FirstActicity onCreate");

        findViewById(R.id.mBtn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(FirstActivity.this, FirstActivity.class));
            }
        });

        findViewById(R.id.mBtn2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                startActivity(new Intent(FirstActivity.this,SecondActivity.class));

                /*Intent intent  = new Intent();
                intent.setClass(FirstActivity.this,SecondActivity.class);
                // 通过Intent的addFlag指定 ,这里指定为 single task
                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

                startActivity(intent);*/
            }
        });

        findViewById(R.id.mBtn3).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(FirstActivity.this,ThirdActivity.class));
            }
        });

        findViewById(R.id.mBtn4).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(FirstActivity.this,FourtActivity.class));
            }
        });

    }
}

manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskstack" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".FirstActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".SecondActivity" />
        <activity android:name=".ThirdActivity"/>
        <activity android:name=".FourtActivity"/>
    </application>

</manifest>

.
.

4.2、singletop 单一顶部模式 (顶部不会重复)

  结论:如果开启的Activity已经存在一个实例在任务栈的顶部(仅限于顶部),再去开启这个Activity,任务栈不会创建新的Activity的实例了,而是复用已经存在的这个Activity,onNewIntent方法被调用;之前打开过,但不是位于栈顶,那么还是会产生新的实例入栈,不会回调onNewIntent方法。

当我们把一个Activity设置为singleTop,当我们点击打开这个Activity的时候,我们打开B页面,会出现几种情况:

说明:当前A和C都是Standard,B是singleTop

之前没打开过:
此时任务栈里面只有A,A所在的任务栈是S1,这个时候打开singleTop的B,B入栈,入的是S1这个栈,谁打开它进入谁的栈,此时S1的情况是BA,B为栈顶。

之前打开过,但是位于栈顶:
那么复用这个栈,不会有新的实例压入栈中。同时 onNewIntent 方法会被回调,我们可以利用这个方法获得页面传过来的消息或者其他操作。

之前打开过,但是不是位于栈顶:
那么还是会产生新的实例入栈。

情景实测

实测前,我们不改动Standard的代码,只是在manifest里面的将SecondActivity的启动模式设置为singleTop

<activity android:name=".SecondActivity"
android:launchMode="singleTop"    />

以下的谈论都是在A和C都是Standard模式,B为singleTop模式的情况进行的。

  • 情景一:打开A,然后在打开B
    没什么好说的,很普通的没什么区别

请出终端大哥

    Running activities (most recent first):
      TaskRecord{529c0838 #19 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{52a35f58 u0 com.amqr.taskstack/.SecondActivity t19}
        Run #0: ActivityRecord{52a6c77c u0 com.amqr.taskstack/.FirstActivity t19}

生命周期log

12-03 07:57:20.728 6639-6639/? D/Cur: FirstActicity onCreate
12-03 07:57:20.728 6639-6639/? D/Cur: FirstActivity onStart
12-03 07:57:20.728 6639-6639/? D/Cur: FirstActivity onResume

12-03 07:57:21.988 6639-6639/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 07:57:22.004 6639-6639/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 07:57:22.004 6639-6639/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 07:57:22.004 6639-6639/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 07:57:22.428 6639-6639/com.amqr.taskstack D/Cur: FirstActivity onStop
  • 情况2:打开A,打开B,然后再打开B
    这个时候singleTop的作用就发挥出来了。这是不会产生新的实例,会复用的顶部的已经存在的实例。
    第一次打开B,这时任务栈里面的顺序是BA,B为栈顶
    第二次打开B,这时任务栈里面的顺序还是BA,B为栈顶。

请出终端大哥:

    Running activities (most recent first):
      TaskRecord{52b29134 #21 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{5295bdf8 u0 com.amqr.taskstack/.SecondActivity t21}
        Run #0: ActivityRecord{52a395a8 u0 com.amqr.taskstack/.FirstActivity t21}

生命周期log
简单说可以认为,由于在我们不开新的实例的基础上打开自己,所以就只经历了一个 焦点的失去和获取 的过程。
在SecondActivity中复写onNewIntent方法

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("Cur", "SecondActivity onDestroy");
    }

    // onNewIntent 方法,当被复用时调用
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d("Cur", "SecondActivity onNewIntent");
    }

SecondActivity 的onNewIntent方法被调用了,代表被复用。
(需要注意的是,当我们第二次打开B的时候,因为没有新的实例,所以不存在onCreate和onStart方法的执行,也不存在onStop)

12-03 08:02:05.564 9277-9277/? D/Cur: FirstActicity onCreate
12-03 08:02:05.564 9277-9277/? D/Cur: FirstActivity onStart
12-03 08:02:05.564 9277-9277/? D/Cur: FirstActivity onResume
// 第一次打开B
12-03 08:02:10.000 9277-9277/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 08:02:10.024 9277-9277/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 08:02:10.024 9277-9277/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 08:02:10.024 9277-9277/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 08:02:10.436 9277-9277/com.amqr.taskstack D/Cur: FirstActivity onStop

// 第二次打开B 生命周期明显发生变化,只剩下 onRause和onResume
12-03 08:02:11.720 9277-9277/com.amqr.taskstack D/Cur: SecondActivity onPause
12-03 08:02:11.724 9277-9277/com.amqr.taskstack D/Cur: SecondActivity onNewIntent
12-03 08:02:11.724 9277-9277/com.amqr.taskstack D/Cur: SecondActivity onResume
  • 情况3 ,打开A,打开B,打开C,再打开B

这时虽然B是singleTop,但是由于打开A,打开B,打开C 的步骤走完任务栈里面的顺序是CBA,C是栈顶,因为B不处于栈顶所以不会复用B,此时还是会产生新的B实例进行入栈。

有请终端先生:

    Running activities (most recent first):
      TaskRecord{52a15e50 #23 A=com.amqr.taskstack U=0 sz=4}
        Run #3: ActivityRecord{529d9310 u0 com.amqr.taskstack/.SecondActivity t23}
        Run #2: ActivityRecord{529d1b64 u0 com.amqr.taskstack/.ThirdActivity t23}
        Run #1: ActivityRecord{5297e728 u0 com.amqr.taskstack/.SecondActivity t23}
        Run #0: ActivityRecord{5297b8fc u0 com.amqr.taskstack/.FirstActivity t23}

从上面的终端信息我们知道,B真的还是产生新的实例,没有复用,只要B不在顶部,特技就发挥不出来。

生命周期也是没有什么特别的

12-03 08:10:24.080 11761-11761/com.amqr.taskstack D/Cur: FirstActicity onCreate
12-03 08:10:24.080 11761-11761/com.amqr.taskstack D/Cur: FirstActivity onStart
12-03 08:10:24.080 11761-11761/com.amqr.taskstack D/Cur: FirstActivity onResume
// 第一次打开B
12-03 08:10:26.888 11761-11761/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 08:10:26.908 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 08:10:26.908 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 08:10:26.908 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 08:10:27.340 11761-11761/com.amqr.taskstack D/Cur: FirstActivity onStop

// 打开C
12-03 08:10:27.880 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onPause
12-03 08:10:27.888 11761-11761/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-03 08:10:27.888 11761-11761/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-03 08:10:27.888 11761-11761/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-03 08:10:28.300 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onStop

// 第二次打开B,
12-03 08:10:29.040 11761-11761/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-03 08:10:29.056 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 08:10:29.056 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 08:10:29.056 11761-11761/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 08:10:29.436 11761-11761/com.amqr.taskstack D/Cur: ThirdActivity onStop

.
.

4.3、singleTask 单一任务 (整个任务栈只有一个对应自身的实例)

  结论:如果开启的甲Activity已经存在一个实例在任务栈S1,再去开启这个Activity,位于栈顶则直接复用,回调onNewIntent方法;位于里面,也是复用,回调onNewIntent方法,复用的同时的是直接把自己上方的全部Activity都干掉。

当我们把一个Activity设置为singleTask模式之后,当我们点击开启这个Activity,会出现3种情况:

说明:打开B,A和C是Standard,B是singleTask

之前没开启过:A开启B的时候,B进入A的任务栈。为了顶部

之前开启过情况1:如果现在任务栈情况是BA,B位于栈顶,此时点击B,那么不会创建新的实例,任务栈还是BA,回调onNewIntent方法。

之前开启过情况2:假如现在任务栈情况是CBA,C为了栈顶,那么这时打开B,因为B是singleTask,这时不会创建新的实例,但是肯定会把B置为栈顶(B在回到栈顶的时候不是跳过去的,而是把自己上面的其他Activity全部干掉,这样就只剩下自己和自己下面的Activity了),那么这时任务栈里面的情况就剩下 BA,回调onNewIntent方法.

.
.

情景实测

好啦,情况说完了,现在应该进行实测了。
原先的代码不用改,只需要把SecondActivity指定为singleTask模式,我们这里采用manifes指定

        <activity android:name=".SecondActivity"
            android:launchMode="singleTask"
            />

以下讨论的都是A和C都为Standard,B为singleTask

  • 情况1,打开A,打开B,在打开B。
    最终任务栈的结果是 BA. 其中B是栈顶,而且B只有一个,A是栈底。此时onNewIntent方法会被回调。

这个演示需要有一个图片,明确清楚地知道我们第二次打开B的时候是打不开,没反应的。

singleTask只有一个实例.gif

通过上图,我们知道第二次点击“开启第2个Activity”是没有办法再次打开一个已经采用singleTask而且已经位于顶部的Activity新的实例。因为他就是那里,不会新增一个新的实例。(可以跟Standard的 情况3 做比较)

终端的数据可以证明一切:

 Running activities (most recent first):
      TaskRecord{529f16ec #2 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{52a52380 u0 com.amqr.taskstack/.SecondActivity t2}
        Run #0: ActivityRecord{529bc564 u0 com.amqr.taskstack/.FirstActivity t2}

查看下面的log,对比生命周期。
这里跟单一顶部一样的效果。

12-03 07:11:22.024 11795-11795/com.amqr.taskstack D/Cur: FirstActicity onCreate
12-03 07:11:22.024 11795-11795/com.amqr.taskstack D/Cur: FirstActivity onStart
12-03 07:11:22.024 11795-11795/com.amqr.taskstack D/Cur: FirstActivity onResume
第一次按下打开B
12-03 07:11:30.980 11795-11795/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 07:11:30.992 11795-11795/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 07:11:30.992 11795-11795/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 07:11:30.992 11795-11795/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 07:11:31.384 11795-11795/com.amqr.taskstack D/Cur: FirstActivity onStop
第二次按下打开B
12-03 07:11:48.280 11795-11795/com.amqr.taskstack D/Cur: SecondActivity onPause
12-03 07:11:48.280 11795-11795/com.amqr.taskstack D/Cur: SecondActivity onNewIntent
12-03 07:11:48.280 11795-11795/com.amqr.taskstack D/Cur: SecondActivity onResume
  • 情况2 打开A,打开B,打开C,打开D,再次打开B。
    任务栈最终的情况 BA
    这个情况有意思,当我们第二次打开B的时候,他会把自己上方的全部Activity给干掉,最后只剩下自己和他身下的Activity了。
    说到底也正常,栈的结构看数据结构就知道了,其实也可以联想到现实,子弹夹我们想把倒数第二的子弹打出来,自然要把他前面的两个子弹给清掉。

分两步来吧看个究竟吧
当我们打开A,打开B,打开C,打开D,任务栈里面的情况是这样滴

召唤终端大哥

    Running activities (most recent first):
      TaskRecord{52a36d80 #11 A=com.amqr.taskstack U=0 sz=4}
        Run #3: ActivityRecord{529be7bc u0 com.amqr.taskstack/.FourtActivity t11}
        Run #2: ActivityRecord{5295bdf8 u0 com.amqr.taskstack/.ThirdActivity t11}
        Run #1: ActivityRecord{5294692c u0 com.amqr.taskstack/.SecondActivity t11}
        Run #0: ActivityRecord{52a5f5f8 u0 com.amqr.taskstack/.FirstActivity t11}

就在这时,任务栈的顺序是:DCBA ,其中D是栈顶。
这时我们按下打开B,倒数第二的B 发射出一道闪闪金光,把上方的C和D全歼了,这时任务栈的顺序就只剩下BA了,B为栈顶,好残忍的说。

然后再次召唤终端出场

    Running activities (most recent first):
      TaskRecord{52a15cdc #12 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{5294692c u0 com.amqr.taskstack/.SecondActivity t12}
        Run #0: ActivityRecord{52a66cc8 u0 com.amqr.taskstack/.FirstActivity t12}

这里在查看一下生命周期的log,发现D和C付出了血的代价,直接被onDestroy了。

12-03 07:24:16.536 23760-23760/? D/Cur: FirstActicity onCreate
12-03 07:24:16.536 23760-23760/? D/Cur: FirstActivity onStart
12-03 07:24:16.536 23760-23760/? D/Cur: FirstActivity onResume

12-03 07:24:20.072 23760-23760/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 07:24:20.088 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 07:24:20.088 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 07:24:20.088 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 07:24:20.484 23760-23760/com.amqr.taskstack D/Cur: FirstActivity onStop

12-03 07:24:20.940 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onPause
12-03 07:24:20.956 23760-23760/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-03 07:24:20.956 23760-23760/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-03 07:24:20.956 23760-23760/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-03 07:24:21.352 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onStop

12-03 07:24:21.708 23760-23760/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-03 07:24:21.720 23760-23760/com.amqr.taskstack D/Cur: FourtActivity onCreate
12-03 07:24:21.720 23760-23760/com.amqr.taskstack D/Cur: FourtActivity onStart
12-03 07:24:21.720 23760-23760/com.amqr.taskstack D/Cur: FourtActivity onResume
12-03 07:24:22.128 23760-23760/com.amqr.taskstack D/Cur: ThirdActivity onStop
12-03 07:24:24.400 23760-23760/com.amqr.taskstack D/Cur: ThirdActivity onDestroy

12-03 07:24:24.404 23760-23760/com.amqr.taskstack D/Cur: FourtActivity onPause
12-03 07:24:24.408 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onNewIntent
12-03 07:24:24.408 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 07:24:24.408 23760-23760/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 07:24:24.784 23760-23760/com.amqr.taskstack D/Cur: FourtActivity onStop
12-03 07:24:24.784 23760-23760/com.amqr.taskstack D/Cur: FourtActivity onDestroy

singleTask的谈论至此完毕。

.
.

4.4 singleInstance 单一实例(单例),任务栈里面自已自己一个人

  结论:当启动一个启动模式为singleInstance的Activity时(之前没启动过),这时系统将开辟出另外一个任务栈,用于存放这个Activity,而且这个新的任务栈只能存放自身这唯一一个Activity。singleInstance页面作为前台任务打开自己打开自己,则复用,任务栈顺序无变化;singleInstance页面作为后台任务栈,则切换成为前台任务栈,无新实例产生,复用。

复用就会调用onNewIntent方法。

情景实测

以下的A和B都是Standard,然后C和D我们都通manifest把启动模式设定为 singleInstance

        <activity android:name=".ThirdActivity"

            android:launchMode="singleInstance"
            />
        <activity android:name=".FourtActivity"
            android:launchMode="singleInstance"
            />
  • 情景1:打开A,打开B,打开为singleInstance的C
    打开A,A存在于任务栈S1,此时任务栈里面只有A,
    打开B, B入栈,进入S1,此时S1的里面有两个Activity,顺序为BA,B为栈顶。

打开C,因为C是singleInstance,所以自己的任务栈。很明显,这个时候C不会进入S1的。而是会开辟出来一个全新的任务栈,我们就称为任务栈S2吧,
注意,此时S2属于前台任务栈,S1属于后台任务栈。

如果非要排个需要序号的话,那么就是 (S2-C)、(S1-B)、(S1-A),前面的是老大。

请出终端大哥:

    Running activities (most recent first):
      TaskRecord{52a54d24 #31 A=com.amqr.taskstack U=0 sz=1}
        Run #2: ActivityRecord{5297e728 u0 com.amqr.taskstack/.ThirdActivity t31}
      TaskRecord{52a193f4 #30 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{5297b8fc u0 com.amqr.taskstack/.SecondActivity t30}
        Run #0: ActivityRecord{52a5f120 u0 com.amqr.taskstack/.FirstActivity t30}

别只看A="aaa.bbb.ccc"相同, 注意,#num ,比如#31和#30就不同就代表是两个不同任务栈。#号带的数字是最能区分的标志。我们说的 # 是紧挨着任务栈名称左边的那个#。

生命周期没什么影响

12-03 08:32:27.108 24263-24263/com.amqr.taskstack D/Cur: FirstActicity onCreate
12-03 08:32:27.108 24263-24263/com.amqr.taskstack D/Cur: FirstActivity onStart
12-03 08:32:27.108 24263-24263/com.amqr.taskstack D/Cur: FirstActivity onResume
// 打开B
12-03 08:32:28.828 24263-24263/com.amqr.taskstack D/Cur: FirstActivity onPause
12-03 08:32:28.844 24263-24263/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-03 08:32:28.844 24263-24263/com.amqr.taskstack D/Cur: SecondActivity onStart
12-03 08:32:28.844 24263-24263/com.amqr.taskstack D/Cur: SecondActivity onResume
12-03 08:32:29.236 24263-24263/com.amqr.taskstack D/Cur: FirstActivity onStop
// 打开为 singleInstance 的C
12-03 08:32:29.888 24263-24263/com.amqr.taskstack D/Cur: SecondActivity onPause
12-03 08:32:29.892 24263-24263/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-03 08:32:29.892 24263-24263/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-03 08:32:29.892 24263-24263/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-03 08:32:30.656 24263-24263/com.amqr.taskstack D/Cur: SecondActivity onStop
  • 情景2、打开A,打开B,打开为singleInstance的C,然后按下返回键,第二次按返回键,第三次再按下返回键。

也就是在情景1的条件下再按下返回键。

我们再续前缘接着说。
第一次按下返回键,S2销毁,S1成为前台任务栈,S1任务栈里面的顺序是BA,其中B是栈顶。
界面停留在B界面。

第二次按下返回键,界面退到A界面,S1任务里面B出栈,任务栈里面只剩A。

第三次按下返回键,整个程序退出,任务栈S1销毁。

  • 情景3,打开A,打开B,打开singleInstance的C,C打开C(复用,无新实例),接着打开singleInstance的D,接着D打开C。(复用,无新实例,改变任务栈顺序)
    C和D都复写onNewIntent方法以便观察。

那么这时会产生3个任务栈,A和B所在的为S1,C所在的任务栈为S2,D所所在的任务栈为S3.

步骤一:
打开A,打开B,B打开的C
终端

    Running activities (most recent first):
      TaskRecord{529819f4 #44 A=com.amqr.taskstack U=0 sz=1}
        Run #2: ActivityRecord{529cfc00 u0 com.amqr.taskstack/.ThirdActivity t44}
      TaskRecord{52a05480 #43 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{529c2564 u0 com.amqr.taskstack/.SecondActivity t43}
        Run #0: ActivityRecord{529a6e24 u0 com.amqr.taskstack/.FirstActivity t43}

生命周期

12-05 23:10:11.178 15891-15891/? D/Cur: FirstActicity onCreate
12-05 23:10:11.178 15891-15891/? D/Cur: FirstActivity onStart
12-05 23:10:11.178 15891-15891/? D/Cur: FirstActivity onResume
// 打开B
12-05 23:10:16.166 15891-15891/com.amqr.taskstack D/Cur: FirstActivity onPause
12-05 23:10:16.174 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-05 23:10:16.174 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onStart
12-05 23:10:16.174 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onResume
12-05 23:10:16.558 15891-15891/com.amqr.taskstack D/Cur: FirstActivity onStop
// 第一次打开C
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onPause
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-05 23:10:18.622 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onStop

.
.
步骤2:再次打开C,复用,终端信息一致。就不附上了。
生命周期有所不同。

12-05 23:10:11.178 15891-15891/? D/Cur: FirstActicity onCreate
12-05 23:10:11.178 15891-15891/? D/Cur: FirstActivity onStart
12-05 23:10:11.178 15891-15891/? D/Cur: FirstActivity onResume
// 打开B
12-05 23:10:16.166 15891-15891/com.amqr.taskstack D/Cur: FirstActivity onPause
12-05 23:10:16.174 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onCreate
12-05 23:10:16.174 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onStart
12-05 23:10:16.174 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onResume
12-05 23:10:16.558 15891-15891/com.amqr.taskstack D/Cur: FirstActivity onStop
// 第一次打开C
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onPause
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onCreate
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-05 23:10:17.826 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-05 23:10:18.622 15891-15891/com.amqr.taskstack D/Cur: SecondActivity onStop
// 第二次打开C
12-05 23:10:20.650 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-05 23:10:20.650 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onNewIntent
12-05 23:10:20.650 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onResume

步骤3:C打开D

    Running activities (most recent first):
      TaskRecord{529f9160 #45 A=com.amqr.taskstack U=0 sz=1}
        Run #3: ActivityRecord{529f8ff8 u0 com.amqr.taskstack/.FourtActivity t45}
      TaskRecord{529819f4 #44 A=com.amqr.taskstack U=0 sz=1}
        Run #2: ActivityRecord{529cfc00 u0 com.amqr.taskstack/.ThirdActivity t44}
      TaskRecord{52a05480 #43 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{529c2564 u0 com.amqr.taskstack/.SecondActivity t43}
        Run #0: ActivityRecord{529a6e24 u0 com.amqr.taskstack/.FirstActivity t43}

生命周期

接着上次的LOG

// 第二次打开C
12-05 23:10:20.650 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-05 23:10:20.650 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onNewIntent
12-05 23:10:20.650 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onResume
// C打开D
12-05 23:32:29.242 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-05 23:32:29.246 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onCreate
12-05 23:32:29.246 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onStart
12-05 23:32:29.246 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onResume
12-05 23:32:30.038 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onStop

发现多了一个任务栈。

步骤4 D打开C

终端

    Running activities (most recent first):
      TaskRecord{529819f4 #44 A=com.amqr.taskstack U=0 sz=1}
        Run #3: ActivityRecord{529cfc00 u0 com.amqr.taskstack/.ThirdActivity t44}
      TaskRecord{529f9160 #45 A=com.amqr.taskstack U=0 sz=1}
        Run #2: ActivityRecord{529f8ff8 u0 com.amqr.taskstack/.FourtActivity t45}
      TaskRecord{52a05480 #43 A=com.amqr.taskstack U=0 sz=2}
        Run #1: ActivityRecord{529c2564 u0 com.amqr.taskstack/.SecondActivity t43}
        Run #0: ActivityRecord{529a6e24 u0 com.amqr.taskstack/.FirstActivity t43}

生命周期

接上次的C打开D的Log
// C打开D
12-05 23:32:29.242 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onPause
12-05 23:32:29.246 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onCreate
12-05 23:32:29.246 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onStart
12-05 23:32:29.246 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onResume
12-05 23:32:30.038 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onStop

// D打开C
12-05 23:56:10.402 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onPause
12-05 23:56:10.402 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onNewIntent
12-05 23:56:10.402 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onStart
12-05 23:56:10.402 15891-15891/com.amqr.taskstack D/Cur: ThirdActivity onResume
12-05 23:56:11.154 15891-15891/com.amqr.taskstack D/Cur: FourtActivity onStop

没有新的任务栈产生,前台任务栈发了变化。任务栈里面也不会有新的Activity产生。

singleInstance分析至此结束。

5、另说singleTask的小伙伴:taskAffinity + allowTaskReparenting

说在前面

taskAffinity是singleTask的好伙伴,这是肯定的。

按照官网大概是这么说taskAffinity 和 allowTaskReparenting的:
taskAffinity: 可以指定一个Activity放入哪一个任务栈中,利用taskAffinity制定一个任务栈的名称,把Activity放进这个任务栈中。实现这个过程需要singleTask和allowTaskReparenting两者的协助。

allowTaskReparenting:参数是boolean,如果我们利用taskAffinity让Activity放入一个指定的任务栈,需要allowTaskReparenting的同意,为true就可以跟着别人跑,为false就乖乖在原地呆着那都不许去。

按照的官网的说法,实测后发现,taskAffinity确实可以让Activity跑到指定的任务栈(不用跟app自带的任务栈混了),但是allowTaskReparenting没什么作用,设置或者不设置没什么改变。设置为true,设置为false,或者不设置,一点都没区别。

即是说官方说,taskAffinity要和allowTaskReparenting配合着使用,实测是上不用,taskAffinity单兵作战也是可以的。(个人看法,疏忽之处请熟悉的朋友指点一下)

下面开始正式分析。

5.1、taskAffinity

taskAffinity属性用于给Activity单独指定任务栈名称。这个名称不能和包名相同,否则就没有意义。注意taskAffinity属性值为String,而且中间必须包含有分隔符 . (英文状态下的点),比如com.baidu.test

另外,如果想要指定一个非包名的任务栈,该Activity一定要把启动模式设置为singleTask模式,否则不会生效。如果taskAffinity指定的名称是其他程序的包名,那么可以不结合singleTask。念起来好像有点拗口,看下面的实测就知道怎么回事了。

注意:任务栈分为前台任务栈和后台任务栈,后台任务栈里面的Activity全部处于onStop状态。

在minifest里面,application可以设定taskAffinity,activity也可以设定taskAffinity。
taskAffinity设定的任务栈我们也称其为一个宿主任务栈。

  • application设定

    • applicatipn如果不设定,那么就系统默认设定为包名。如果设定了,activity跟着application走,application指定的是什么activity的任务栈的名称就是什么。(application自带的不设定,一般我们也不手动设定,要设定也是单独在activity里面设定)
  • activity设定
    • 设定taskAffinity之后,当启动这个Activity之后,如果之前没有任务栈的存在,那么就启动系统会开辟出来一个新的任务栈(或者叫宿主任务栈),用于存放这个activity,如果已经存在了这个任务栈,那么这个activity就对应进入已经的宿主任务栈。(设定taskAffinity,不管之前存不存在,反正就不跟默认的混了,自己只认指定的任务栈,当然,如果你非要把taskAffinity指定自己的包名那也没办法,只是那没撒意思嘛)

我们利用taskAffinity+taskAffinity指定非手机里面任何程序的包名的任务栈时,这个任务是可以容纳多个activity的。比如现在有A,B,C三个界面,B和C都启动模式都是taskAffinity,而且taskAffinity指定的都是同一个包名。
那么当我们A开启B,B再开启C的时候,结果就是A在app默认自带的任务栈S1里,而B和C在同一个任务栈S2里面。这是S2里面的顺序是CB,C是栈顶。然后这个时候从C打开B,那么事情来了,本来的C下面的B会再次汇聚天地能量,发出一道闪闪金光,把自己上方的所有Activity全部歼灭,这个时候C就挂了。可见,不管和谁合作,singleTask依旧霸道,不可阻挡,谁挡谁卒。

有代码有真相,先来看一下如何利用taskAffinity给activty指定任务栈:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.newtaskone" >

    <activity android:name=".OtherActivity"
// android:taskAffinity 的使用一般都是有singeTask一起出现的
        android:taskAffinity="com.task.try"

        />

如上,我们的包名是 com.amqr.newtaskone,然后我们给我们的activity指定了宿主任务栈的的名称为 com.task.try 。这样他就和系统默认的任务栈名称不同了。

5.2、allowTaskReparenting

allowTaskReparenting:参数是boolean。
如果我们利用taskAffinity让Activity放入一个指定的任务栈,需要allowTaskReparenting的同意,为true就可以跟着别人跑,为false就乖乖在原地呆着除了自己家那都不许去。

怎么用

        <activity android:name=".OtherActivity"

            android:taskAffinity="com.task.try"
            android:allowTaskReparenting="true"

            />

5.3 实测

情况1和情况2,app2的activity指定的任务栈名称是app1的包名,所以不需要singleTask(这里指定包名是为了演示效果)

  • 情况1:新建两个app。
    app1什么都不改,自带一个MainActivity(布局文件标识一下是app1的Mainactivity)。
    在app2的manifest给app2的MainAcivity添加android:taskAffinity属性,指定包名为 app1的包名。

也就是说,当app2的MainActivity的启动的时候会把app的默认任务栈当做自己的宿主。

我们把两个程序安装一下,清掉其他正在运行的程序
运行app1,然后按下home键(让app1变成后台程序),接着打开app2,我们会发现,这时候进入的不是app2的界面,而是app1的。

先来看一下两个app的Mainactivty本来的面目:

app1的MainActivityUI截图

app1的MainActivityUI截图.png

app2的MainActivityUI截图

app2的MainActivityUI截图.png

接下来看一下各自的manifest

app1的manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskaffinity1" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        >
        <activity android:name=".MainActivity1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

app2的manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskaffinity2" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity2"
            android:taskAffinity="com.amqr.taskaffinity1"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

查看效果:

宿主.gif

终端大哥也显示为只有一个任务栈,那就是app1自带的那个com.amqr.taskaffinity1

    Running activities (most recent first):
      TaskRecord{5296a83c #4 A=com.amqr.taskaffinity1 U=0 sz=1}
        Run #0: ActivityRecord{52983d74 u0 com.amqr.taskaffinity1/.MainActivity1 t4}

生命周期

12-03 22:50:46.324 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onCreate
12-03 22:50:46.324 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onStart
12-03 22:50:46.324 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onResume
// 按下home键
12-03 22:50:48.356 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onPause
12-03 22:50:48.872 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onStop
// 启动app2,结果打开是app1
12-03 22:50:52.868 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onRestart
12-03 22:50:52.868 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onStart
12-03 22:50:52.868 1835-1835/com.amqr.taskaffinity1 D/Cur: MainActivity1, onResume

我们发现,我们点击启动的app2,结果启动的却是app1,而且,在运行列表里面看不到app2的存在,只有一个app1。

里面发生的是这样的一个过程,
当我们启动app1时:app1的任务栈com.amqr.taskaffinity1 正常作为一个任务栈进入系统,
按下home键时:任务栈 com.amqr.taskaffinity1 成为了一个后台任务栈
点击app2时: 本来应该打开的是app2的界面,但是要打开的MainActivity2发现自身含有android:taskAffinity,而指定的宿主任务栈就是app1打开后就存在的任务栈 com.amqr.taskaffinity1 ,那么这时候 com.amqr.taskaffinity1 就成为了 MainActivity2 的宿主任务栈。
按道理,这个时候本来应该是 MainActivity2 入栈而且作为栈顶的啊,但是事实却没有这么发生。也就是说,这个时候,MainActivity2没有入栈,对的,没有入栈。他是跑过来搞笑的,通知了一下别人可以变成前台任务栈了,然后自己没有入栈,具体详情,不得而知。
实际上发生的事情是:com.amqr.taskaffinity1 由后台任务栈转为前台任务栈,而那个MainActity没有入栈,从终端的信息就可以看得出来。

有人说,你没按谷歌的来,没有把allowTaskReparenting设置为true,好,那我们进行其概况2,接着来一遍。

  • 情况2 还是上面的代码,我们只是app2的manifest里面给MainActivity加上一句

android:allowTaskReparenting="true"

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskaffinity2" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity2"
            android:taskAffinity="com.amqr.taskaffinity1"
            android:allowTaskReparenting="true"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

我们重新安装两个程序,接着来一遍。
(清除所有运行的程序,打开app1,按下home,打开app2)

宿主情况2.gif

结果发现一样,还是贴一下对应的终端信息吧

    Running activities (most recent first):
      TaskRecord{52992bc4 #10 A=com.amqr.taskaffinity1 U=0 sz=1}
        Run #0: ActivityRecord{52a97bdc u0 com.amqr.taskaffinity1/.MainActivity1 t10}

一个样,其实就算设置为false也是没有变化,有兴趣的可以自己试一下。

注意:宿主不是绝对化的,当两个app都认定一个宿主后,就先来后到了,谁先开启那个任务栈谁是老大。比如说,我们在可以清掉所有运行的程序,先开启app2,然后再开启app1,我们会发现打开的是app2,就不是app1了。

app2宿主.gif

  • 情况三:
    情况三不是指定程序的包名作为指定的任务栈名称,所以需要启动的模式singleTask一起工作。(如果不跟singleTask合作,又不指定为程序的包名,那么设置android:taskAffinity不生效)

情况三我们这样弄,app1和app2各自都有两个页面,各自的启动页都是普通常见的,然后各自的都有另外一个其他页面,这分属于两个app的其他页面我们都给他们指定同一个任务栈名(这个任务站名不跟任意一个app包名相同)

也就是说,app1里面有MainActivity1和App1Other两个Activity,
app2里面有MainActivity2和App2Other两个Activity

MainActivity1和MainActivity2都是普通的Standard
而App1Other和App2Other都在清单文件指定了自定义的任务栈名称和singelTask启动模式。详见下方代码

我们这样操作:
第一阶段:先打开app1,再打开App1Other页面,接着按下home键
第二阶段:(接按下home件之后)打开app2,打开app2的App2Other页面

先贴上相关图片和代码先:

Paste_Image.png

Paste_Image.png

Paste_Image.png

Paste_Image.png

app1的manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskaffinity1" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        >
        <activity android:name=".MainActivity1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".App1Other"

            android:taskAffinity="com.amqr.independent"
            android:launchMode="singleTask"

            />
    </application>

</manifest>

.
.
app2的manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskaffinity2" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity2"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".App2Other"
            android:taskAffinity="com.amqr.independent"
            android:launchMode="singleTask"
            />
    </application>

</manifest>
  • 第一阶段,
    我们先打开app1,然后打开的app1Other界面。按下home键

效果如下图:打开第一个activity,然后再点击打开app1Other,按下home键盘,查看运行列表,会看见变成了两个任务栈(任务栈是两个,程序还是一个,进程更加只是一个)

taskAffinity1.gif

任务栈信息

    Running activities (most recent first):
      TaskRecord{52a8eab0 #48 A=com.amqr.independent U=0 sz=1}
        Run #1: ActivityRecord{52ae1518 u0 com.amqr.taskaffinity1/.App1Other t48}
      TaskRecord{52a9cd20 #47 A=com.amqr.taskaffinity1 U=0 sz=1}
        Run #0: ActivityRecord{52a9f230 u0 com.amqr.taskaffinity1/.MainActivity1 t47}
  Recent tasks:
  * Recent #0: TaskRecord{52973240 #52 A=com.amqr.independent U=0 sz=1}
  * Recent #1: TaskRecord{52979df8 #51 A=com.amqr.taskaffinity1 U=0 sz=1}
  * Recent #2: TaskRecord{5295e6f8 #1 A=com.android.launcher U=0 sz=1}
  * Recent #3: TaskRecord{52a90f20 #46 A=com.android.systemui U=0 sz=0}

生命周期

12-04 01:53:51.808 18259-18259/? D/Cur: MainActivity1, onCreate
12-04 01:53:51.808 18259-18259/? D/Cur: MainActivity1, onStart
12-04 01:53:51.808 18259-18259/? D/Cur: MainActivity1, onResume

12-04 01:53:55.704 18259-18259/com.amqr.taskaffinity1 D/Cur: MainActivity1, onPause
12-04 01:53:55.708 18259-18259/com.amqr.taskaffinity1 D/Cur: App1Other, onCreate
12-04 01:53:55.708 18259-18259/com.amqr.taskaffinity1 D/Cur: App1Other, onStart
12-04 01:53:55.708 18259-18259/com.amqr.taskaffinity1 D/Cur: App1Other, onResume
12-04 01:53:56.468 18259-18259/com.amqr.taskaffinity1 D/Cur: MainActivity1, onStop

从上面的图文我们知道,
当我们点击app1时,默认的用的是系统的根据包名定的默认任务栈,这个没什么可说的。

但是当我们点击打开 app1Other 页面时,就多出来一个任务栈,栈名是我们指定的com.amqr.independent。

而且观察运行程序的列表,发现多了个图标,但是要知道,这个绝对不是多了一个进程,只是我们每多产生多一个任务栈,任务列表就会多出来一个图标。

一个程序的两个任务栈.png

进程不变

Paste_Image.png

  • 第二阶段

紧接上面的环节
接下来我们来打开app2,然后打开app2的 app2Other 界面
(完整的操作:打开app1,打开app1Other界面,按下home,打开app2,打开app2的Other界面)

终端信息

    Running activities (most recent first):
      TaskRecord{52a2e064 #62 A=com.amqr.independent U=0 sz=2}
        Run #3: ActivityRecord{529d1550 u0 com.amqr.taskaffinity2/.App2Other t62}
      TaskRecord{52a337a0 #63 A=com.amqr.taskaffinity2 U=0 sz=1}
        Run #2: ActivityRecord{5299485c u0 com.amqr.taskaffinity2/.MainActivity2 t63}
      TaskRecord{52a2e064 #62 A=com.amqr.independent U=0 sz=2}
        Run #1: ActivityRecord{529827a0 u0 com.amqr.taskaffinity1/.App1Other t62}
      TaskRecord{528a1e60 #61 A=com.amqr.taskaffinity1 U=0 sz=1}
        Run #0: ActivityRecord{5296bc68 u0 com.amqr.taskaffinity1/.MainActivity1 t61}

程序截图

Paste_Image.png

我们发现了,
图片的任务列表是3个,终端的日志任务栈看起来像出现了4个,其实是3个任务栈,因为有两个是重复的,为什么重复了呢?

我们先来看一下这里面的两句:
TaskRecord{52a2e064 #62 A=com.amqr.independent U=0 sz=2}
TaskRecord{52a2e064 #62 A=com.amqr.independent U=0 sz=2}
我们通过名字com.amqr.independent可以知道这肯定是同一个任务栈
其实这个sz后面表示的就是activity的个数(已测)
至于为什么分开显示,这个应该是终端显示的一种表示方法,终端的具体规则不太清楚。这里不是我们深究的方向。到了这里明白运行的是3个任务栈就好了。

我们这里了明白singleTask + taskAffinity是如何使用的就好了。

  • 情况4

app1里面弄3个activity,MainActivity,App1Other,App3Activity,分别用A,B,C指代,A是Standard的启动页,B和C都是single + taskAffinity且指定的任务栈名称相同(不跟任何程序包名相同)。

操作过程,
第一阶段:运行app1,启动页A启动B,

第二阶段:
再通过B启动C
最后我们发现,在singleTask + taskAffinity模式下还是谁当灭灭谁,在它前面的全部遭殃。

贴上代码和相关图片:

manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.amqr.taskaffinity1" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        >
        <activity android:name=".MainActivity1">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".App1Other"

            android:taskAffinity="com.amqr.independent"
            android:launchMode="singleTask"

            />

        <activity android:name=".App3Activity"

            android:taskAffinity="com.amqr.independent"
            android:launchMode="singleTask"

            />
    </application>

</manifest>

第一阶段:

A开启B,B开启C,这时候我们的指定任务栈里面的顺序就是BC

两个taskAffinity.gif

第一阶段终端信息

    Running activities (most recent first):
      TaskRecord{52983174 #116 A=com.amqr.independent U=0 sz=2}
        Run #2: ActivityRecord{5290923c u0 com.amqr.taskaffinity1/.App3Activity t116}
        Run #1: ActivityRecord{52a80e58 u0 com.amqr.taskaffinity1/.App1Other t116}
      TaskRecord{5299bf74 #115 A=com.amqr.taskaffinity1 U=0 sz=1}
        Run #0: ActivityRecord{52a32b30 u0 com.amqr.taskaffinity1/.MainActivity1 t115}

第一阶段结束。

.
.
第二阶段:

紧接着上面的操作,在上面的操作里面我们的指定的任务栈里面已经有了两个都是singleTask的任务在里面了,B在C的下面,C是栈顶,这个时候我们从C开启B,结果C卒,挂了。

两个taskAffinity,消灭上方.gif

第二阶段终端信息:

    Running activities (most recent first):
      TaskRecord{5295e6f8 #1 A=com.android.launcher U=0 sz=1}
        Run #1: ActivityRecord{5295cb64 u0 com.android.launcher/com.android.launcher2.Launcher t1}
      TaskRecord{52abf0cc #117 A=com.android.systemui U=0 sz=1}
        Run #0: ActivityRecord{5290923c u0 com.android.systemui/.recent.RecentsActivity t117}

本篇就先说道这里吧,下次说标志位—— FLAG.

在某些情况下,Android需要知道一个Activity属于哪个Task,即使它没有被启动到一个具体的Task里。这是通过任务共用性(Affinities)完成的。任务共用性(Affinities)为这个运行一个或多个Activity的Task提供了一个独特的静态名称,默认的一个活动的任务共用性(Affinity)是实现了该Activity的.apk包的名字。

当开始一个没有Intent.FLAG_ACTIVITY_NEW_TASK标志的Activity时,任务共用性affinities不会影响将会运行该新活动的Task:它总是运行在启动它的Task里。但是,如果使用了NEW_TASK标志,那么共用性(affinity)将被用来判断是否已经存在一个有相同共用性(affinity)的Task。如果是这样,这项Task将被切换到前面而新的Activity会启动于这个Task的顶层。

这种特性在您必须使用NEW_TASK标志的情况下最有用,尤其是从状态栏通知或桌面快捷方式启动活动时。结果是,当用户用这种方式启动您的应用程序时,它的当前Task将被切换到前台,而且想要查看的Activity被放在最上面。

你可以在程序清单(Manifest)文件的应用程序application标签中为.apk包中所有的活动分配你自己的任务共用性Affinites,或者在活动标记中为各个活动进行分配。

时间: 2024-11-18 09:49:52

TaskRecord分析的相关文章

【Android】应用程序启动过程源码分析

在Android系统中,应用程序是由Activity组成的,因此,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细分析应用程序框架层的源代码,了解Android应用程序的启动过程. 启动Android应用程序中的Activity的两种情景:其中,在手机屏幕中点击应用程序图标的情景就会引发Android应用程序中的默认Activity的启动,从而把应用程序启动起来.这种启动方式的特点是会启动一个新的进程来加载相应的Activity. 这里,我们以这个例子为例来说明

Activity的launchMode详细分析

在研究了Activity的启动过程后,我觉得很有必要对Activity的launchMode进行分析一下,因为到目前为止,我发现网上对launchMode的讲解都是通过实例讲解,看完了总是似懂非懂的感觉,并没有根本上理解launchMode的原理.这里我会从源码的角度讲解launchMode.相信大家会和我一样,看了源码之后就会有一种豁然开朗的感觉. Activity的启动模式一种有四种,分别如下: 1.standard 2.singleTop 3.singleTask 4.singleInst

Activity的启动流程分析

Activity是Android应用程序的四大组件之一,负责管理Android应用程序的用户界面,一般一个应用程序中包含很多个Activity,他们可能运行在一个进程中,也可能运行在不同的进程中. 我们主要通过启动在不同进程中的Activity,来分析Activity的启动流程及AMS对Activity的管理逻辑. 有两个应用程序App1和App2,在App1的Activity A中点击button 启动 App2中的Activity B. 通过分析以上ActivityB的启动过程来了解AMS对

Activity启动流程源码分析之Launcher启动(二)

1.前述 在前一篇文章中我们简要的介绍Activity的启动流程Activity启动流程源码分析之入门(一),当时只是简单的分析了一下流程,而且在上一篇博客中我们也说了Activity的两种启动方式,现在我们就来分析其中的第一种方式--Launcher启动,这种启动方式的特点是会创建一个新的进程来加载相应的Activity(基于Android5.1源码). 2.Activity启动流程时序图 好啦,接下来我们先看一下Launcher启动Activity的时序图: 好啦,接下来我们将上述时序图用代

Android应用程序启动过程源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6689748 前文简要介绍了Android应用程序的Activity的启动过程.在Android系统中,应用程序是由Activity组成的,因此,应用程 序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细分析应用程序框架层的源代码,了解Android应用程序的启动过程. 在上一篇文章Android应用程序的Activit

Android4.4 framework分析——ActivityManagerService的启动和对Activity的管理

本文主要介绍android4.4中ActivityManagerService的启动和ActivityManagerService对Activity堆栈的管理. 一.ActivityManagerService的启动 ActivityManagerService也是在SystemServer启动的时候创建的, <span style="font-size:18px;">class ServerThread { .......   public void initAndLoo

Android startActivity原理分析(基于Android 8.1 AOSP)

应用进程内 如何使用Intent做Activity的跳转 Intnet intent = new Intent(MainActivity.this,TestActivity.class); startActivity(intent); 我们通常会这样写,就能跳转到TestActivity中,但是你知道这简简单单的两行代码在我们庞大的安卓系统中经历了怎样的加工处理吗?接下来我带大家深入到系统内了解整个的启动过程 这里调用了startActivity的函数,这个函数被声明在了Context类中,然后

爱奇艺、优酷、腾讯视频竞品分析报告2016(一)

1 背景 1.1 行业背景 1.1.1 移动端网民规模过半,使用时长份额超PC端 2016年1月22日,中国互联网络信息中心 (CNNIC)发布第37次<中国互联网络发展状况统计报告>,报告显示,网民的上网设备正在向手机端集中,手机成为拉动网民规模增长的主要因素.截至2015年12月,我国手机网民规模达6.20亿,有90.1%的网民通过手机上网. 图 1  2013Q1~2015Q3在线视频移动端和PC端有效使用时长份额对比 根据艾瑞网民行为监测系统iUserTracker及mUserTrac

Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量)

原文:http://www.cnblogs.com/heshan664754022/archive/2013/03/27/2984357.html Tomcat启动分析(我们为什么要配置CATALINA_HOME环境变量) 用文本编辑工具打开用于启动Tomcat的批处理文件startup.bat,仔细阅读.在这个文件中,首先判断CATALINA_HOME环境变量是否为空,如果为空,就将当前目录设为CATALINA_HOME的值.接着判断当前目录下是否存在bin\catalina.bat,如果文件