Activity launchmode和Intent flag详解

时间:2021-08-31 09:06:32

学习安卓,首先就要接触和学习Activity,想必大家在学习activity的过程中一定对activity的launchmode有过困惑。好在网络上关于activity launchmode的博客、解释一大堆,可以方便我们去理解和使用activity launchmode模式,但我强烈建议大家还是要自己动手实践做一些例子,这样才能真正理解activity的launchmode,而且经过实践后你会发现,原来网上的那些解释有很多都是错的、过时的,通过亲自实践得出的结论才是真正被理解的,正确的。

闲话少说,我们来看下官方文档列出的四种Activity的启动模式:


“standard”

系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的task,并且一个task可以拥有多个实例。

“singleTop”

如果当前task的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。

“singleTask”

系统创建新task并实例化将其置于新task底部。但是,如果该 Activity 的一个实例已存在于任何一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。

“singleInstance”

与 “singleTask” 相同,只是系统不会将任何其他 Activity 启动到包含singleInstance实例的task中。该 Activity 始终是其task唯一仅有的成员。


好了,这些是官方文档的说明,这里我要强调两个概念:任务 task 和 返回栈(任务栈)back stack 。任务是指与用户交互的一系列 Activity的集合, 这些Activity 按照各自的打开顺序排列在一个返回栈中,而返回栈(任务栈)则是一个栈,存储一个或多个任务。这里千万不要搞混,有些朋友认为二者是一个东西,就是一个“任务栈”,例如,在singleTask模式中,会新建一个“任务”,而不是新建一个“任务栈”,这个任务在不在一个新的任务栈里还有待商榷,下文会详细介绍。

standard模式自然不用多说,也没什么争议,只要通过intent进行启动,就会创建一个新Activity实例,即使是在intent Flag设定为FLAG_ACTIVITY_CLEAR_TOP时,也会将原有的activity实例干掉,重新创建一个activity。singleTop也好理解,如果当前task的顶部存在该activity的实例就不会创建新的实例,而是调用activity的onNewIntent方法。
关于singleTask,网上的说法则有很多种,很多说法是当activity不存在任何一个任务中时,采用singleTask方法启动会单独建立一个任务栈,activity放在新任务栈的栈底。这种说法不对的,首先,在activity中有个taskAffinity(任务亲和力)的概念,官方文档对taskAffinity的说法是:除有特殊情况(conceptually 概念上,我不知道咋翻译)有相同taskAffinity的activity属于同一个任务(Activities with the same affinity conceptually belong to the same task)。而taskAffinity的优先级要比singleTask、Intent.FLAG_ACTIVITY_NEW_TASK要高,当两个activity有相同的taskAffinity时,singleTask并不会新建一个任务栈,它只是当该activity不存在于任何一个任务中时,在当前任务栈中新建一个activity,新建立的activity和当前app原有的任务在一个相同的任务栈中,当新建的activity的taskaffinity与之前不同时,才会新建返回栈并将activity放置其中。如果该Activity的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent。而singleInstance不同,它“无视”taskAffinity,将activity启动到新的任务栈中,并且该 Activity 始终是其任务唯一仅有的成员。这里我们可以做个小例子验证一下:

这里我创建了三个Activity:Activity1 、Activity2、Activity3。Activity1中包含了一个button,会跳转到Activity2,Activity2有两个button,一个跳到Activity1,一个会跳到Activity3。Activity3和Activiyt1一样,只包含了一个button,可以跳转到Activity2。代码和布局如下:
Activity1:

public class Activity1 extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_task1);
        Log.v("lzq", "activity 1 onCreate");
        Log.v("lzq", "activity 1 is task root? " + isTaskRoot());
    }

    public void toActivity2(View view) {
        startActivity(new Intent(this, Activity2.class));
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.v("lzq", "activity 1 onNewIntent");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.v("lzq", "activity 1 onResume");
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v("lzq", "activity 1 onDestroy");
    }
}

布局:
Activity launchmode和Intent flag详解

Activity2:

public class Activity2 extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_task2);
        Log.v("lzq", "activity 2 onCreate");
        Log.v("lzq", "activity 2 is task root? " + isTaskRoot());
    }

    public void toActivity3(View view) {
        startActivity(new Intent(this, Activity3.class));
    }

    public void toActivity1(View view) {
        startActivity(new Intent(this, Activity1.class));
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.v("lzq", "activity 2 onNewIntent");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.v("lzq", "activity 2 onResume");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v("lzq", "activity 2 onDestroy");
    }
}

