深入理解Activity——生命周期、启动模式、taskAffinity

基本用法

  • startActivity
  • startActivityForResult+onActivityResult

生命周期

深入理解Activity——生命周期、启动模式、taskAffinity_第1张图片
- Activity的整个生命周期发生在onCreate()调用与onDestory()调用之间。
- Activity的可见生命周期发生在onStart()调用与onStop()之间。在这段时间,用户可以在屏幕上看到Activity并与其交互。在Activity的整个生命周期,当Activity在对用户可见和隐藏两种状态中交替变化时,系统可能会多次调用onStart()和onStop()。
- Activity的前台生命周期发生在onResume()调用与onPause()调用之间。在这段时间,Activity位于屏幕上的所有其他Activity之前,并具有用户输入焦点。Activity可频繁转入和转出前台,由于此状态可能经常发生转变,因此这两个方法中应采用适度轻量级的代码,以避免因转变速度慢而让用户等待。

保存与销毁

深入理解Activity——生命周期、启动模式、taskAffinity_第2张图片
Activity在可能出现被销毁的情况下,系统会调用Activity的onSaveInstanceState()方法进行保存信息,该方法默认会保存Activity的View树的信息,会调用每个含有id的View的onSaveInstanceState()方法,从而由上至下完成信息的保存;当然除了View的信息,我们也可以在这个方法里面保存一些状态信息。Android系统对于任何可能出现Activity非主动被销毁的情况,都会执行onSaveInstanceState()方法,属于“宁可错杀一千,不可放过一个的原则”。那么,都有哪些情况可能会导致Activity处于易被销毁的状态呢?
在回答这个问题之前,首先需要知道Activity的三种优先级:
1. 前台Activity——可见并且可交互
2. 可见但非前台Activity——可见,但是不可交互。比如Activity中跳出了一个对话框,一个来电等等
3. 后台Activity——已经被暂停的Activity

在这三种状态中,只有第一种是不会被系统主动销毁的,其余两种均是属于易销毁状态,所以一旦进入这两种情况,都会执行onSaveInstanceState()方法。
有一种正常销毁状态,那就是用户按返回键,退出一个Activity时,这种情况下是不会调用onSaveInstanceState()方法保存状态信息的。
在执行了onSaveInstanceState()保存了状态后,在Activity重新被创建时如何恢复信息呢?可以在onCreate或onRestorInstanceState()方法中,这两个方法均有一个Bundle的参数,区别在于onCreate()中该参数可能为null,为null表示没有进行状态保存;而onRestorInstanceState()方法中该参数是不会被null的,因为能进入该方法就表示之前保存过状态信息。

注:无法保证系统会在销毁您的 Activity 前调用 onSaveInstanceState(),因为存在不需要保存状态的情况(例如用户使用“返回”按钮离开您的 Activity 时,因为用户的行为是在显式关闭 Activity)。 如果系统调用 onSaveInstanceState(),它会在调用 onStop() 之前,并且可能会在调用 onPause() 之前进行调用。

注:由于无法保证系统会调用 onSaveInstanceState(),因此您只应利用它来记录 Activity 的瞬态(UI 的状态)— 切勿使用它来存储持久性数据,而应使用 onPause() 在用户离开 Activity 后存储持久性数据(例如应保存到数据库的数据)。

启动模式

Activity一共有四种启动模式,启动模式会影响Activity的启动,是新建一个Activity还是复用存在的Activity。分别是:
- standard:标准模式
- singleTop:栈顶复用模式
- singleTask:栈内复用模式
- singleInstance:单实例模式

启动Activity时并指定启动模式有两种方式,一种是在清单文件中指明;另一种则是在Intent中添加FLAG标志位。
因此,如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity B 的请求(如其清单文件中所定义)。
Activity如果不指定启动模式,那么默认就是标准模式;如果想使用其他模式,需要在清单文件中指明launchMode属性。

注:某些适用于清单文件的启动模式不可用作 Intent 标志,同样,某些可用作 Intent 标志的启动模式无法在清单文件中定义。

