1.2Activity的启动模式与任务栈

Activity的启动模式

系统通过“任务栈”对Activity进行管理,任务栈遵循“先进后出的原则”。Activity有四种启动模式:standard,singleTop,singleTask,singleInstance。

1. standard :标准模式

  • 这是系统的默认模式,每次启动一个Activity系统都会重新创建一个新的实例,不管这个实例是否已经存在。

  • 这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以运行在不同的任务栈中。

  • 在这种模式下,新启动的Activity会添加到启动它的Activity所在的任务栈中。注意,如果使用没有任务栈的Context(比如ApplicationContext)启动standard模式下的Activity,系统将会报错,这个时候,就不能使用standard模式启动了,可以在启动时加一个FlagIntent.FLAG_ACTIVITY_NEW_TASK,这样启动的时候就会为新的Activity创建一个新的任务栈。

Intent i = new Intent(getApplicationContext(), ActivityB.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);

2. singleTop:栈顶复用模式

  • 这种模式下,比如我们要启动AcitivityB,如果已经有一个ActivityB位于任务栈中,并且它位于栈顶的位置,那么新的ActivityB不会被重新创建,系统会调用当前ActivityB 的 onNewIntent(),而它的onCreate()和onStart()均不会被调用。

  • 如果任务栈中不存在AcitivityB,或者任务栈中的AcitvityB不是位于栈顶的位置,那么系统会重新创建一个ActivityB。

3. singleTask:栈内复用模式

  • 这是一种单实例模式,在这种模式下,比如我们要启动AcitivityD,只要目标任务栈中存在AcitivityD,那么多次启动此AcitivityD系统都不会创建新的实例,和singleTop一样,系统会调用onNewIntent()。

  • 如果ActivityD以singleTask模式启动,系统首先会寻找是否存在ActivityD所需的任务栈,如果不存在则会新建一个,然后把ActivityD添加进去。如果任务栈已经存在,就要看任务栈中是否存在ActivityD,如果不存在,则创建ActivityD并把它添加到任务栈的栈顶,否则,由于singleTask模式默认具有clearTop效果,位置处于ActivityD之上所有Activity都会自动出栈,让ActivityD处于栈顶位置。

4. singleInstance:单实例模式

  • 这是一种加强版的singleTask模式,它除了具有singleTask所具有的所有特性外,还加强了一点,该模式下的Activity始终会单独处于一个任务栈中。

  • 一个Activity以singleInstance模式第一次启动时,系统会新建一个任务栈,并创建Activity的实例加入到这个任务栈中,后续再次启动该Activity,系统都不会创建Activity实例,也不会为它新建任务栈,除非这个任务栈被销毁了。

如何给Activity指定启动模式?

有两种方法,第一种是通过AndroidManifest.xml给Activity指定launchMode:

<activity
    android:name="me.cv.main.SecondActivity"
    android:launchMode="singleTask"
    android:label="@string/app_name" />

另一种是通过在Intent中设置Intent Flag,比如:

Intent intent = new Intent()
intent.setClass(MainActivity.this, SecondActivity.class) ; 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent) ;
  • 这两种方式都可以为Activity设置启动模式,但是二者还是有区别的,首先,优先级上,第二种方式要优于第一种,当两种方式同时存在,以第二种为准;其次,两种方式在限定范围上有所不同,比如,第一种方式无法直接为Activity设定Intent.FLAG_ACTIVITY_CLEAR_TOP 等标识,而第二种方式无法为Activity指定singleInstance模式。

什么是Activity所需的任务栈?

前面多次提到“任务栈”这个概念,那到底什么是任务栈呢?Android任务栈简单介绍如下:

  1. Android任务栈又称为Task,它是一个栈结构,具有后进先出的特性,用于存放我们的Activity组件。

  2. 我们每次打开一个新的Activity或者退出当前Activity都会在一个称为任务栈的结构中添加或者减少一个Activity组件,因此一个任务栈包含了一个activity的集合, android系统可以通过Task有序地管理每个activity,并决定哪个Activity与用户进行交互:只有在任务栈栈顶的activity才可以跟用户进行交互。

  3. 在我们退出应用程序时,必须把所有的任务栈中所有的activity清除出栈时,任务栈才会被销毁。当然任务栈也可以移动到后台, 并且保留了每一个activity的状态. 可以有序的给用户列出它们的任务, 同时也不会丢失Activity的状态信息。

  4. 需要注意的是,一个App中可能不止一个任务栈,某些特殊情况下,单独一个Activity可以独享一个任务栈。还有一点就是一个Task中的Activity可以来自不同的App,同一个App的Activity也可能不在一个Task中。

