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

    xiaoxiao2021-03-25  100

    基本用法

    startActivity startActivityForResult+onActivityResult

    生命周期

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

    保存与销毁

    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,操作如下图所示: 在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的情况,就不显示adb命令的结果了,直接以图片代替。整个任务栈中的变化情况如下图: 从上图可以看到,当第二次从ActivityA启动ActivityC时,因为任务栈中已经存在了ActivityC,所以会清除栈顶的ActivityA,栈中最后剩下的是ActivityC和ActivityA。

    singleInstance

    与 “singleTask” 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。 下面的例子中,ActivityA以singleInstance模式启动ActivityD,ActivityD再以标准模式启动ActivityA,然后再以singleInstance模式启动ActivityD,操作示意图如下: 其任务栈的变化如下图: 上图中,蓝色的为一个任务栈,黄色的为一个任务栈。当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得到的任务栈如下: 下面再看下以如下代码启动ActivityB的效果:

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

    效果如下: 从上面两个对比可以看出,只有在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仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。

    转载请注明原文地址: https://ju.6miu.com/read-8745.html

    最新回复(0)