Activity和Task的基本模型

转载自:http://hubingforever.blog.163.com/blog/static/17104057920101194575866/

一、基础知识

  一个 Activity 可以启动另一个Activity,即便是定义在不同应用程序中的 Activity 。例如,假设你想让用户显示一些地方的街景。
而这里已经有一个Activity可以做到这一点,因此,你的Activity所需要做的只是在Intent对象中添加必要的信息,并传递给startActivity()。
地图浏览将会显示你的地图。当用户按下BACK键,你的Activity会再次出现在屏幕上。
对于用户来说,看起来好像是地图浏览与你的Activity一样,属于相同的应用程序,即便是它定义在其它的应用程序里,并运行在那个应用程序的进程里。
Android通过将这两个Activity保存在同一个Task里来体现这一用户体验。简单来说,一个Task就是用户体验上的一个“应用”。
它将相关的Activity组合在一起,以stack的方式管理。用Activity启动Task的一典型就是用户在应用程序启动栏中选择一个Activity。
位于stack顶端的Activity是当前正在运行的——能够聚焦用户的动作。当一个Activity启动另一个新的Activitys时,新的Activity将运行,成为前端Activity,并压入stack。
之前的Activity仍保留在stack中。当用户按下 BACK 键,当前的Activity从 stack 中退出,之前的那个Activity,将重新运行,成为前端Actiivity。
注意 1: 一个task并不是对应一个线程,实际上一个task可以包含多个进程。
  一个Task中可能有多个同一个Activity的实例时(如多个地图浏览)。
一个Task就是一组Activity,不是一个类或者在manifest中定义的一个元素。Task的属性值是由根Activity决定的(即由Task的第一个Activity设置)。例如Task的“affinity”;那个值就是从Task中的根Activity中读取的。

Task中的所有Activity作为一个单元一起移动。整个Task(整个Activity stack)可以进入前台或者退到后台。
例如,假设当前Task 中的 stack A 中有4个Activity——3个位于当前 Activity A 下方。用户按下HOME键,进入到应用程序启动栏,
然后选择一个新的 应用程序B (实际上,一个新的Task,暂命名为 Task B ),假设它启动 Activity B 。当前Task退到后台,并且新Task中的根Activity会显示出来。
然后,经过一段时间后,用户回到Home画面,然后再次选择前一个 应用程序A (前一个Task)。
那个拥有4个Activity的 Task A 会进入前台。当用户按下BACK键,屏幕不会显示用户刚刚离开的 Activity B (前一个Task的根 Activity B )。
而是,这个 stack A 中的顶端Activity移除,相同Task中的前一个Activity会显示出来。
  刚才描述的行为是Activity和Task的默认行为。但有方法来完全改变它。Task之间的关联和Task中的Activity行为,受启动Activity的Intent对象中设置的Flag和manifest文件中Activity的<activity>元素的特性值交互控制。调用者和响应者都有权决定如何发生。
注意:可以在Activity中通过getTaskId()得到它所在task的Id
二、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);
        }
判断是否是Task的根和得到taskId可以Activity的 isTaskRoot()方法和 getTaskId()用如下的方法
text.setText("is TaskRoot:"+isTaskRoot()+"this is the No "+(id++)+"Hello"+"task id:"+this.getTaskId());
定义在不同应用程序中的Activity可以共享同一个affinity,或者定义在同一个应用程序中的Activity设置不同的affinity。
Affinity在两种环境下工作:Intent对象包含 FLAG_ACTIVITY_NEW_TASK标志,和Activity的 allowTaskReparenting特性设置为“true”。
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.
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()的Activity)。
  对于“singleTask”来说,如果它的taskAffinity属性和启动它Activity的一样的话,那么它是产生在启动它Activity的Task中(即调用startActivity()的Activity),否则它是产生在新Task中,且是该Task的根
    对比而言,“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的位置。
“singleTask”Activity可 能有或没有其它Activity处于它的上方,它可能是根,也可能不是根。 如果它的 android:taskAffinity 和启动它的acitivity的 android:taskAffinity 不一样,它就总是位于Activity stack的底部,它就是根,如果一样,它就不是位于Activity stack的底部,它就不是根。 如果在它上方有activity,而这时来个启动该 “singleTask”ActivityIntent,那么是 该Activity处理该Intent,而不是它上方的Activity。这时 上方的所有Activity会被清除掉,当然也Destroy
如果在它上方有activity,而这时来个启动其上方的某个Activity的Intent,那么是上方的Activity处理该Intent,而不是该“singleTask”Activity。
    当一个存在的Activity请求去处理一个新的Intent时,Intent对象将传到该Activity的 onNewIntent() 的方法中。(原来启动Activity的Intent对象可以通过调用getIntent()得到。)
注意当一个新的实例创建来处理新的Intent时,用户可以按下BACK键返回到之前的状态(前一个Activity)。
但一个存在的实例来处理新的Intent时,用户不能按下BACK键返回到新Intent到来之前那个Activity的状态,它也是返回stack中的当前Activity下方的Activity。

三、Task中Activity的清理
    如果用户离开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上使用。
“singleTask”在没有MAIN和LAUNCHER的Activity上使用,应该让它和入口Activity(有MAIN和LAUNCHER的Activity)的taskAffinity一致
例如,假设这个Filter没有且它和入口Activity的taskAffinity又不一致的话:一个Intent启动了一个“singleTask”Activity,初始化一个新的Task,然后用户花费了一些时间在它上面。然后,用户按下HOME键。现在,这个Task处于后台并且被HOME画面遮盖。
由于它不能在应用程序启动栏显示,用户就没有办法可以返回它。
注意 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”。
五、非Activity Context中启动Activity
    对于Context的startActivity方法,如果不是在其子类(Activity)中调用,那么必须对Intent加 上 FLAG_ACTIVITY_NEW_TASK
具体可以参照Context中对startActivity方法的说明:
public void  startActivity (Intent intent)
    Launch a new activity. You will not receive any information about when the activity exits.
Note that if this method is being called from outside of an Activity Context, 
then the Intent must include the FLAG_ACTIVITY_NEW_TASK launch flag. This is because, without being started from an existing Activity, there is no existing task in which to place the new activity and thus it needs to be placed in its own separate task.
This method throws ActivityNotFoundException if there was no Activity found to run the given Intent.
Parameters
intent     The description of the activity to start.

你可能感兴趣的:(Activity和Task的基本模型)