任务栈关键属性:taskAffinity

TaskAffinity特点如下:

  • “Affinity”这个单词有“密切关系,吸引力”的意思, TaskAffinity 参数标识着Activity更倾向于加入的任务栈的名称,默认情况下,一个应用中Activity都加入到和包名相同的任务栈中。

  • TaskAffinity 属性一般跟singleTask模式、allowTaskReparenting属性或Intent.FLAG_ACTIVITY_NEW_TASK结合使用,在其他情况下没有实际意义。

  • TaskAffinity 属性默认的值为当前应用的包名,所以其值应和包名不一样才能起作用。

  • TaskAffinity 属性的值需包含至少一个“.”。

怎么给Activity指定任务栈呢?

默认情况下,Activity所需任务栈的名字和应用的包名相同,如果想给Activity指定其他任务栈,可以在AndroidManifest.xml中给Activity添加taskAffinity属性,而taskAffinity的用法有三种,分别是:

  • 1、taskAffinity与singleTask结合使用:
<activity    
    android:name="me.cv.main.SecondActivity"    
    android:taskAffinity="me.cv.test1"    
    android:launchMode="singleTask">
</activity>

我们已经知道,当Activity的launchMode被指定为”singleTask”时,启动该Activity会先寻找它所需的任务栈是否存在,我们这里指定了taskAffinity,系统就会查询是否存在名字叫做”me.cv.test1”的任务栈,不存在则创建并新建Activity实例放进去,否则继续遵循singleTask的特性判断是否任务栈是否存在该Acitivity,从而决定是新建Acitivity实例,还是调用onNewIntent并把位于该Acitivity之上的Activity出栈。

  • 2、taskAffinity与Intent.FLAG_ACTIVITY_NEW_TASK结合使用:
<activity    
    android:name="me.cv.main.SecondActivity"    
    android:taskAffinity="me.cv.test2">
</activity>
Intent intent = new Intent()
intent.setClass(MainActivity.this, SecondActivity.class) ; 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent) ;

这种方法与跟singleTask结合使用类似,只有一点不同,就是它将遵循Intent.FLAG_ACTIVITY_NEW_TASK的规则,下文会详细说明,这里简单讲一下。这种情况下启动Activity同样会先寻找它所需的任务栈是否存在,在这里是名字叫做”me.cv.test2”的任务栈,不存在则创建并新建Activity实例放进去。如果存在!注意了, 如果任务栈存在情况会比较复杂,如果该Activity已经存在与任务栈中,并且在栈底位置,那么整个任务栈会切换到前台,并保持栈中Activity的顺序和状态,就是说这个时候看到任务栈中栈顶位置那个Activity;如果任务栈存在但是Activity不在栈底位置,那么仍然会新建Activity实例并放到栈顶位置。以上说的Activity都是指launchMode为standar模式的,如果是其他模式,还会附带其launchMode的效果。这也说明了一个问题,Intent.FLAG_ACTIVITY_NEW_TASK 并不能等价于singleTask模式,它并不具备 singleTask自带的clearTop效果,Intent.FLAG_ACTIVITY_NEW_TASK 和启动模式组合使用会产生不同的效果。

  • 3、taskAffinity与allowTaskReparenting结合使用:
<activity    
    android:name="me.cv.main.SecondActivity"    
    android:taskAffinity="me.cv.test3"
    android:allowTaskReparenting="true">
</activity>

这是个非常有意思用法,首先我们来聊聊allowTaskReparenting属性,它的主要作用是Activity的迁移,即从一个task迁移到另一个task,这个迁移跟Activity的taskAffinity有关。当allowTaskReparenting的值为“true”时,则表示Activity能从启动的Task移动到有着affinity的Task(当这个Task进入到前台时),当allowTaskReparenting的值为“false”,表示它必须呆在启动时呆在的那个Task里。如果这个特性没有被设定,元素(当然也可以作用在每次activity元素上)上的allowTaskReparenting属性的值会应用到Activity上。默认值为“false”。这样说可能还比较难理解,我们举个例子,比如现在有两个应用A和B,A启动了B的一个ActivityC,然后按Home键回到桌面,再单击B应用时,如果此时,allowTaskReparenting的值为“true”,那么这个时候并不会启动B的主Activity,而是直接显示已被应用A启动的ActivityC,我们也可以认为ActivityC从A的任务栈转移到了B的任务栈中。这就好比我们在路边收养了一只与主人走失了的猫,养着养着突然有一天,主人找上门来了,这只猫也就被带回去了。我们通过图解来更好地理解这种情景:


