Android——Activity生命周期与启动模式

来源:互联网 发布:java调用scala程序 编辑:程序博客网 时间:2024/06/08 08:48

本章主要介绍内容:

  • Android的生命周期分析
    • 典型情况下的生命周期分析
    • 异常情况下的生命周期分析
  • Activity的启动模式
    • Activity的LaunchMode
    • Activity的Flags
  • IntentFilter的匹配规则

Android的生命周期分析

典型情况下的生命周期分析

在正常情况下,Activity会经历如下生命周期:

  • onCreate :表示Activity正在被创建,这是生命周期的第一个方法,在这个方法中我们可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需要的数据
  • onRestart :表示Activity正在重新启动,一般情况下,当当前的Activity从不可见重新变为可见状态是,onRestart方法会被调用。这种情况一般是用户行为导致的,比如用户按Home键回到桌面或者用户新打开一个Activity,这时当前的Activity就会被暂停,也就是会执行onPause和onStop方法,接着用户又回到这个Activity,就会执行onRestart方法
  • onStart :表示Activity正在被启动,这时的Activity已经处于可见状态,但是并不具备和用户交互的能力。这个时候我们可以认为Activity已经显现出来了,只是我们还看不到
  • onResume :表示Activity已经可以看到了,并且已经具备了可以和用户交互的能力。这里需要注意和onStart方法的区别,onStart和onResume虽然都表示已经可见,但是onStart的时候其实Activity还在后台,而onResume的时候才是真正的处于前台
  • onPause :表示Activity正在停止,和OnResume对应,正常情况下当Activity处于不可见状态时,该方法会调用,而随后onStop方法会调用,在特殊情况下,如果这个时候快速的回到当前Activity,那么会直接跳过onStart方法直接调用onResume方法,这种情况属于极端情况,用户操作很难重现这一场景。在该方法中可以做一些存储数据、停止动画等不是太耗时的操作,因为这会影响到新的Activity的显示,只有在onPause方法执行完毕后onResume方法才会被执行
  • onStop :表示Activity即将停止,和onStart对应,和onPause方法一样可以用来做一些稍微重量级的回收工作,但是同样不能太耗时
  • onDestory :表示Activity即将被销毁,这是Activity生命周期中的最后一个方法,在这个方法中我们可以做一些回收工作和资源的释放

下面给出Activity的生命周期图:
这里写图片描述

典型情况下生命周期方法举例:

  • 针对上图,它的周期方法执行如下:
    • (1)第一次启动,会执行:onCreate –> onStart –> onResume
    • (2)当用户打开新的Activity时,生命周期方法回调如下: onPause –>onCreate –> onStart –> onResume –>onStop(上一个Activity的onStop)
    • (3)当用户打开一个新的Activity(该Activity采用了透明主题)时,生命周期方法回调如下: onPause –>onCreate –> onStart –> onResume 不会在调用上一个Activity的onStop方法
    • (4)当用户回到桌面时,生命周期方法回调如下:onPause –> onStop
    • (5)当用户再次回到原来的Activity时,生命周期方法回调如下:onRestart –> onStart – onResume
    • (6)当用户按back键回退到桌面时,生命周期方法回到如下:onPause –> onStop –> onDestory。
    • (7)当Activity被系统回收后,再次打开当前Activity,此时生命周期方法调用顺序和(1)一样,注意只是生命周期方法一样,不代表所以过程也一样
    • (8)从整个生命周期来说,onCreate和onDestory是配对的,分别标识者Activity的创建和销毁,并且只可能调用一次。从Activity是否可见来说,onStart和onStop是配对的,随着用户的操作或者设备的点亮和熄灭,这两个方法可能会被调用多次。从Activity是否存于前台来说,onResume和onPause是配对的,随着用户操作或者设备的点亮和熄灭,这两个方法可能会被调用多次。

异常情况下的生命周期分析

  • 1、情况1:资源相关的系统配置发生改变导致的Activity被杀死并重新创建
    比如说:当前的Activity处于竖屏状态,如何突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity。在默认情况下,如果我们的Activity不做任何处理,那么当我们的系统配置发生改变后,Activity就会被销毁并重新创建