Activity2布局:
Activity launchmode和Intent flag详解
Activity3:

public class Activity3 extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_task3);
        Log.v("lzq", "activity 3 onCreate");
        Log.v("lzq", "activity 3 is task root? " + isTaskRoot());
    }

    public void toActivity2(View view) {
        startActivity(new Intent(this, Activity2.class));
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.v("lzq", "activity 3 onNewIntent");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.v("lzq", "activity 3 onResume");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v("lzq", "activity 3 onDestroy");
    }
}

Activity3布局:
Activity launchmode和Intent flag详解
mainfests.xml配置如下:

     <activity  android:name=".singletask.Activity1" android:launchMode="standard" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".singletask.Activity2" android:launchMode="singleTask" >
        </activity>
        <activity android:name=".singletask.Activity3" android:launchMode="singleInstance" >
        </activity>

注意,Activity1 launchmode为standard,Activity2为singleTask,Activity3的launchmode为singleInstance。进入app会打印以下log:

12-03 14:59:06.097 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 onCreate
12-03 14:59:06.117 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 is task root? true
12-03 14:59:06.117 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 onResume

Activity中的isTaskRoot()方法用于判断是否当前的activity在任务栈的栈底,可见,Activity1位于任务栈的栈底,点击返回键时,该app会退出。当点击toActivity2 button时:

12-03 15:06:46.587 12823-12823/lizongquan.test.com.test4 V/lzq: activity 2 onCreate
12-03 15:06:46.587 12823-12823/lizongquan.test.com.test4 V/lzq: activity 2 is task root? false
12-03 15:06:46.587 12823-12823/lizongquan.test.com.test4 V/lzq: activity 2 onResume

可见,Activity2不位于任务栈的栈底,它与Activity1位于同一个任务栈中。也就是说,在taskAffinity相同的情况下,当activity不在任何一个task中存在时,singleTask启动模式并不会为其新建立一个任务栈,只会在当前的任务栈中新建这个activity。再次点击toActivity1按钮可以发现:

12-03 15:12:46.157 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 onCreate
12-03 15:12:46.157 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 is task root? false
12-03 15:12:46.157 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 onResume

Log显示系统又新建立了个Activity1实例,它在任务栈的位置位于activity2之上。这也验证了以standard模式启动,不论该activity实例是否存在,系统都会创建新实例。这时页面已经在Activity1中,我们再次点击toActivity2:

12-03 15:18:32.967 12823-12823/lizongquan.test.com.test4 V/lzq: activity 2 onNewIntent
12-03 15:18:32.967 12823-12823/lizongquan.test.com.test4 V/lzq: activity 2 onResume
12-03 15:18:33.347 12823-12823/lizongquan.test.com.test4 V/lzq: activity 1 onDestroy

可以看到,activity2执行了它的onNewIntent方法,没有创建新的实例,并将activity2任务栈上面的activity1干掉。然后点击Activity3:

12-03 15:24:53.707 6339-6339/lizongquan.test.com.test4 V/lzq: activity 3 onCreate
12-03 15:24:53.707 6339-6339/lizongquan.test.com.test4 V/lzq: activity 3 is task root? true
12-03 15:24:53.707 6339-6339/lizongquan.test.com.test4 V/lzq: activity 3 onResume

可以看到,activity3的isTaskRoot = true,证明singleInstance会将activity放入新的任务栈中,并不会与之前的task放在一个任务栈中,而当我们点击toActivity2的时候:

12-03 15:27:22.067 6339-6339/lizongquan.test.com.test4 V/lzq: activity 2 onNewIntent
12-03 15:27:22.067 6339-6339/lizongquan.test.com.test4 V/lzq: activity 2 onResume

可见,Activity3并没有销毁,这也再次说明了Activity3并没有和Activity2位于一个任务栈中。

上面描述的都是activity具有相同taskAffinity情况。在不同的taskAffinity情况下singleTask表现会截然不同,在这里我将Activity2和Activity3都设置为不同的taskAffinity,并且Activity2为singleTask的,Activity3换成了standard,其它代码不变,mainifests.xml配置如下:

    <activity  android:name=".singletask.Activity1" android:launchMode="standard" android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>
        <activity android:name=".singletask.Activity2" android:launchMode="singleTask" android:taskAffinity="lizongquan.test.com.test5" >
        </activity>
        <activity android:name=".singletask.Activity3" android:launchMode="standard" android:taskAffinity="lizongquan.test.com.test6" >
        </activity>

从Activity1点击toActivity2,log显示:

12-08 14:49:25.286 12010-12010/lizongquan.test.com.test4 V/lzq: activity 1 onCreate
12-08 14:49:25.286 12010-12010/lizongquan.test.com.test4 V/lzq: activity 1 is task root? true
12-08 14:49:25.286 12010-12010/lizongquan.test.com.test4 V/lzq: activity 1 onResume
12-08 14:49:37.376 12010-12010/lizongquan.test.com.test4 V/lzq: activity 2 onCreate
12-08 14:49:37.376 12010-12010/lizongquan.test.com.test4 V/lzq: activity 2 is task root? true
12-08 14:49:37.376 12010-12010/lizongquan.test.com.test4 V/lzq: activity 2 onResume

可以看到,activity 2 的isTaskRoot为true,说明Activity2被放在单独的一个任务栈里了。再点击toActivity3:

12-08 14:51:35.596 12010-12010/lizongquan.test.com.test4 V/lzq: activity 3 onCreate
12-08 14:51:35.596 12010-12010/lizongquan.test.com.test4 V/lzq: activity 3 is task root? false
12-08 14:51:35.596 12010-12010/lizongquan.test.com.test4 V/lzq: activity 3 onResume

可见,虽然Activity3属于不同的taskAffinity,Activity3并没有开辟一个新的任务栈,事实上,因为我们是通过Activity2调用的Activity3,所以Activity3与Activity2会在一个任务栈中,点击toActivity2进行验证:

12-08 14:55:45.516 12010-12010/lizongquan.test.com.test4 V/lzq: activity 2 onNewIntent
12-08 14:55:45.516 12010-12010/lizongquan.test.com.test4 V/lzq: activity 2 is task root? true
12-08 14:55:45.516 12010-12010/lizongquan.test.com.test4 V/lzq: activity 2 onResume
12-08 14:55:45.886 12010-12010/lizongquan.test.com.test4 V/lzq: activity 3 onDestroy

可以看到,Activity3被干掉了(被clear top)。证明Activity3与Activity2在一个任务栈中。

总结:创建新Activity时是否创建新的任务栈由activity的launchmode(或调用activity的Intent Flag)决定,如果launchmode为singleTask模式或singleInstance,则会“倾向”于建立一个新的任务栈。当launchmode为singleTask模式时,taskAffinity优先级会更高,它来决定是否会建立一个新的任务栈,如新的activity taskAffinity与之前的相同,则不会建立新任务栈,taskAffinity与之前不同则建立新的任务栈。而singleInstance模式无视taskAffinity,建立新的任务栈,不管taskAffinity如何,standard模式和singleTop模式都不会建立新的任务栈。

Intent Flag分析

在分析Intent Flag之前,不得不提的是,android关于activity和task还存在许多bug(Android has TONS of bugs related to activities and tasks.)建议大家在遇到关于task问题的时候,优先去*查看前人的教训以免入坑。

与activity launchmode有关的Intent Flag有如下几种:

FLAG_ACTIVITY_CLEAR_TOP
如果设置了FLAG_ACTIVITY_CLEAR_TOP,并且这个Activity已经在当前的Task中运行,这个activity不会被重新加载,而是关闭该Activity上方的所有Activity,然后将这个Intent发送给这个activity。
FLAG_ACTIVITY_SINGLE_TOP

 /**
     * If set, the activity will not be launched if it is already running
     * at the top of the history stack.
     */

可见,FLAG_ACTIVITY_SINGLE_TOP与launchmode的singleTop功能相同。

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_NEW_TASK 的作用效果与singleTask类似,都会判断调用Activity的实例是否存在,如存在则不会新建立实例,而是发送intent给该activity,不存在则会建立一个新的实例。但不同的是,FLAG_ACTIVITY_NEW_TASK不会将被调用activity上层的activity干掉,它与FLAG_ACTIVITY_CLEAR_TOP 联用就可以达到类似singleTask的效果。

值得注意的是,与singleTask一样,FLAG_ACTIVITY_NEW_TASK的优先级要比taskAffinity低,相同的taskAffinity采用FLAG_ACTIVITY_NEW_TASK调用并不会新建立任务栈。此外,采用FLAG_ACTIVITY_NEW_TASK时,为了不创建新的实例而是调用onNewIntent()方法,还需加上FLAG_ACTIVITY_SINGLE_TOP标记(见我的博客——Activity onNewIntent方法浅析)。所以,要达到singleTask的效果我们需要这么做:

Intent intent = new Intent(this,OtherActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);