图片出自:http://blog.csdn.net/javazejian/article/details/52072131

常用的Intent Flag有哪些?

Intent.FLAG_ACTIVITY_NEW_TASK

设置此状态,记住以下原则,首先会查找是否存在和被启动的Activity具有相同的亲和性的任务栈(即taskAffinity,注意同一个应用程序中的Activity的亲和性在没有修改的情况下是一样的,一般来讲就是包名,所以下面的a情况会在同一个栈中),如果有,则直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的Activity顺序不变,如果没有,则新建一个栈来存放被启动的Activity。(这里说到的Activity都是standard模式。 )
  a. 前提 : Activity A和Activity B在同一个应用中。
  操作: Activity A启动开僻Task堆栈(堆栈状态:A),在Activity A中启动Activity B, 启动Activity B的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,Activity B被压入Activity A所在堆栈(堆栈状态:AB)。
  原因: 默认情况下同一个应用中的所有Activity拥有相同的taskAffinity。
  
  b. 前提 : Activity A在名称为”TaskOne应用”的应用中, Activity C和Activity D在名称为”TaskTwo应用”的应用中。

  操作1 : 在Launcher中单击“TaskOne应用”图标,Activity A启动开僻Task堆栈,命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C, 启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,Android系统会为Activity C开僻一个新的Task,命名为TaskB(TaskB堆栈状态: C), 长按Home键,选择TaskA,Activity A回到前台, 再次启动Activity C(两种情况:1.从桌面启动;2.从Activity A启动,两种情况一样), 这时TaskB回到前台, Activity C显示,供用户使用, 即:包含FLAG_ACTIVITY_NEW_TASK的Intent启动Activity的Task正在运行,则不会为该Activity创建新的Task,而是将原有的Task返回到前台显示。

  操作2 : 在Launcher中单击”TaskOne应用”图标,Activity A启动开僻Task堆栈,命名为TaskA(TaskA堆栈状态: A),在Activity A中启动Activity C,启动Activity C的Intent的Flag设为FLAG_ACTIVITY_NEW_TASK,Android系统会为Activity C开僻一个新的Task,命名为TaskB(TaskB堆栈状态: C), 在Activity C中启动Activity D(TaskB的状态: CD) 长按Home键, 选择TaskA,Activity A回到前台, 再次启动Activity C(从桌面或者ActivityA启动,也是一样的),这时TaskB回到前台, Activity D显示,供用户使用。说明了在此种情况下设置FLAG_ACTIVITY_NEW_TASK后,会先查找是不是有Activity C存在的栈,根据亲和性(taskAffinity),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的顺序不变。

Intent.FLAG_ACTIVITY_NO_ANIMATION
  禁止activity之间的切换动画
  
Intent.FLAG_ACTIVITY_NO_HISTORY
  该Activity将不在stack中保留,用户一离开它,这个Activity就关闭了。
  
Intent.FLAG_ACTIVITY_NO_USER_ACTION
  禁止activity调用onUserLeaveHint()函。onUserLeaveHint()作为activity周期的一部分,它在activity因为用户要跳转到别的activity而退到background时使用。比如,在用户按下Home键(用户的操作),它将被调用。比如有电话进来(不属于用户的操作),它就不会被调用。注意:通过调用finish()时该activity销毁时不会调用该函数。
  
Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
  如果给Intent对象设置了这个标记,这个Intent对象被用于从一个存在的Activity中启动一个新的Activity,那么新的这个Activity不能用于接受发送给顶层activity的intent,这个新的activity的前一个activity被作为顶部activity。
  
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
  如果在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标志也设置的话,那么这个标志将被覆盖。
  
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  这个标记在以下情况下会生效:1.启动Activity时创建新的task来放置Activity实例;2.已存在的task被放置于前台。系统会根据affinity对指定的task进行重置操作,task会压入某些Activity实例或移除某些Activity实例。我们结合上面的FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET可以加深理解。
  
Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS
  api21加入。
  默认情况下通过FLAG_ACTIVITY_NEW_DOCUMENT启动的activity在关闭之后,task中的记录会相对应的删除。如果为了能够重新启动这个activity你想保留它,就可以使用者个flag,最近的记录将会保留在接口中以便用户去重新启动。接受该flag的activity可以使用autoRemoveFromRecents去复写这个request或者调用Activity.finishAndRemoveTask()方法。
  
