活动的:Activity 在栈顶,它是可视、有焦点、可接受用户输入的。Android 试图尽最大可能保持它活动状态,杀死其它 Activity 来确保当前活动 Activity 有足够的资源可使用。 当另外一个 Activity 被激活,这个将会被暂停。
暂停:Activity 可视,但是它没有焦点, 换句话说它被暂停了 。可能的原因是一个透明或者非全屏的 Activity 被激活。 当被暂停,一个 Activity 仍会当成活动状态,只不过是不可以接受用户输入。在极特殊的情况下,Android 将会杀死一个暂停的 Activity 来为活动的 Activity 提供充足的资源。当一个 Activity 变为完全隐藏,它将会变成停止。
停止:当一个 Activity 不是可视的,它“停止”了。这个 Activity 将仍然在内存中保存它所有的状态和会员信息。尽管如此,当其它地方需要内存时,它将是最有可能被释放资源的。 当一个 Activity 停止后,一个很重要的步骤是要保存数据和当前 UI 状态。一旦一个 Activity 退出或关闭了,它将变为待用状态。
待用:在一个 Activity 被杀死后和被装载前,它是待用状态的。待用 Acitivity 会被移除 Activity 栈,并且需要在显示和可用之前重新启动它。
在 android 的多 activity 开发中,activity 之间的跳转可能需要有多种方式,有时是普通的生成一个新实例,有时希望跳转到原来某个 activity 实例,而不是生成大量的重复的 activity。 加载模式便是决定以哪种方式启动一个跳转到原来某个 Activity 实例。
在 android 里,有 4 种 activity 的启动模式,这些启动模式可以在功能清单文件 AndroidManifest.xml 中进行设置,Activity 中的 launchMode 属性。 分别为:
标准模式,一调用 startActivity()方法就会产生一个新的实例。
启动 Activity 时每次都创建新的实例,仅一个例外:当栈顶的 activity 恰恰就是该 activity 的实例(即需要创建的实例)时,不再创建新实例。这解决了栈顶复用问题。
谷歌的官方文档上称,如果一个activity的启动模式为singleTask,那么系统总会在一个新任务的最底部(root)启动这个activity,并且被这个activity启动的其他activity会和该activity同时存在于这个新任务中。如果系统中已经存在这样的一个activity则会重用这个实例,并且调用他的onNewIntent()方法。即,这样的一个activity在系统中只会存在一个实例。其实官方文档中的这种说法并不准确,启动模式为singleTask的activity并不会总是开启一个新的任务。
设置singleTask启动模式的activity,它在启动的时候,会先在系统中查找属性值 affinity 等于它的属性值 taskAffinity 的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了 singleTask 启动模式的 activity 在新的任务中启动,就要为它设置一个独立的 taskAffinity 属性值。如果设置了 singleTask 启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的 activity 实例,如果存在,就会把位于这个 activity 实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。解决了在一个 task 中共享一个 activity。这样的一个activity在系统中只会存在一个实例。
设置了该模式的activity,总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其 activity会自动运行于另一个任务中。当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。解决了多个 task 共享一个 activity。
上面将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解,下面会通过一个简单的例子进行验证。由以上的介绍可知,standard和singleTop这两种启动模式行为比较简单,所以在下面的例子中,会对singleTask着重介绍。
以下为验证示例AndroidTaskTest。这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。以下为这个示例的manifest文件。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jg.zhang.androidtasktest"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name">
<activity android:label="@string/app_name"
android:name="com.jg.zhang.androidtasktest.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.jg.zhang.androidtasktest.SecondActivity"
android:launchMode="singleTask">
<intent-filter >
<action android:name="com.jg.zhang.androidtasktest.SecondActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"
android:label="@string/app_name" >
</activity>
</application>
</manifest>
由代码可见,MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。
测试逻辑很简单,在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。并且在onCreate方法中会以log的形式打印出当前activity所属的任务(Task)的Id。
int taskId = getTaskId();
Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " + taskId);
其他代码就不展示了。
运行该示例,并且点击MainActivity界面中的按钮,开启SecondActivity。在该示例中SecondActivity的启动模式为singleTask。按照官方文档的说法,SecondActivity会在一个新的任务中开启。但是查看打印出的log,发现MainActivity和SecondActivity所在的任务的Id相同。
所以,和官方文档表述的不同,MainActivity和SecondActivity是启动在同一个任务中的。其实,把启动模式设置为singleTask,framework在启动该activity时只会把它标示为可在一个新任务中启动,至于是否在一个新任务中启动,还要受其他条件的限制。现在在SecondActivity增加一个taskAffinity属性,如下所示:
<activity android:name="com.jg.zhang.androidtasktest.SecondActivity"
android:launchMode="singleTask"
android:taskAffinity="com.jg.zhang.androidtasktest.second">
<intent-filter >
<action android:name="com.jg.zhang.androidtasktest.SecondActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:
由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。
在这里便引出了manifest文件中<activity>的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息:
这就可以解释上面示例中的现象了,由第5条可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity为com.jg.zhang.androidtasktest,SecondActivity的taskAffinity为com.jg.zhang.androidtasktest.second,根据上面第4条,taskAffinity可以影响当 activity 以 FLAG_ACTIVITY_NEW_TASK 标志启动时,它会被启动到哪个任务中。这句话的意思是,当新启动的activity(SecondActivity)是以 FLAG_ACTIVITY_NEW_TASK 标志启动时(可以认为FLAG_ACTIVITY_NEW_TASK 和singleTask 作用相同,当启动模式为singleTask时,framework会将它的启动标志设为FLAG_ACTIVITY_NEW_TASK),framework会检索是否已经存在了一个affinity为com.jg.zhang.androidtasktest.second的任务(即一个TaskRecord对象):
上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:
为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.jg.zhang.androidtasktest.second 时的启动过程:
其实framework中对任务和activity 的调度是很复杂的,尤其是把启动模式设为singleTask或者以 FLAG_ACTIVITY_NEW_TASK标志启动时。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序。这也是官方文档上的建议。
行时分配到同一任务中,下面对此进行验证,在这里,会使用上面的示例AndroidTaskTest,并创建一个新的示例AndroidTaskTest1。AndroidTaskTest1由两个activity组成,分别为MianActivity和OtherActivity,在MianActivity中点击按钮会启动OtherActivity,该程序的界面和上一个类似,代码也类似,再此仅列出清单文件。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jg.zhang.androidtasktest1"
android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17" />
<application
android:allowBackup="true" android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.jg.zhang.androidtasktest1.MainActivity"
android:label="com.jg.zhang.androidtasktest1.MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.jg.zhang.androidtasktest1.OtherActivity"
android:label="com.jg.zhang.androidtasktest1.OtherActivity"
android:taskAffinity="com.jg.zhang.androidtasktest.second"
android:launchMode="singleTask">
</activity>
</application>
</manifest>
可以看到OtherActivity的启动模式被设置为singleTask,并且taskAffinity属性被设置为com.jg.zhang.androidtasktest.second,这和AndroidTaskTest应用中的SecondActivity相同。现在将这两个应用安装在设备上。执行以下操作:
启动AndroidTaskTest应用,在它的MianActivity中点击按钮开启SecondActivity,由上面的介绍可知secondActivity是运行在一个新任务中的,这个任务就是com.jg.zhang.androidtasktest.second。
然后按Home键回到Launcher,启动AndroidTaskTest1,在启动AndroidTaskTest1的入口Activity(MianActivity)时,会自动启动新的任务,那么现在一共有三个任务,AndroidTaskTest的MianActivity和SecondActivity分别占用一个任务,AndroidTaskTest1的MianActivity也占用一个任务。
在AndroidTaskTest1的MianActivity中点击按钮启动OtherActivity,那么这个OtherActivity是在哪个任务中呢?
所以由此可见,AndroidTaskTest的SecondActivity和AndroidTaskTest1的OtherActivity是在同一任务中的。AndroidTaskTest和AndroidTaskTest1这两个应用程序会开启两个进程,他们的所有组件分别运行在独立的进程中。com.jg.zhang.androidtasktest.second任务中的两个activity属于不同的应用,并且运行在不同的进程中,这也说明了一个问题:任务(Task)不仅可以跨应用(Application),还可以跨进程(Process)。
核心的 Intent Flag 有:
FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP、FLAG_ACTIVITY_RESET_TASK_IF_NEEDED、FLAG_ACTIVITY_SINGLE_TOP
核心的特性有:
taskAffinity、launchMode、allowTaskReparenting、clearTaskOnLaunch、alwaysRetainTaskState、finishOnTaskLaunch
Activity 在 Activity 栈(Task)中的加载顺序是可以控制的,这就需要用到 Intent Flag。
如果设置,这个 Activity 会成为历史 stack 中一个新 Task 的开始。一个 Task(从启动它的Activity 到下一个 Task 中的 Activity)定义了用户可以迁移的 Activity 原子组。Task 可以移动 到前台和后台;在某个特定 Task 中的所有 Activity 总是保持相同的次序。
这个标志一般用于呈现“启动”类型的行为:它们提供用户一系列可以单独完成的事情, 与启动它们的 Activity 完全无关。
使用这个标志,如果正在启动的 Activity 的 Task 已经在运行的话,那么,新的 Activity 将 不会启动;代替的,当前 Task 会简单的移入前台。参考 FLAG_ACTIVITY_MULTIPLE_TASK 标志, 可以禁用这一行为。
这个标志不能用于调用方对已经启动的 Activity 请求结果。
如果设置,当这个 Activity 位于历史 stack 的顶端运行时,不再启动一个新的。
当启动一个activity设置了该flag,如果要启动的这个 Activity 已经在当前的 Task 中运行:
这个启动模式还可以与 FLAG_ACTIVITY_NEW_TASK 结合起来使用:用于启动一个 Task 中的根 Activity,它会把那个 Task 中任何运行的实例带入前台,然后清除它直到根 Activity。这非常有用,例如,当从 Notification Manager 处启动一个 Activity。
如果设置,在activity启动前,与该activity关联的任务被清空(就是该activity将要加入的任务栈会被清空)。也就是说,新activity成为新任务的根,旧的活动都被结束了。本flag只能与FLAG_ACTIVITY_NEW_TASK联合使用。
如果在 Intent 中设置,并传递给 Context.startActivity(),这个标志将引发已经运行的 Activity 移动到历史 stack 的顶端。 例如,假设一个 Task 由四个 Activity 组成:A,B,C,D。如果 D 调用 startActivity()来启动 Activity B ,那么, B 会移动到历史 stack 的顶端,现在的次序变成 A,C,D,B 。如果 FLAG_ACTIVITY_CLEAR_TOP 标志也设置的话,那么这个标志将被忽略。
这个标志一般不是由程序代码设置的,如在 launchMode 中设置 singleTask 模式时系统帮你设定。
如果设置,这将在 Task 的 Activity stack 中设置一个还原点,当 Task 恢复时,需要清理 Activity。也就是说,下一次 Task 带着 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标记进入前台时(典型的操作是用户在主画面重启它),这个 Activity 和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的 Activity。
这在你的程序有分割点的时候很有用。例如,一个 e-mail 应用程序可能有一个操作是查看一个附件,需要启动图片浏览 Activity 来显示。这个 Activity 应该作为 e-mail 应用程序 Task 的一部分,因为这是用户在这个 Task 中触发的操作。然而,当用户离开这个 Task,然后从 主画面选择 e-mail app,我们可能希望回到查看的会话中,但不是查看图片附件,因为这让人困惑。通过在启动图片浏览时设定这个标志,浏览及其它启动的 Activity 在下次用户返回 到 mail 程序时都将全部清除。
如果设置,新的 Activity 不会在最近启动的 Activity 的列表中保存。
如果设置,并且这个 Intent 用于从一个存在的 Activity 启动一个新的 Activity,那么,这个作为答复目标的 Activity 将会传到这个新的 Activity 中。这种方式下,新的 Activity 可以调用 setResult(int),并且这个结果值将发送给那个作为答复目标的 Activity。
这个标志一般不由应用程序代码设置,如果这个 Activity 是从历史记录里启动的(常按 HOME 键),那么,系统会帮你设定。
不要使用这个标志,除非你自己实现了应用程序启动器。与 FLAG_ACTIVITY_NEW_TASK 结合起来使用,可以禁用把已存的 Task 送入前台的行为。当设置时,新的 Task 总是会启动来处理 Intent,而不管这是是否已经有一个 Task 可以处理相同的事情。 由于默认的系统不包含图形 Task 管理功能,因此,你不应该使用这个标志,除非你提供给用户一种方式可以返回到已经启动的 Task。 如果 FLAG_ACTIVITY_NEW_TASK 标志没有设置,这个标志被忽略。
如果在 Intent 中设置,并传递给 Context.startActivity()的话,这个标志将阻止系统进入下 一个 Activity 时应用 Acitivity 迁移动画。这并不意味着动画将永不运行——如果另一个 Activity 在启动显示之前,没有指定这个标志,那么,动画将被应用。这个标志可以很好的 用于执行一连串的操作,而动画被看作是更高一级的事件的驱动。
如果设置,新的 Activity 将不再历史 stack 中保留。用户一离开它,这个 Activity 就关闭了。 这也可以通过设置 noHistory 特性。
如果设置,作为新启动的 Activity 进入前台时,这个标志将在 Activity 暂停之前阻止从最 前方的 Activity 回调的onUserLeaveHint()。典型的,一个 Activity 可以依赖这个回调指明显式的用户动作引起的 Activity 移出后台。 这个回调在 Activity 的生命周期中标记一个合适的点,并关闭一些 Notification。如果一个 Activity 通过非用户驱动的事件,如来电或闹钟,启动的,这个标志也应该传递 给 Context.startActivity,保证暂停的 Activity 不认为用户已经知晓其 Notification。
待补充
待补充
如果是从 BroadcastReceiver 启动一个新的 Activity,或者是从 Service 往一个 Activity 跳转时,不要忘记添加 Intent 的 Flag 为 FLAG_ACTIVITY_NEW_TASK。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。