Android开发艺术探索知识回顾——第1章 Activity的生命周期和启动模式:2、启动模式和Flags标记位

目录

1.2 Activity的启动模式

1.2.1 Activity 的 LaunchMode

(1) standard:标准模式,这也是系统的默认模式。

(2) singleTop:栈顶复用模式。

(3) singleTask:栈内复用模式。

(4) singlelnstance:单实例模式。

任务栈情况示例

什么是 Activity 所需要的任务栈呢?

如何给Activity指定启动模式呢?

体验启动模式——代码示例

执行 adb shell dumpsys activity 命令分析

 singleTask 特殊情况说明

修改代码进行说明

从理论上分析这个问题

釆用dumpsys命令验证说明

1.2.2 Activity 的 Flags

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_SINGLE_TOP

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS


1.2 Activity的启动模式

上一节介绍了 Activity 在标准情况下和异常情况下的生命周期,我们对Activity的生命周期应该有了深入的了解。除了 Activity 的生命周期外,Activity 的启动模式也是一个难点, 原因是形形色色的启动模式和标志位实在是太容易被混淆了,但是Activity作为四大组件之首,它的的确确非常重要,有时候为了满足项目的特殊需求,就必须使用 Activity 的启动模式,所以我们必须要搞清楚它的启动模式和标志位,本节将会一一介绍。

 

1.2.1 Activity 的 LaunchMode

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

关于任务栈的系统工作原理,这里暂时不做说明,在后续章节会专门介绍任务栈。知道了 Activity 的默认启动模式以后,我们可能就会发现一个问题:多次启动同一个 Activity,系统重复创建多个实例,这样不是很傻吗?这样的确有点傻,Android在设计的时候不可能不考虑到这个问题,所以它提供了启动模式来修改系统的默认行为。目前有四种启动模式:standard、singleTop、singleTask 和 singlelnstance,下面先介绍各种启动模式的含义:

 

(1) standard:标准模式,这也是系统的默认模式。

每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下 Activity 的生命周期,如上节描述,它的 onCreate、onStart、onResume 都会被调用。这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。

在这种模式下,谁启动了这个 Activity,那么这个Activity 就运行在启动它的那个 Activity 所在的栈中。比如 Activity A 启动了 Activity B (B是标准模式),那么 B 就会进入到 A 所在的栈中。不知道读者是否注意到,当我们用 ApplicationContext 去启动 standard 模式的 Activity 的时候会报错,错误如下:

E/AndroidRuntime(674): android.util.androidruntiomException: Calling startActivity from 
outside of an Activity context requires the FLAG_ACTIVITY_TASK flag . Is this really what are want?

相信这句话读者一定不陌生,这是因为 standard 模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中,但是由于非 Activity 类型的 Context (如ApplicationContext)  并没有所谓的任务栈,所以这就有问题了。

解决这个问题的方法是:为待启动 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候待启动 Activity实际上是以 singleTask 模式启动的,读者可以仔细体会。

 

(2) singleTop:栈顶复用模式。

在这种模式下,如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的 onNewIntent 方法会被回调通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个 Activity 的 onCreate、onStart 不会被系统调用,因为它并没有发生改变。如果新 Activity 的实例已存在但不是位于栈顶,那么新 Activity 仍然会重新重建。

举个例子,假设目前栈内的情况为 ABCD,其中 ABCD 为四个Activity,A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD。

 

(3) singleTask:栈内复用模式。

这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例,和 singleTop 一样,系统也会回调其onNewIntent。

具体一点,当一个具有 singleTask 模式的 Activity 请求启动后,比如Activity A,系统首先会寻找是否存在 A 想要的任务栈。如果不存在,就重新创建一个任务栈,然后创建 A 的实例后把 A 放到栈中。

如果存在 A 所需的任务栈,这时要看 A 是否在栈中有实例存在,如果有实例存在,那么系统就会把 A 调到栈顶并调用它的 onNewIntent 方法。如果实例不存在,就创建 A 的实例并把 A 压入栈中。举几个例子:

  • 比如目前任务栈 S1 中的情况为 ABC,这个时候 Activity D 以 singleTask 模式请求启动,其所需要的任务栈为 S2,由于 S2 和 D 的实例均不存在,所以系统会先创建任务栈 S2,然后再创建 D 的实例并将其入栈到 S2。
  • 另外一种情况,假设 D 所需的任务栈为 S1,其他情况如上面例子 1 所示,那么由于 S1 已经存在,所以系统会直接创建 D 的实例并将其入栈到 S1。
  • 如果 D 所需的任务栈为 S1,并且当前任务栈 S1 的情况为 ADBC,根据栈内复用的原则,此时 D 不会重新创建,系统会把 D 切换到栈顶并调用其 onNewIntent 方法, 同时由于 singleTask 默认具有 clearTop 的效果,会导致栈内所有在 D 上面的 Activity 全部岀栈,于是最终 S1 中的情况为 AD。这一点比较特殊,在后面还会对此种情况详细地分析。

