Activity和Task的基本模型
一个Activity可以启动另一个,即便是定义在不同应用程序中的Activity。例如,假设你想让用户显示一些地方的街景。
而这里已经有一个Activity可以做到这一点,因此,你的Activity所需要做的只是在Intent对象中添加必要的信息,并传递给startActivity()。
地图浏览将会显示你的地图。当用户按下BACK键,你的Activity会再次出现在屏幕上。
对于用户来说,看起来好像是地图浏览与你的Activity一样,属于相同的应用程序,即便是它定义在其它的应用程序里,并运行在那个应用程序的进程里。
Android通过将这两个Activity保存在同一个Task里来体现这一用户体验。简单来说,一个Task就是用户体验上的一个“应用”。
它将相关的Activity组合在一起,以stack的方式管理。用Activity启动Task的一典型就是用户在应用程序启动栏中选择一个Activity。
位于stack顶端的Activity是当前正在运行的——能够聚焦用户的动作。当一个Activity启动另一个新的Activity进入stack;它成为正在运行的Activity。
之前的Activity仍保留在stack中。当用户按下BACK键,当前的Activity从stack中退出,之前的那个成为正在运行的Activity。
注意1:一个task并不是对应一个线程,实际上一个task可以包含多个进程。
一个Task中可能有多个同一个Activity的实例时(如多个地图浏览)。
一个Task就是一组Activity,不是一个类或者在manifest中定义的一个元素。Task的属性值是由根Activity决定的(即由Task的第一个Activity设置)。例如Task的“affinity”;那个值就是从Task中的根Activity中读取的。
Task中的所有Activity作为一个单元一起移动。整个Task(整个Activity stack)可以进入前台或者退到后台。
例如,假设当前Task中的stack中有4个Activity——3个位于当前Activity下方。用户按下HOME键,进入到应用程序启动栏,
然后选择一个新的应用程序(实际上,一个新的Task)。当前Task退到后台,并且新Task中的根Activity会显示出来。
然后,经过一段时间后,用户回到Home画面,然后再次选择前一个应用程序(前一个Task)。
那个拥有4个Activity的Task会进入前台。当用户按下BACK键,屏幕不会显示用户刚刚离开的Activity(前一个Task的根Activity)。
而是,这个stack中的顶端Activity移除,相同Task中的前一个Activity会显示出来。
刚才描述的行为是Activity和Task的默认行为。但有方法来完全改变它。Task之间的关联和Task中的Activity行为,受启动Activity的Intent对象中设置的Flag和manifest文件中Activity的<activity>元素的特性值交互控制。调用者和响应者都有权决定如何发生。
注意:可以在Activity中通过getTaskId()得到它所在task的Id
核心的Intent Flag有:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP
核心的<activity>特性有:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
接下来的将描述一些Flag和特性的用法,如何相互影响,以及在使用时的建议。
Affinity和新Task
默认情况下,一个应用程序中(APK)的所有Activity都有相同的affinity(即manifest中的package名)
然而,每个Activity都可以在<activity>元素的taskAffinity特性上设置单独的值。
例如:
<activity android:name=".HelloActivity"
android:label="@string/app_name"
android:taskAffinity="com.teleca.robin.taskAffinity1">>
<intent-filter>
<action android:name="com.teleca.robin.Hello" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
在程序中可以通过如下的方式得到:
Intent mainIntent = new Intent("com.teleca.robin.Hello", null);
mainIntent.addCategory("android.intent.category.DEFAULT");
List<ResolveInfo> apps;
apps = getPackageManager().queryIntentActivities(mainIntent, 0);
if(apps==null)
System.out.println("no acitivity is find");
for(int i=0;i<apps.size();i++)
{
ActivityInfo a=apps.get(i).activityInfo;
System.out.println(a.name+" taskAffinity:"+a.taskAffinity);
}
定义在不同应用程序中的Activity可以共享同一个affinity,或者定义在同一个应用程序中的Activity设置不同的affinity。
Affinity在两种环境下工作:Intent对象包含FLAG_ACTIVITY_NEW_TASK标志,和Activity的allowTaskReparenting特性设置为“true”。
核心的Intent Flag
FLAG_ACTIVITY_NEW_TASK:
一个Activity一般通过调用startActivity()启动并加入到Task中。它同调用者一样,进入同一个Task。
然而,如果传递给startActivity()的Intent对象中包含FLAG_ACTIVITY_NEW_TASK时,系统会搜索一个新的Task来容纳新的Activity。
通常,如标志的名字所示,是一个新的Task。然而,并不是必须是。如果已经存在一个Task与新Activity的affinity相同,这个Activity就会加入到那个Task中。如果不是,启动一个新的Task。
如果启动它的acitve和新Activity的affinity相同,那么新Activity的会进入启动它的acitve所在的Task.
核心的<activity>特性:
allowTaskReparenting:
如果一个Activity的allowTaskReparenting特性设置为“true”,它就能从启动的Task中移到有着相同affinity的Task(这个Task进入到前台的时候)。
例如,在一个旅游的程序中定义了一个可以报告选择城市的天气情况的Activity。它和同一个应用程序的其它Activity一样,有着相同的Affinity(默认的Affinity),并且它允许重新宿主。
你的Activity中的一个启动了天气预报Activity,因此,它初始化到和你Activity相同的Task中。
如果旅游应用程序当前没被启动,那么下次它被启动时,天气预报那个Activity将会被显示(即在旅游应用程序的Task中被显示)。
如果从用户的角度出发,一个.apk文件包含多个“应用”的话,你可能希望为关联的Activity设置不同的affinity。
Launch Mode
这里4种不同的启动模式可以设置到<activity>元素的launchMode特性上:
standard(默认模式)
singleTop
singleTask
singleInstance
注意1:对于singleTask,如果新的Activity和旧的Activity的affinity一样时,这时它相当于FLAG_ACTIVITY_NEW_TASK。
注意2:对于singleTask和singleInstance的affinity最好不要和别的task的affinity相冲突。
这些模式有以下四点区别:
1, 哪个Task将容纳响应Intent的Activity。
对于“standard”和“singleTop”来说,除非Intent对象包含FLAG_ACTIVITY_NEW_TASK,那么新的Activity在是产生Intent的那个Task中(并调用startActivity())。
对比而言,“singleInstance”指示Activity总是一个Task的根。
系统会重新创建一个Task,并把新的Activity加入其中,它们不会加入到另一个Task中。
2, 是否有多个Activity的实例。“standard”和“singleTop”可以实例化多次。它们可以属于多个Task,一个特定的Task可以有相同Activity的多个实例。
对比而言,“singleTask”和“singleInstance”只能有一个实例。不可能在多个task中同时存在有该Activity的多个实例。
也不可能在一个task中存在有该Activity的多个实例。
3, 是否可以在同一个Task中拥有其它的Activity。“singleInstance”Activity保持单身,在它的Task中它是仅有的Activity。
如果它启动另一个Activity,那个Activity将会放入到不同的Task中,而不管它的启动模式——好像FLAG_ACTIVITY_NEW_TASK在Intent中一样。
其它三个模式允许多个Activity加入到这个Task中。
4, 是否一个新的实例启动来处理新的Intent。对于默认的“standard”来说,都是创建一个新的实例来响应新的Intent。每个实例处理一个Intent。
对于“singleTop”来说,如果它位于目标Task的顶端,那么,已经存在的实例就可以重复使用来处理这个新的Intent。
如果它不在顶端,那么它就不能重复使用。替代的,新的实例将创建来响应新的Intent,并进入到stack中。
例如,假设一个Task的Activity stack中包含根Activity A和其它Activity B,C,D,并且D位于顶端,因此,stack是A-B-C-D。有一个Intent来了,它要启动D类型的Activity。如果D有默认的“standard”启动模式,那么一个新的实例将被启动并且stack变成A-B-C-D-D。
然而,如果D的启动模式“singleTop”,已经存在的实例将去处理新来的Intent(因为它正好处在stack的顶端),并且stack依旧是A-B-C-D。
换句话说,如果来临的Intent是冲着B类型的,那么,B类型的实例将被创建启动而不管B的模式是“standard”或“singleTop”(因为B不处在stack的顶端),
因此,stack将会是A-B-C-D-B。
之前提到的,设备上不会出现超过一个实例的“singleTask”或“singleInstance”Activity,因此,那个实例都将去处理所有新来的Intent。
“singleInstance”Activity总是位于stack的顶端(因为它是task中唯一的Activity),因此,它总是处于能处理Intent的位置。
注意: 当一个新的实例创建来处理新的Intent时,用户可以按下BACK键返回到之前的状态(前一个Activity)。
但一个存在的实例来处理新的Intent时,用户不能按下BACK键返回到新Intent到来之前当前Activity的状态,它也是返回前一个Activity。
stack的清除
如果用户离开Task很长一段时间,系统会清除Task中的所有Activity,除根Activity外。当用户再次返回到这个Task时,和用户离开时不一样,仅仅只是初始化Activity呈现。
这样做的意图是,经过一些时间后,用户可能已经忘记之前正在做的事情,并且打算回到Task开始些新的时期。
注意1:弹出任务管理器不算离开Task
这是默认情况。下面一些Activity特性可以用于控制这一行为并且修改它:
alwaysRetainTaskState:
如果Task的根Activity的这个特性设置为“true”时,上面描述的默认行为不会发生。Task保留所有的Activity,即便是经过很长一段时间。
clearTaskOnLaunch:
如果Task的根Activity的这个特性设置为“true”时,当用户离开Task并返回时,stack会清除直到根Activity。换句话说,它是alwaysRetainTaskState的另一个极端。用户总是回到Task的初始化状态,即便是一个短暂的离开。
finishOnTaskLaunch:
这个特性和clearTaskOnLaunch相似,但它针对单个Activity,不是整个Task。它能使除了根Activity之外的任何Activity消失。
当它设置为“true”时,这个Activity仅在当前会话期间保持为Task的部分。如果用户离开并再次返回到这个Task,它就不再显示了。
注意:对于clearTaskOnLaunch和finishOnTaskLaunch所谓的清除总是在该task被再次启动时执行的。
这里还有其它的方式可以强制Activity从stack中移除。如果Intent对象中包含FLAG_ACTIVITY_CLEAR_TOP标志,并且目标Task中已经有一个这个类型Activity的实例,
而且这个实例应该处理这个Intent,那么,位于其上的Activity都将移除,这样,这个Activity就能在stack的顶端并响应这个Intent。
如果这个Activity的启动模式设定为“standard”,它也会从stack中清除,然后新的实例启动来响应这个Intent。
这是因为当启动模式设定为“standard“时,总是会创建一个新的实例来响应新的Intent。
FLAG_ACTIVITY_CLEAR_TOP经常与FLAG_ACTIVITY_NEW_TASK结合起来使用。当一起使用时,这些标志可以定位其它Task中已经存在的Activity,并且把它置于可以响应Intent的位置。
启动Task
如果一个Activity的Intent Filter的action为“android.intent.action.MAIN”、category为“android.intent.category.LAUNCHER”时,
它就可以作为一个Task的入口点。有这种类型的Filter会在导致这个Activity在应用程序启动栏显示一个图标和标签,给用户提供一个方式可以启动这个Task和在任何时候可以再次回到这个Task。
第二个能力很重要:用户一定可以离开一个Task,然后可以再次回到它。
基于这个原因,“singleInstance”应该只在有MAIN和LAUNCHER的Activity上使用。
注意1: 弹出任务管理器不算离开Task。
注意2: 应用程序启动的只是个Task,如果“singleInstance”的Activity没这个Intent Filter的话,自然不能在离开后返回到它。如果“singleTask”的Activity没这个Intent Filter的且它和入口Activity的taskAffinity又不一致的话,自然不能在离开后返回到它.
在面对FLAG_ACTIVITY_NEW_TASK时,也有相似的困难。如果这个标志导致一个Activity启动了一个新的Task,并且用户按下HOME键离开它,这里必须有方法可以再次回到它。
一些机能(如Notification Manager)总是在外部的Task中启动Activity,而不是作为自己的一部分,因此,它总是把FLAG_ACTIVITY_NEW_TASK标志放入Intent,然后传递给startActivity()。
如果你的Activity可能会被外部的机能(可能使用这个标志)调用,注意用户可以额外的方式可以返回到启动的Task。
如果你不想用户回到某个Activity,可以把<activity>元素的finishOnTaskLaunch设置为“true”。