其生命周期图:

这里写图片描述

当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestory、方法均会被调用,由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState方法来保存当前Activity的状态。这个方法的调用时机是在onStop之前,它和onPause没有既定的时序关系,它既可能在onPause执行前调用,也可能在onPause之后调用。需要强调一点的是,这个方法只会在Activity被异常终止的情况下,正常情况下系统不会调用这个方法。当Activity被重新创建后,系统会自动调用onRestoreInstanceState方法,并且把Activity销毁时onSaveInstanceState方法中所保留的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。因此我们可以通过OnRestoreInstanceState和onCreate方法来判断Activity是否被重新创建了,如果被重新创建了,那么我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState方法的调用时机在onStart方法之后

既然onRestoreInstanceState和onCreate这两个方法都可以用来做数据的恢复操作,那么他们两个方法有什么区别呢?

二者的区别:onRestoreInstanceState一旦被调用,其参数Bundle savedInstanceState一定是有值的,我们不用额外地判断是否为null;但是onCreate方法不行,onCreate如果是正常启动的情况下,其参数Bundle savedInstanceState一定为null;所以需要我们额外判断,所以在选择数据恢复时官方文档的建议是采用onRestoreInstanceState方法去恢复数据

关于横竖屏切换时生命周期的执行顺序以及处理方法请参照这两篇博客Activity 的生命周期 以及横屏竖屏切换时 Activity 的状态变化 和 Android 屏幕旋转生命周期以及处理方法

  • 2、情况2:资源内存不足导致低优先级的Activity被杀死

之所以出现这种情况是因为:当系统内存不足时,系统会按照一种优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState方法来存储和恢复数据

而Android系统释放资源的优先顺序可以按照下面几种来依次进行释放:

1、空进程

这是Android优先杀死,因为此时该进程已经没有任何用途

2、后台进程

包含不可见的Activity,即跳转到其他Activity后,由于资源不足,系统会将原来的Activity杀死(即跳转的来源)

3、服务进程

即Service,当系统资源不足时,系统可能会杀掉正在执行任务的Service。因此在Service中执行比较耗时的操作,并不能保证一定会执行完毕

4、可见进程

当前屏幕上可以看到的Activity,例如显示一个对话框的Activity,那么对话框就变成了前台进程,而调用它的Activity是可见进程,并不属于前台

5、前台进程

当前处于最前端的Activity,也就是Android最后考虑杀死的对象。一般来说,前台进程Android系统是不会杀死的,只有在上面的四种进程都杀死后资源依旧不够的情况下才会考虑杀死前台进程。

Android的启动模式

Activity的LaunchMode

我们知道在默认情况下,当我们多次启动同一个Activity的时候,系统会创建多个实例并把它们一一放入任务栈中,当我们单击back键,会发现这些Activity会一一回退。任务栈是一种“后进先出”的栈结构,这个比较好理解,每按一下back键就会有一个Activity出栈,直到栈空为止,当栈中无任何Activity的时候,系统就会回收这个任务栈。

Activity的四种启动模式:

  • standard
  • singleTop
  • singleTask
  • singleInstance

standard

标准模式,也是系统默认模式,每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的任务栈中。比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的任务栈中。

singleTop

栈顶复用模式。在这种模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前的请求的信息。需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果新Activity的实例已经存在但不是处于栈顶位置,那么新的Activity仍然会被重新创建。举个例子:假设目前栈内的情况为 ABCD,其中 ABCD为四个Activity,A位于栈底,D位于栈顶,如果这个时候要再次启动D,而且D的启动模式为singleTop,那么栈内的情况依然是ABCD;如果这个时候我们要启动C,就算C的启动模式为singleTop,那么栈内的情况还是会发生改变 ABCDC

singleTask

