Android-Activity所应该了解的大概就这样。(中)

本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。

Android-Activity所应该了解的大概就这样。(上)
Android-Activity所应该了解的大概就这样。(下)

五、任务栈/Activity的启动模式

知道生命周期、线程的优先级和Activity的异常销毁,下面我们来认识一下任务栈。

我们上面进行的那么多描述和代码,都是在standard这种默认的任务栈进行的。

栈的概念这里就不再赘述,这里知道先进后出就好。就像子弹夹。

不同的任务栈,也称为不同的启动模式。

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,这时候我们的指定任务栈里面的顺序就是CB


两个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.

对5.2情况3有兴趣的可以参考这里Android-Activity5.3情景3之完整Log参考
完。

推荐阅读更多精彩内容