standard启动模式

默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
下面的例子中,ActivityA中有一个按钮,会以标准模式启动ActivityA,代码如下:

//standard模式启动A
    public void standardStartA(View view) {
        startActivity(new Intent(this, ActivityA.class));
    }

点击了两次之后,执行“adb shell dumpsys activity”命令,可以查看到Activity信息如下:

 Stack #1:
    Task id #354
      TaskRecord{191a1e9f #354 A=com.xks.activitydemo U=0 sz=3}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.xks.activitydemo/.launchMode.ActivityA }
        Hist #2: ActivityRecord{2e8655c7 u0 com.xks.activitydemo/.launchMode.ActivityA t354}
          Intent { cmp=com.xks.activitydemo/.launchMode.ActivityA }
          ProcessRecord{294ded8f 2291:com.xks.activitydemo/u0a71}
        Hist #1: ActivityRecord{2d50f5c4 u0 com.xks.activitydemo/.launchMode.ActivityA t354}
          Intent { cmp=com.xks.activitydemo/.launchMode.ActivityA }
          ProcessRecord{294ded8f 2291:com.xks.activitydemo/u0a71}
        Hist #0: ActivityRecord{233cd433 u0 com.xks.activitydemo/.launchMode.ActivityA t354}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.xks.activitydemo/.launchMode.ActivityA }
          ProcessRecord{294ded8f 2291:com.xks.activitydemo/u0a71}

    Running activities (most recent first):
      TaskRecord{191a1e9f #354 A=com.xks.activitydemo U=0 sz=3}
        Run #2: ActivityRecord{2e8655c7 u0 com.xks.activitydemo/.launchMode.ActivityA t354}
        Run #1: ActivityRecord{2d50f5c4 u0 com.xks.activitydemo/.launchMode.ActivityA t354}
        Run #0: ActivityRecord{233cd433 u0 com.xks.activitydemo/.launchMode.ActivityA t354}

从上面可以看到确实存在三个Activity,这是因为以标准模式启动Activity的话,只会创建Activity然后与任务关联。

singleTop启动模式

如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。
下面的例子中,ActivityA以singleTop模式启动ActivityB,然后ActivityB再以singleTop模式启动ActivityB,操作如下图所示:
深入理解Activity——生命周期、启动模式、taskAffinity_第3张图片
在ActivityA启动ActivityB时,执行“adb shell dumpsys activity”命令后,结果如下:

 Stack #1:
    Task id #356
      TaskRecord{1b50d1d6 #356 A=com.xks.activitydemo U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.xks.activitydemo/.launchMode.ActivityA }
        Hist #1: ActivityRecord{18219d8e u0 com.xks.activitydemo/.launchMode.ActivityB t356}
          Intent { cmp=com.xks.activitydemo/.launchMode.ActivityB }
          ProcessRecord{22dcbc57 3336:com.xks.activitydemo/u0a71}
        Hist #0: ActivityRecord{191d11c7 u0 com.xks.activitydemo/.launchMode.ActivityA t356}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.xks.activitydemo/.launchMode.ActivityA }
          ProcessRecord{22dcbc57 3336:com.xks.activitydemo/u0a71}

    Running activities (most recent first):
      TaskRecord{1b50d1d6 #356 A=com.xks.activitydemo U=0 sz=2}
        Run #1: ActivityRecord{18219d8e u0 com.xks.activitydemo/.launchMode.ActivityB t356}
        Run #0: ActivityRecord{191d11c7 u0 com.xks.activitydemo/.launchMode.ActivityA t356}

    mResumedActivity: ActivityRecord{18219d8e u0 com.xks.activitydemo/.launchMode.ActivityB t356}

从上面可以看到此时任务栈中有两个Activity,分别是ActivityB和ActivityA;下面从ActivityB再次以singleTop模式启动ActivityB,执行adb命令结果如下:

  Stack #1:
    Task id #356
      TaskRecord{1b50d1d6 #356 A=com.xks.activitydemo U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.xks.activitydemo/.launchMode.ActivityA }
        Hist #1: ActivityRecord{18219d8e u0 com.xks.activitydemo/.launchMode.ActivityB t356}
          Intent { cmp=com.xks.activitydemo/.launchMode.ActivityB }
          ProcessRecord{22dcbc57 3336:com.xks.activitydemo/u0a71}
        Hist #0: ActivityRecord{191d11c7 u0 com.xks.activitydemo/.launchMode.ActivityA t356}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.xks.activitydemo/.launchMode.ActivityA }
          ProcessRecord{22dcbc57 3336:com.xks.activitydemo/u0a71}

    Running activities (most recent first):
      TaskRecord{1b50d1d6 #356 A=com.xks.activitydemo U=0 sz=2}
        Run #1: ActivityRecord{18219d8e u0 com.xks.activitydemo/.launchMode.ActivityB t356}
        Run #0: ActivityRecord{191d11c7 u0 com.xks.activitydemo/.launchMode.ActivityA t356}

    mResumedActivity: ActivityRecord{18219d8e u0 com.xks.activitydemo/.launchMode.ActivityB t356}

可以看到,任务栈中依旧是ActivityB和ActivityA,这就是singleTop模式的作用,栈顶复用,因为ActivityB启动ActivityB时,ActivityB已经位于栈顶,那么就会直接使用ActivityB,而不是直接创建。

singleTask

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

注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。

下面的例子以ActivityA以singleTask模式启动ActivityC,然后ActivityC以standard模式启动ActivityA,之后ActivityA再以singleTask模式启动ActivityC观察一下效果。操作图如下所示:
深入理解Activity——生命周期、启动模式、taskAffinity_第4张图片
下面再分析任务栈中Activity的情况,就不显示adb命令的结果了,直接以图片代替。整个任务栈中的变化情况如下图:
深入理解Activity——生命周期、启动模式、taskAffinity_第5张图片
从上图可以看到,当第二次从ActivityA启动ActivityC时,因为任务栈中已经存在了ActivityC,所以会清除栈顶的ActivityA,栈中最后剩下的是ActivityC和ActivityA。

singleInstance

与 “singleTask” 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。
下面的例子中,ActivityA以singleInstance模式启动ActivityD,ActivityD再以标准模式启动ActivityA,然后再以singleInstance模式启动ActivityD,操作示意图如下:
深入理解Activity——生命周期、启动模式、taskAffinity_第6张图片
其任务栈的变化如下图:
深入理解Activity——生命周期、启动模式、taskAffinity_第7张图片
上图中,蓝色的为一个任务栈,黄色的为一个任务栈。当ActivityA第一次启动ActivityD后,由于ActivityD为singleInstance,所以创建了一个新任务栈存放ActivityD,然后ActivityD再以标准模式启动ActivityA,由于singleIntsnace意味着栈内只能有一个Activity,所以ActivityA和之前的ActivityA在一个任务中,当再次启动ActivityD时,此时由于ActivityD已经存在于其中一个任务中,直接转移到前台即可。这时整个栈中的顺序为ActivityD、ActivityA、ActivityA,此后依次按back键,可以观察到退出顺序确实如栈中所示。
上述的启动模式均是在清单文件中配置的,如下:

<activity android:name=".launchMode.ActivityA">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
        <activity
            android:name=".launchMode.ActivityB"
            android:launchMode="singleTop" />
        <activity
            android:name=".launchMode.ActivityC"
            android:launchMode="singleTask" />
        <activity
            android:name=".launchMode.ActivityD"
            android:launchMode="singleInstance">activity>

使用Intent标志

启动Activity时,可以在传递给startActivity()的Intent中加入相应的标志,修改Activity与其任务的默认关联方式。可用于修改默认行为的标志包括:

FLAG_ACTIVITY_NEW_TASK

在新任务中启动Activity。如果已为正在启动的Activity运行任务,则该任务会转到前台并恢复其最后状态。与“singleTask”相同。

FLAG_ACTIVITY_SINGLE_TOP

如果正在启动的Activity是当前Activity(位于返回栈的顶部),则现有实例会接受对onNewIntent()的调用,而不是创建Activity的新实例。与“singleTop”相同。

FLAG_ACTIVITY_CLEAR_TOP

如果正在启动的Activity已在当前任务中运行,则会销毁当前任务顶部的所有Activity,并通过onNewIntent()将次Intent传递给Activity已恢复的实例(现在位于顶部),而不是启动该Activity的新实例。
FLAG_ACTIVITY_CLEAR_TOP通常与FLAG_ACTIVITY_NEW_TASK结合使用。仪器使用时,通过这些标志,可以找到其他任务中的现有Activity,并将其放入可从中响应Intent的位置。

处理关联

“关联”指示Activity优先属于哪个任务。默认情况下,同一应用中的所有Activity彼此关联。因此,默认情况下,同一应用中的所有Activity优先位于相同任务中。不过,可以通过修改Activity的默认关联,在不同应用中定义的Activity可以共享关联,或者可为在同一应用中定义的Activity分配不同的任务关联。
可以使用元素的taskAffinity属性修改任何给定Activity的关联。
taskAffinity属性取字符串值,该值必须不同于在元素中声明的默认软件包名称,因此系统使用该名称标识应用的默认任务关联。
在两种情况下,关联会起作用:
- 启动Activity的Intent包含FLAG_ACTIVITY_NEW_TASK标志
默认情况下,新Activity会启动到调用startActivity()的Activity任务中。它将推入与调用方相同的返回栈。但是,如果传递给startActivity的Intent包含FLAG_ACTIVITY_NEW_TASK标志,则系统会寻找其他任务来储存新Activity。这通常是新任务,但未做强制要求。如果现有任务与新Activity具有相同关联,则会将Activity启动到该任务中。否则,将开始新任务。
下面的例子中ActivityA启动ActivityB,在清单文件中设置ActivityB的taskAffinity属性,如下:

<activity android:name=".affinity.ActivityA">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
        <activity android:name=".affinity.ActivityB"
            android:taskAffinity="com.xks.wangli"
            >activity>

首先ActivityA启动ActivityB的代码如下:

Intent intent = new Intent(this, ActivityB.class);
//        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

上面的代码中没有在Intent中添加FLAG_ACTIVITY_NEW_TASK标志,那么操作后执行adb shell dumpsys activity得到的任务栈如下:
深入理解Activity——生命周期、启动模式、taskAffinity_第8张图片
下面再看下以如下代码启动ActivityB的效果:

 Intent intent = new Intent(this, ActivityB.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

效果如下:
深入理解Activity——生命周期、启动模式、taskAffinity_第9张图片
从上面两个对比可以看出,只有在Intent有FLAG_ACTIVITY_NEW_TASK时,taskAffinity属性才会生效。
- Activity将其allowReparenting属性设置为“true”
在这种情况下,Activity可以从其启动的任务移到到与其具有关联的任务(如果该任务出现在前台)。

清理返回栈

如果用户长时间离开任务,则系统会清除所有Activity的任务,根Activity除外。当用户再次返回到任务时,仅恢复根Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
可以使用下列几个Activity属性修改此行为:
- alwaysRetainTaskState
如果在任务的根Activity中将此属性设置为true,则不会发生上述的默认行为。即使在很长一段时间后,任务仍将所有Activity保留在其堆栈中。
- clearTaskOnLaunch
如果在任务的根Activity中将此属性设置为true,则每当用户离开任务然后返回时,系统都会讲堆栈清除到只剩下根Activity。换而言之,它与alwaysRetainTaskState正好相反。即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。
- finishOnTaskLaunch
此属性类似于clearTaskOnLaunch,但它对单个Activity起作用,而非整个任务。此外,它还有可能会导致任务Activity停止,包括根Activity。设置为“true”时,Activity仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。

你可能感兴趣的:(Android基础)