栈内复用模式。和singleTop的区别是singleTask是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例。这就是singleTask的模式,如果发现所在的Activity栈中有对应的Activity的实例,则会使此Activity实例之上的其他Activity的实例全部出栈,使此Activity的实例位于栈顶,显示到前台。

举几个例子,当然例子的验证我们稍后会给验证代码:
1、比如目前任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需要的任务栈(所需要的任务栈的概念稍后会给出)为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后在创建D的实例并将D入栈到S2。
2、另外一种情况,假设D所需要的任务栈为S1,其他情况如上面例子1所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1.
3、如果D所需要的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会在重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有cleerTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD

singleInstance

单实例模式。这是一种加强的singleTask模式,他除了具有singTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中,换句话说,比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了

关于启动模式不了解的可以借鉴下这篇文章,这篇文章介绍的比较形象 Android总结篇系列:Activity启动模式(lauchMode)

上面遗留问题

上面我们有提到过一个 “Activity所需要的任务栈”的概念

关于singleTask模式其实有一个 “Activity所需的任务栈”的概念;什么是Activity所需要的任务栈呢?这就牵扯到一个参数“TaskAffinity”,可以翻译为任务相关性。这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需要的任务栈的名字为应用的包名。当然我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相对于没有指定。TaskAffinity属性注和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有任何意义。

当TaskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。

当TaskAffinity和allowTaskReparenting结合的时候,这种情况比较复杂,会产生特殊的效果。当一个应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。举个例子:比如现在有2个应用A和B,A启动了B的一个Activity C,然后按Home键回到桌面,然后在单击B的桌面图标,这个时候并不是启动了B的主Activity,而是重新显示了已经被应用A启动的Activity C,或者说,C从A的任务栈转移到了B的任务栈中。可以这么理解,由于A启动了C,这个时候C只能运行在A的任务栈中,但是C属于B应用,正常情况下,它的TaskAffinity值肯定不可能和A的任务栈相同(因为包名不同)。所以,当B被启动后,B会创建自己的任务栈,这个时候系统发现原来C原本想要的任务栈已经被创建了,所以就把C从A的任务栈中转移过来了

想了解关于TaskAffinity属性的可以看下这篇博客 Android中Activity四种启动模式和taskAffinity属性详解

下面就是一一验证上面例子的时候了

在验证之前我们还需要做一件事,那就是要知道如何为Activity指定启动模式

给Activity指定启动模式有两种方式,第一种是通过AndroidMenifest为Activity指定启动模式,如下所示:
        <activity        android:name=".ActivityB"        android:configChanges="screenLayout"        android:launchMode="singleTask"></activity>
另外一种是通过Intent中设置标志位来为Activity指定启动模式,比如:
Intent intent = new Intent(MainActivity.this, ActivityB.class);            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);

注:这两种方式都可以为Activity指定启动模式,但是二者之间还是有区别的。首先,优先级上,第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;其次,上述两种方式在限定范围上有所不同,比如,第一种方式无法直接为Activity设置FLAG_ACTIVITY_CLEAR_TOP标识,而第二种方式无法为Activity指定singleInstance模式

验证

  • 先来验证singleTask,这里我们把MainActivity的启动模式设为singleTask,然后重复启动它,看看是否会重复创建,代码如下:
<activity android:name=".MainActivity"            android:configChanges="screenLayout"            android:launchMode="singleTask">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>       @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Log.e("time", "Main-->onCreate");        findViewById(R.id.call_activity).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent(MainActivity.this, MainActivity.class);                intent.putExtra("time", System.currentTimeMillis());                startActivity(intent);            }        });    }    @Override    protected void onNewIntent(Intent intent) {        super.onNewIntent(intent);        Log.e("time", "Main-->onNewIntent,   time="+intent.getLongExtra("time", 0));    }