Intent.FLAG_ACTIVITY_SINGLE_TOP
  singleTop一样
  
Intent.FLAG_ACTIVITY_TASK_ON_HOME
  api11加入。
  把当前新启动的任务置于Home任务之上,也就是按back键从这个任务返回的时候会回到home,即使这个不是他们最后看见的activity,注意这个标记必须和FLAG_ACTIVITY_NEW_TASK一起使用。
  
Intent.FLAG_DEBUG_LOG_RESOLUTION
  将log置为可用状态,如果设置了这个flag,那么在处理这个intent的时候,将会打印相关创建日志。
  
Intent.FLAG_EXCLUDE_STOPPED_PACKAGES和FLAG_INCLUDE_STOPPED_PACKAGES
  在3.1之后,系统的package manager增加了对处于“stopped state”应用的管理,这个stopped和Activity生命周期中的stop状态是完全两码事,指的是安装后从来没有启动过和被用户手动强制停止的应用,与此同时系统增加了2个Flag:FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES ,来标识一个intent是否激活处于“stopped state”的应用。当2个Flag都不设置或者都进行设置的时候,采用的是FLAG_INCLUDE_STOPPED_PACKAGES的效果。
  
Intent.FLAG_FROM_BACKGROUND
  用来标识该intent的操作是一个后端的操作而不是一个直接的用户交互。
FLAG_GRANT_PERSISTABLE_URI_PERMISSION
  api19添加
  当和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用时,uri权限在设置重启之后依然存在直到用户调用了revokeUriPermission(Uri, int)方法,这个标识仅为可能的存在状态提供许可,接受的应用必须要调用takePersistableUriPermission(Uri, int)方法去实际的变为存在状态。
  
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
  api21加入。
  当和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用时,uri的许可只用匹配前缀即可(默认为全部匹配)。
FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION
  如果设置FLAG_GRANT_READ_URI_PERMISSION这个标记,Intent的接受者将会被赋予读取Intent中URI数据的权限和lipData中的URIs的权限。当使用于Intent的ClipData时,所有的URIs和data的所有递归遍历或者其他Intent的ClipData数据都会被授权。FLAG_GRANT_WRITE_URI_PERMISSION同FLAG_GRANT_READ_URI_PERMISSION只是相应的赋予的是写权限。
  一个典型的例子就是邮件程序处理带有附件的邮件。进入邮件需要使用permission来保护,因为这些是敏感的用户数据。然而,如果有一个指向图片附件的URI需要传递给图片浏览器,那个图片浏览器是不会有访问附件的权利的,因为他不可能拥有所有的邮件的访问权限。针对这个问题的解决方案就是per-URI permission:当启动一个activity或者给一个activity返回结果的时候,呼叫方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION . 这会使接收该intent的activity获取到进入该Intent指定的URI的权限,而不论它是否有权限进入该intent对应的content provider。
  
Intent.FLAG_RECEIVER_FOREGROUND
  api16添加。
  当发送广播时,允许其接受者拥有前台的优先级,更短的超时间隔。
  
Intent.FLAG_RECEIVER_NO_ABORT
  api19添加
  如果这是一个有序广播,不允许接受者终止这个广播,它仍然能够传递给下面的接受者。
  
Intent.FLAG_RECEIVER_REGISTERED_ONLY
  如果设置了这个flag,当发送广播的时,动态注册的接受者才会被调用,在Androidmanifest.xml 里定义的Receiver 是接收不到这样的Intent 的。
  
Intent.FLAG_RECEIVER_REPLACE_PENDING
  api8添加。
  如果设置了的话,ActivityManagerService就会在当前的系统中查看有没有相同的intent还未被处理,如果有的话,就由当前这个新的intent来替换旧的intent,所以就会出现在发送一系列的这样的Intent 之后,中间有些Intent 有可能在你还没有来得及处理的时候, 就被替代掉了的情况

参考阅读

《android开发艺术探索》
http://blog.csdn.net/self_study/article/details/48055011
http://blog.csdn.net/javazejian/article/details/52072131
http://blog.csdn.net/javazejian/article/details/52071885

猜你喜欢

转载自blog.csdn.net/cvStronger/article/details/79079958