通过上述 3 个例子,读者应该能比较清晰地理解 singleTask 的含义了。

 

(4) singlelnstance:单实例模式。

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

 

任务栈情况示例

上面介绍了几种启动模式,这里需要指出一种情况:我们假设目前有 2 个任务栈,前台任务栈的情况为AB,而后台任务栈的情况为CD,这里假设 CD 的启动模式均为singleTask。

现在请求启动 D,那么整个后台任务栈都会被切换到前台,这个时候整个后退列表变成了 ABCD。当用户按 back 键的时候,列表中的 Activity 会出栈,如图1-7所示。如果不是请求启动 D 而是启动 C,那么情况就不一样了,请看图1-8,具体原因在本节后面会再进行详细分析。

  

 

什么是 Activity 所需要的任务栈呢?

另外一个问题是,在 singleTask 启动模式中,多次提到某个 Activity 所需的任务栈,什么是 Activity 所需要的任务栈呢?这要从一个参数说起:TaskAffinity,可以翻译为任务相关性。这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity 所需的任务栈的名字为应用的包名。

当然,我们可以为每个Activity都单独指定 TaskAffinity 属性,这个属性值必须不能和包名相同,否则就相当于没有指定。TaskAffinity 属性主要和 singleTask 启动模式或者 allowTaskReparenting 属性配对使用,在其他情况下没有意义。

另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的 Activity 位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。当 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 的任务栈中转移过来了。这种情况读者可以写个例子测试一下,这里就不做示例了。

 

如何给Activity指定启动模式呢?

如何给Activity指定启动模式呢?有两种方法,第一种是通过 AndroidMenifest 为 Activity 指定启动模式,如下所示。

另一种情况是通过在 Intent 中设置标志位来为 Activity 指定启动模式,比如:

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

 

体验启动模式——代码示例

关于 Intent 中为 Activity 指定的各种标记位,在下面的小节中会继续介绍。下面通过一个例子来体验启动模式的使用效果。还是前面的例子,这里我们把 MainActivity 的启动模式设为 singleTask,然后重复启动它,看看是否会重复创建,代码修改如下:

1、把 MainActivity 的启动模式设为 singleTask

<activity
   android:name="com.yyh.demo4.launchMode.MainActivity"
   android:configChanges="orientation|screenSize"
   android:label="@string/app_name" 
   android:launchMode="singleTask"
   >
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
</activity>

 

2、MainActivity里面添加如下代码:

findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, MainActivity.class);
        intent.putExtra("time", System.currentTimeMillis());
        startActivity(intent);
    }
});


@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Log.d(TAG, "onNewIntent, time=" + intent.getLongExtra("time", 0));
}

根据上述修改,我们做如下操作,连续单击三次按钮启动3次 MainActivity,算上原本的 MainActvity 的实例,正常情况下,任务栈中应该有4个 MainActivity 的实例,但是我们为其指定了 singleTask 模式,现在来看一看到底有何不同。

 

执行 adb shell dumpsys activity 命令分析

mac 上打开 terminal终端,输入命令 adb shell dumpsys activity,

command + f,搜索关键字:ACTIVITY MANAGER ACTIVITIES

具体日志如下:

从上面导出的 Activity 信息可以看出,尽管启动了 4 次 MainActivity,但是它始终只有一个实例在任务栈中。从图1-9的 log 可以看出,Activity 的确没有重新创建,只是暂停了一 下,然后调用了onNewIntent,接着调用 onResume 就又继续了。

 

现在我们去掉singleTask,再来对比一下,还是同样的操作,单击三次按钮启动 MainActivity 三次。

执行 adb shell dumpsys activity 命令:

mac 上打开 terminal终端,输入命令 adb shell dumpsys activity,

command + f,搜索关键字:ACTIVITY MANAGER ACTIVITIES

导出信息很多,我们可以有选择地看,比如就看 Running activities (most recent first)这一块,如下所示。