我们可以运行 adb dumpsys activity 命令,打印下当前设备正在运行的Activity ,这个命令输出的东西会比较多,这里只复制有用的信息:

  Running activities (most recent first):      TaskRecord{429add20 #32 A=com.layoutinflate.mk.www.myapplication U=0 sz=1}        Run #5: ActivityRecord{42610078 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t32}

从上面导出的Activity信息可以看出,尽管我们启动了四次Activity,但是它始终只有一个实例在任务栈中,然后我们再来看一下我们的log输出,可以看到Activity真的并没有被创建,只是暂停了一下,然后调用onNewIntent,接着调用onResume就又继续了。

08-21 08:57:49.751 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onPause08-21 08:57:49.753 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onNewIntent,   time=147174106974508-21 08:57:49.753 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onresume08-21 08:57:51.493 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onPause08-21 08:57:51.493 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onNewIntent,   time=147174107147808-21 08:57:51.494 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onresume08-21 08:57:52.696 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onPause08-21 08:57:52.699 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onNewIntent,   time=147174107268908-21 08:57:52.700 23901-23901/com.layoutinflate.mk.www.myapplication E/time: Main-->onresume
  • 还是上面的代码,现在我们去掉Activity的singleTask,在来对比一下,还是同样的操作,单击三次按钮启动MainActivity三次
    执行adb shell dumpasys activity 命令:
 Running activities (most recent first):      TaskRecord{42e55cf8 #44 A=com.layoutinflate.mk.www.myapplication U=0 sz=4}        Run #11: ActivityRecord{42786090 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t44}        Run #10: ActivityRecord{4272d170 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t44}        Run #9: ActivityRecord{42295680 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t44}        Run #8: ActivityRecord{421f0530 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t44}

我们再来看一下输出的log信息:

08-21 09:16:11.286 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onPause08-21 09:16:11.339 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onCreate08-21 09:16:11.339 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onStart08-21 09:16:11.340 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onresume08-21 09:16:11.766 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onstop08-21 09:16:12.513 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onPause08-21 09:16:12.556 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onCreate08-21 09:16:12.557 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onStart08-21 09:16:12.557 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onresume08-21 09:16:12.979 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onstop08-21 09:16:13.404 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onPause08-21 09:16:13.436 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onCreate08-21 09:16:13.436 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onStart08-21 09:16:13.437 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onresume08-21 09:16:13.876 31733-31733/com.layoutinflate.mk.www.myapplication E/time: Main-->onstop

我们可以明显的看出目前前台任务栈的TaskAffinity的值为 com.layoutinflate.mk.www.myapplication ,它里面有4个Activity。而从输出的log信息中我们也可以看到Activity被重新创建了

  • 下面我们就来验证一下使用TaskAffinity属性时,singleTask任务栈到底是什么样的 代码如下:
<activity            android:name=".MainActivity"            android:configChanges="screenLayout">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <activity            android:name=".SecondActivity"            android:configChanges="screenLayout"            android:launchMode="singleTask"            android:taskAffinity="com.layoutinflate.mk.www.myapplication.task1"></activity>        <activity            android:name=".ThirdActivity"            android:configChanges="screenLayout"            android:launchMode="singleTask"            android:taskAffinity="com.layoutinflate.mk.www.myapplication.task1"></activity>

代码已经给出,现在我们就来分析一下:我们将SecondActivity和Thirdactivity都设成singleTask模式并指定他们的TaskAffinity属性都为“com.layoutinflate.mk.www.myapplication.task1”,注意这个TaskAffinity属性的值为字符串,且中间必须含有包名分隔符“.”。然后做如下操作在MainActivity中点击按钮启动SecondActivity,在SecondActivity中单击按钮启动ThirdActivity,在ThirdActivity中单击按钮有启动MainActivity,最后再在MainActivity中单击按钮启动SecondActivity,现在按2次back按钮,然后看到的是那个Activity?答案是回到桌面。和你心里想的答案是否一样呢?为什么会回到桌面呢?接下来我们就来分析分析这个问题。

首先,从理论上分析这个问题,先假设MainActivity为A,SecondActivity为B,ThirdActivity为C,我们知道A为standard模式,按照规定,A的TaskAffinity值继承在application的TaskAffinity,而Application默认TaskAffinity为包名,所以A的TaskAffinity值为包名。由于我们在XML中为B和C指定了TaskAffinity和启动模式,所以B和C是singleTask模式且有相同的TaskAffinity值为“com.layoutinflate.mk.www.myapplication.task1”。A启动B的时候,按照singleTask的规则,这个时候系统需要为B重新创建一个任务栈“com.layoutinflate.mk.www.myapplication.task1”。B再启动C,按照singleTask的规则,由于C所需的任务栈(和B为同一个任务栈)已经被B创建,所以无需在创建新的任务栈,这个时候系统只是创建C的实例并将C入栈了。接着C在启动A,A是standard模式,所以系统会为他创建一个新的实例并将它加入到启动它的那个Activity任务栈中(com.layoutinflate.mk.www.myapplication.task1,C启动的A,所以A处于C和B所在的任务栈),这个时候已经有两个任务栈了,一个是名字为包名的任务栈,里面只有A,另一个是名字为“com.layoutinflate.mk.www.myapplication.task1”的任务栈,里面的Activity为BCA。接下来,A在启动B,由于B是singleTask,B需要回到任务栈的栈顶,由于栈的工作模式为“后进先出”,B想回到栈顶,只能是CA出栈。所以到这里就好理解了,如果再按back键,B就出栈了,这时B所在的任务栈也就不存在了,这个时候只能回到后台任务栈并把A显示出来。注意这个A是后台任务栈中的A,不是“com.layoutinflate.mk.www.myapplication.task1”任务栈中的A,接着在继续back,就回到桌面了。分析到这里我们得出一条结论,singleTask模式的Activity切换到栈顶会导致在它之上的栈内的Activity出栈。

现在我们来通过adb shell dumpsys activity 命令来一步一步的观察Activity任务栈的变化,首先,A启动B,任务栈显示如下:

 Running activities (most recent first):      TaskRecord{42b9d0e8 #71 A=com.layoutinflate.mk.www.myapplication.task1 U=0 sz=1}        Run #9: ActivityRecord{422676f0 u0 com.layoutinflate.mk.www.myapplication/.SecondActivity t71}      TaskRecord{42708760 #70 A=com.layoutinflate.mk.www.myapplication U=0 sz=1}        Run #8: ActivityRecord{42708498 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t70}
可以清楚的看到有两个任务栈,第一个任务栈(com.layoutinflate.mk.www.myapplication.task1)中只有一个SecondActivity,第二个任务栈(com.layoutinflate.mk.www.myapplication)中只有一个MainActivity

B再启动C:

 Running activities (most recent first):      TaskRecord{42b9d0e8 #71 A=com.layoutinflate.mk.www.myapplication.task1 U=0 sz=2}        Run #10: ActivityRecord{4269ad20 u0 com.layoutinflate.mk.www.myapplication/.ThirdActivity t71}        Run #9: ActivityRecord{422676f0 u0 com.layoutinflate.mk.www.myapplication/.SecondActivity t71}      TaskRecord{42708760 #70 A=com.layoutinflate.mk.www.myapplication U=0 sz=1}        Run #8: ActivityRecord{42708498 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t70}
可以明显的看到“com.layoutinflate.mk.www.myapplication.task1”所在的任务栈中多了一个ThirdActivity

接着C再启动A:

 Running activities (most recent first):      TaskRecord{422bdba8 #75 A=com.layoutinflate.mk.www.myapplication.task1 U=0 sz=3}        Run #10: ActivityRecord{4225e4c0 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t75}        Run #9: ActivityRecord{4236d210 u0 com.layoutinflate.mk.www.myapplication/.ThirdActivity t75}        Run #8: ActivityRecord{42247ec8 u0 com.layoutinflate.mk.www.myapplication/.SecondActivity t75}      TaskRecord{42ab86c0 #74 A=com.layoutinflate.mk.www.myapplication U=0 sz=1}        Run #7: ActivityRecord{42249ab8 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t74}
可以看到新打开的A也被放入了“com.layoutinflate.mk.www.myapplication.task1”中

接着A再启动B:

 Running activities (most recent first):      TaskRecord{422bdba8 #75 A=com.layoutinflate.mk.www.myapplication.task1 U=0 sz=1}        Run #8: ActivityRecord{42247ec8 u0 com.layoutinflate.mk.www.myapplication/.SecondActivity t75}      TaskRecord{42ab86c0 #74 A=com.layoutinflate.mk.www.myapplication U=0 sz=1}        Run #7: ActivityRecord{42249ab8 u0 com.layoutinflate.mk.www.myapplication/.MainActivity t74}
现在可以看到“com.layoutinflate.mk.www.myapplication.task1”只剩下了SecondActivity ,后面的这里不在演示。

Activity的Flags

Activity的Flags有很多,这里主要分析一些常用的标记位:

  • FLAG_ACTIVITY_NEW_TASK
    这个标记位的作用是为Activity指定“singleTask”启动模式,其效果和在XML中指定该情动模式相同

  • FLAG_ACTIVITY_SINGLE_TOP
    这个标记位的作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定该启动模式相同

  • FLAG_ACTIVITY_CLEAR_TOP
    具有此标记位的Activity,当它启动时,在同一个任务栈中的所有位于它上面的Activity都要出栈,这个标志位一般会和singleTask启动模式一起出现,在这种情况下,被启动Activity的实例如果已经存在,那么系统就会调用它的onNewIntent,如果被启动的Activity采用standard模式启动,那么它连同它之上的所有Activity都要出栈,系统会创建新的Activity实例并放入栈顶

IntentFilter的匹配规则

我们知道,启动Activity分为两种,显式调用和隐式调用。二者的区别:显式调用需要明确指定被启动对象的组件信息,包括包名和类名,而隐式调用则不需要指定组件的信息。原则上一个Intent不应该既是显式调用又是隐式调用,如果二者共存的话以显式调用为主。显式调用比较简单,这里主要介绍一下隐式调用。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中的过滤信息有action、category、data 下面看示例:

<activity            android:name=".SecondActivity"            android:configChanges="screenLayout"            android:launchMode="singleTask"            android:taskAffinity="com.layoutinflate.mk.www.myapplication.task1">            <intent-filter>                <action android:name="com.layoutinflate.mk.www.myapplication.action_name1"/>                <action android:name="com.layoutinflate.mk.www.myapplication.action_name2"/>                <category android:name="com.layoutinflate.mk.www.myapplication.category_name1"/>                <category android:name="android.intent.category.DEFAULT"/>                <data android:mimeType="text/plain"/>            </intent-filter>        </activity>
为了匹配过滤列表,需要同时匹配过滤列表中的action、category、data信息,否则匹配失败。一个过滤列表中的action、category、data可以存在多个,所有的action、category、data分别构成不同的类别,同一类别的信息共同约束当前类别类别的匹配过程。只有一个Intent同时匹配action类别、category类别、data类别才算完全匹配,只有完全匹配才能成功启动目标Activity。另外一点,一个Activity中可以有多个intent-filter,一个intent只要能匹配任何一组intent-filter即可成功启动对应的Activity,如下所示:
<activity            android:name=".SecondActivity"            android:configChanges="screenLayout"            android:launchMode="singleTask"            android:taskAffinity="com.layoutinflate.mk.www.myapplication.task1">            <intent-filter>                <action android:name="com.layoutinflate.mk.www.myapplication.action_name1"/>                <action android:name="com.layoutinflate.mk.www.myapplication.action_name2"/>                <category android:name="com.layoutinflate.mk.www.myapplication.category_name1"/>                <category android:name="android.intent.category.DEFAULT"/>                <data android:mimeType="text/plain"/>            </intent-filter>            <intent-filter>                <action android:name="com.layoutinflate.mk.www.myapplication.action_name3"/>                <action android:name="com.layoutinflate.mk.www.myapplication.action_name4"/>                <category android:name="android.intent.category.DEFAULT"/>                <data android:mimeType="text/plain"/>                <data android:mimeType="image/*"/>                <data android:mimeType="video/*"/>            </intent-filter>        </activity>

下面详细分析各种属性的匹配规则:

1、action的匹配规则

action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里说的匹配是指action的字符串值完全一样。一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。针对上面的过滤规则,只要我们的Intent中action值为“com.layoutinflate.mk.www.myapplication.action_name1”或者“com.layoutinflate.mk.www.myapplication.action_name2”都能成功匹配。需要注意的是,Intent中如果没有指定action,那么匹配失败。总结一下,action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同。另外,action区分大小写,大小写不同字符串相同的action会匹配失败。

2、category的匹配规则:

category也是一个字符串,和action一样系统也预定义了一些category,同时我们也可以在应用中定义自己的category,category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。换句话说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。这里需要注意下它和action匹配过程的不同,action是要求Intent中必须有一个action且必须能够和过滤规则中的某个action相同,而category要求Intent中可以没有category,但是如果你一旦有category,不管你有几个,每个都要能够和过滤规则中的任何一个category相同。为了匹配前面的过滤规则中的category,我们可以写出下面的Intent,intent。addcategory(“com.layoutinflate.mk.www.myapplication.category_name1”)亦或者不设置category,为什么不设置category也可以匹配呢?原因是系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category,所以这个category就可以匹配前面的过滤规则中的第二个category。同时,为了我们的Activity能够接收隐式调用,就必须在intent-filter中指定“android.intent.category.DEFAULT”这个category。

3、data的匹配规则

data的匹配规则和action类似,它也要求Intent中必须包含data数据,并且data数据能够完全匹配过滤规则中的某一个data,这里的完全匹配是指过滤规则中出现的data部分也出现在了Intent中的data中。在介绍data的匹配规则前,我们先了解下data的结构:

                <data                     android:scheme="string"                    android:host="string"                    android:port="string"                    android:path="string"                    android:pathPattern="string"                    android:pathPrefix="string"                    android:mimeType="text/plain"/>

data由两部分组成,mimeType和URL。mimeType是指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同的媒体格式,而URL包含的数据就比较多了,通常都是scheme+host+port+paht/pathpattern/pathPrefix组成了URL。下面将介绍每一个数据的含义:

  • Scheme:URL的模式,如http、file、content等,如果URL中没有指定scheme,那么整个URL的其他参数无效,这也意味着URL是无效的。
  • Host:URL的主机名,比如www.baidu.com,如果host未指定,那么整个URL中的其他参数无效,这也意味着URL是无效的。
  • Port:URL中的端口号,比如80,仅当URL中指定了scheme和host参数的时候port参数才是有意义的。
  • Path、PathPattern和PathPrefix:这三个参数表示路径信息,其中path表示完整的路径信息;pathpattern也表示完整的路径信息,但是它里面可以包含通配符“”,“”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“”要写成“\”,“\”要写成”\\”;pathPrefix表示路径的前缀信息。

练习匹配规则

(1)如下过滤规则:

            <intent-filter>                <data android:mimeType="image/*" />            </intent-filter>

这种规则指定了媒体类型为所有类型的图片,那么intent中的mimeType属性必须是“image/*”才能匹配,这种情况下虽然过滤规则没有指定URL,但是却是有默认值的,URL的默认值为content和file,也就是说,虽然没有指定URL,但是intent中的URL部分的scheme必须为content或者file才能匹配,这点是需要尤其注意的。为了匹配(1)中的规则,代码如下:

intent.setDataAndType(Url.parse("file://abc"),"image/png");

另外,如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData在调用setType,因为这两个方法会彼此会清除对方得到值,这个可以从源码中看出来:

public Intent setData(Url data){    mdata = data;    mType = null;    return this;}

data过滤规则的另外一种写法:

                //第一种                <data                    android:scheme="file"                    android:host="www.baidu.com"                    android:mimeType="text/plain"/>                //第二种                <data android:scheme="file"/>                <data android:host="www.baidu.com"/>
0 0