我们能够得出目前总共有 2 个任务栈,前台任务栈的 taskAffinity 值为 com.ryg.chapter_1,它里面有 4 个 Activity,后台任务栈的 taskAffinity 值为 com.mumu.launcher,它里面有1个Activity,这个Activity就是桌面。( 注意:后台任务栈的 taskAffinity 值不是 com.android.launcher,因为测试用的网易MuMu模拟器,所以是 com.mumu.launcher )

通过这种方式来分析任务栈信息就清晰多了。从上面的导出信息中可以看到,在任务栈中有 4 个MainActivity,这也就验证了 Activity 的启动模式的工作方式。

 

 singleTask 特殊情况说明

上述四种启动模式,standard 和 singleTop 都比较好理解,singlelnstance 由于其特殊性也好理解,但是关于 singleTask 有一种情况需要再说明一下。如图1-7所示,如果在 Activity B 中请求的不是 D 而是 C,那么情况如何呢?这里可以告诉读者的是,任务栈列表变成了 ABC,是不是很奇怪呢? Activity D 被直接出栈了。

  

 

 

修改代码进行说明

下面我们再用实例验证看看是不是这样。 首先,还是使用上面的代码,但是我们做一下修改:

        <activity
            android:name="com.yyh.demo5.singleTask.MainActivity"
            android:configChanges="orientation|screenSize"
            android:label="@string/app_name"
            android:launchMode="standard" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity
            android:name="com.yyh.demo5.singleTask.SecondActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:taskAffinity="com.ryg.task1" 
            android:launchMode="singleTask"/>
        
        <activity
            android:name="com.yyh.demo5.singleTask.ThirdActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:taskAffinity="com.ryg.task1" 
            android:launchMode="singleTask"/>

 

我们将 SecondActivity 和 ThirdActivity 都设成 singleTask 并指定它们的 taskAffinity 属性为“com.ryg.task1”,注意这个taskAffinity属性的值为字符串,且中间必须含有包名分隔符“.”。

然后做如下操作,在 MainActivity 中单击按钮启动 SecondActivity,在 SecondActivity 中单击按钮启动 ThirdActivity,在ThirdActivity 中单击按钮又启动 MainActivity,最后再在 MainActivity 中单击按钮启动 SecondActivity,现在按back键,然后看到的是哪个Activity?答案:是回到桌面。是不是有点摸不到头脑了?没关系,接下来我们分析这个问题。

 

从理论上分析这个问题

首先,从理论上分析这个问题,先假设 MainActivity 为ASecondActivity 为B,ThirdActivity 为C。我们知道 A 为standard模式,按照规定,A 的taskAffinity 值继承自 Application 的 taskAffinity,而 Application 默认 taskAffinity 为包名,所以 A 的 taskAffinity 为包名。由于我们在 XML 中为 B 和 C 指定了 taskAffinity 和启动模式,所以 B 和 C 是 singleTask 模式且有相同的 taskAffinity 值“com.ryg.task1”。

A 启动 B 的时候,按照 singleTask 的规则,这个时候需要为 B 重新创建一个任务栈“com.ryg.task1”。B 再启动 C,按照singleTask 的规则,由于C所需的任务栈(和B为同一任务栈)已经被B创建,所以无须再创建新的任务栈,这个时候系统只是创建 C 的实例后将C入栈了。

接着 C 再启动 A,A 是standard 模式,所以系统会为它创建一个新的实例,并将它加到启动它的那个 Activity 的任务栈,由于是 C 启动了 A,所以 A 会进入 C 的任务栈中并位于栈顶。这个时候己经有两个任务栈了,一个是名字为包名的任务栈,里面只有 A,另一个是名字为“com.ryg.task1”的任务栈,里面的 Activity 为BCA。

接下来,A 再启动 B,由于 B 是 singleTask,B 需要回到任务栈的栈顶,由于栈的工作模式为“后进先出”,B想要回到栈顶,只能是CA出栈。所以,到这里就很好理解了,如果再按back键,B 就出栈了,B 所在的任务栈已经不存在了,这个时候只能是回到后台任务栈并把 A 显示出来。

注意这个 A 是后台任务栈的 A,不是 "com.ryg.task1”任务栈的 A,接着再继续back,就回到桌面了。分析到这里,我们得岀一条结论,singleTask 模式的 Activity 切换到栈顶会导致在它之上的栈内的 Activity 出栈。

 

釆用dumpsys命令验证说明

接着我们在实践中再次验证这个问题,还是釆用dumpsys命令。我们省略中间的过程,直接看 C 启动 A 的那个状态,执行 adb shell dumpsys activity 命令,

mac 上打开 terminal终端,输入命令 adb shell dumpsys activity,

command + f,搜索关键字:ACTIVITY MANAGER ACTIVITIES

查看关键信息 Running activities (most recent first)

日志如下:

可以清楚地看到有 2 个任务栈,第一个(com.ryg.chapter_1)只有 A,第二个(com.ryg.task1) 有BCA,就如同我们上面分析的那样,然后再从 A 中启动 B ,再看一下日志

可以发现在任务栈 com.ryg.task1 中只剩下 B 了,C、A 都已经出栈了。这个时候再按 back 键,任务栈 com.ryg.chapter_1 中的 A 就显示出来了,如果再 back 就回到桌面了。分析到这里,相信读者对 Activity 的启动模式已经有很深入的理解了。

注意:

1、用网易的MuMu模拟器进行测试,在点击跳转过程中,会出现黑屏现象,所以改用真机测试了!

2、其他任务栈为手机系统自带的任务栈,这块可以忽略!

下面介绍Activity中常用的标志位。

 

1.2.2 Activity 的 Flags

Activity 的 Flags 有很多,这里主要分析一些比较常用的标记位。标记位的作用很多,有的标记位可以设定 Activity 的启动模式,比如FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_SINGLE_TOP 等;还有的标记位可以影响 Activity 的运行状态,比如 FLAG_ACTIVITY_CLEAR_TOP 和 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 等。

下面主要介绍几个比较常用的标记位,剩下的标记位读者可以査看官方文档去了解,大部分情况下,我们不需要为 Activity 指定标记位,因此,对于标记位理解即可。在使用标记位的时候,要注意有些标记位是系统内部使用的,应用程序不需要去手动设置这些标记位以防出现问题。

FLAG_ACTIVITY_NEW_TASK

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

FLAG_ACTIVITY_SINGLE_TOP

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

FLAG_ACTIVITY_CLEAR_TOP

具有此标记位的 Activity ,当它启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈。这个模式一般需要和FLAG_ACTIVITY_NEW_TASK 配合使用,在这种情况下,被启动 Activity 的实例如果已经存在,那么系统就会调用它的 onNewIntent。如果被启动的 Activity 釆用 standard 模式启动,那么它连同它之上的 Activity 都要出栈,系统会创建新的 Activity 实例并放入栈顶。通过1.2.1节中的分析可以知道,singleTask启动模式默认就具有此标记位的效果。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

具有这个标记的 Activity 不会出现在历史 Activity 的列表中,当某些情况下我们不希望用户通过历史列表回到我们的 Activity 的时候这个标记比较有用,它等同于在 XML 中指定 Activity 的属性 android:excludeFromRecents="true"。

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这个问题我能回答。您可以按照以下步骤进行操作: 1. 新建一个Android工程,并打开清单文件AndroidManifest.xml。 2. 在清单文件中添加以下代码,以声明一个服务(Service)或活动(Activity)通过启动器(Launcher)方式启动: ```xml <application ...> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".MyService" android:enabled="true" android:exported="false" /> </application> ``` 3. 创建一个名为“MainActivity”或“MyService”的Java类,并重写其生命周期方法(onCreate、onStart、onResume等),以输出一些简单的信息。例如,在MainActivity中,您可以这样写: ```java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("MainActivity", "Activity onCreate"); } @Override protected void onStart() { super.onStart(); Log.i("MainActivity", "Activity onStart"); } @Override protected void onResume() { super.onResume(); Log.i("MainActivity", "Activity onResume"); } // ... } ``` 在MyService中,您可以这样写: ```java public class MyService extends Service { @Override public void onCreate() { super.onCreate(); Log.i("MyService", "Service onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("MyService", "Service onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.i("MyService", "Service onDestroy"); } // ... } ``` 4. 最后,在MainActivity中添加以下代码,以启动MyService: ```java Intent intent = new Intent(this, MyService.class); startService(intent); ``` 或者,在MainActivity中添加以下代码,以启动MainActivity: ```java Intent intent = new Intent(this, MainActivity.class); startActivity(intent); ``` 这样,当您运行该应用程序时,MyService或MainActivity将在其生命周期方法中输出相应的信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

被开发耽误的大厨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值