[置顶] android深入解析Activity的launchMode启动模式,Intent Flag,taskAffinity

时间:2021-01-16 16:44:47

  最近看到一篇文章讲launchMode,想到以前的一次面试,就问了这一个问题,最基本的大家都知道,但是详细的我就迷糊了,最终失败了,所以在此总结一下,希望能够帮助一下大家

LaunchMode

  launchMode分为四种:
  

standard

  standard启动模式为最基本的启动模式,默认为该种启动模式,特点就是每当发送一个intent请求打开该activity时,都会创建一个新的activity实例。实际使用情况分为两种,一种是本应用打开,一种是跨应用打开:  

  • 本应用打开,新创建的activity实例放入本应用,即发送intent的task栈的顶部,这个比较简单
  • 跨引用打开,这里有一个需要注意的地方是跨应用打开的时候会在 Recent app 页面显示两个独立项,但是此时它们两个 Activity 仍然是在一个栈中,非常感谢@梦想编制楠灬 的指正,跨应用打开在 Standard 模式下也是在一个栈中,虽然在 Recent app 页面是两个页面:
    [置顶]        android深入解析Activity的launchMode启动模式,Intent Flag,taskAffinity
    但是问题来了,又一次我偶然发现使用浏览器打开手机上的bilibili则是在同一个 Recent App 项中:
    [置顶]        android深入解析Activity的launchMode启动模式,Intent Flag,taskAffinity
    使用命令adb shell dumpsys activity获取手机的的activity栈的详细信息
Running activities (most recent first):
TaskRecord{9312e08 #811 A=com.htc.task.browser U=0 sz=2}
Run #1: ActivityRecord{1409e01a u0 tv.danmaku.bili/.ui.video.VideoDetailsActivity t811}
Run #0: ActivityRecord{64f3f7c u0 com.htc.sense.browser/.BrowserActivity t811}

如上图所示,com.htc.sense.browser/.BrowserActivity和tv.danmaku.bili/.ui.video.VideoDetailsActivity在同一个 recent app 页面中,这是怎么回事了,最后我发现有一个intent的flag变量有该作用:FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,只要将该flag设置进intent中就会将跨应用的activity打开在同一个 Recent app 中,感兴趣的可以试一下。

singleTop

  
  singleTop其实和standard几乎一样,和standard算一组,使用singleTop的Activity也可以创建很多个实例。唯一不同的就是,如果调用的目标Activity已经位于调用者的Task的栈顶,则不创建新实例,而是使用当前的这个Activity实例,并调用这个实例的onNewIntent方法。
  这个使用场景比较少,可以使用的例子比如用户已经在当前activity,用户点击一条推送消息之后也需要跳转到当前activity,那么为了避免activity的重复打开,则需要将该activity设置为singleTop并且复写onNewIntent即可。
  如果是外部程序启动singleTop的Activity,表现都和standard一样。

singleTask

  使用singleTask启动模式的Activity 在系统中 只会存在一个实例。如果这个实例已经存在,intent就会通过onNewIntent传递到这个Activity,并且将栈中该activity之上的activity清除(销毁过程会调用Activity生命周期回调),如果不存在新的Activity实例将被创建。
  [置顶]        android深入解析Activity的launchMode启动模式,Intent Flag,taskAffinity

 实际使用情况也分为两组:

  • 本应用启动,在一个应用中启动设置为singleTask的activity,如果该activity在task栈中不存在,则会创建一个新的实例放在栈顶,如果在activity的task栈中已经存在了该activity实例,则会将栈中该activity实例之上的其他activity实例清空,并且会调用该activity的onNewIntent方法。最常用的使用例子就是首页,比如首页上面已经有了很多的activity,回到首页就可以使用这种方式,然后复写首页的onNewIntent方法。使用提示:onNewIntent方法中不能进行fragment的相关操作 http://blog.sina.com.cn/s/blog_5da93c8f0101rgb2.html

  • 跨应用启动,由于整个系统只能存在activity的一个实例,所以如果系统中不存在该activity,则会启动一个新的task去启动该activity,并且将该activity放入栈底。如果系统中存在该activity实例,则会直接启动该activity,调用该activity的onNewIntent的方法,同时将该activity task栈上面的其他activity清空(销毁过程会调用Activity生命周期回调),此时如果用户摁下返回键,那么将在singleTask activity的task栈中操作,即返回该栈中singleTask activity的上一个activity直到该栈中无activity时才会返回到最开始启动singleTask activity的activity中。还有一种情况是singleTask Activity所在的应用进程存在,但是singleTask Activity实例不存在,那么从别的应用启动这个Activity,新的Activity实例会被创建,并放入到所属进程所在的Task中,并位于栈顶位置。

注意如果使用了singleTask,FLAG_ACTIVITY_RESET_TASK_IF_NEEDED这个flag将会失效。
  需要特别注意的是:
  1.在4.x和之前的系统下,A1(startActivityForResult)->A2(singleTask, startActivityForResult)->A3->A4,当A1打开A2之后会立即回调onActivityResult()函数,A2打开A3仍然可以正常回调onActivityResult();但是从5.0开始,A1打开A2的时候 onActivityResult() 函数也能正常的回调,不会立即回调。
  2.在4.x和之前的系统下,A1(startActivityForResult)->A2(singleTask, startActivityForResult)->A3,生命周期的流程是

A1.onStart()->A1.onResume()(startActivityForResult打开A2)->A1.onPause()
->
A1.onActvitiyResult->A1.onResume()->A1.onPause()->A2.onStart()
->
A2.onResume()->A1.onStop()(startActivityForResult打开A3)->A2.onPause()
->
A3.onStart()->A3.onResume()->A2.onStop()

(注:onCreate和onDestroy这两个生命周期没有变化,所以没有加进去,还有一个是A1.onStop生命周期在A2.onResume之后这个是不一定的,视情况而定),上面的变化必须要是startActivityForResult()+5.0之前的系统才会出现,可以推测是由于onActivityResult()函数引起的这个问题。

singleInstance

  和singleTask类似,在系统中 只会存在一个实例,唯一的区别就是系统不会在singleInstance activity的task栈中启动任何其他的activity,singleInstance activity栈中仅仅只能有该activity的实例,其他任何从这个activity启动的activity都会在其他的栈中被打开。
  虽然使用adb shell dumpsys activity可以看到singleInstance activity在一个独立的task中,但是在任务管理器中,还是显示的一个
  [置顶]        android深入解析Activity的launchMode启动模式,Intent Flag,taskAffinity
  需要特别注意的是:
  1.A1->A2(SingleInstance),摁下 Home 键之后,点击应用图标再次进入应用,返回的是 A1 页面,这是因为 A2 在另一个单独的 Activity task 栈中,点击图标返回的是主 Activity 栈,所以此时显示的 A1 页面,而不是 A2 页面。
  2.注意singleInstance的返回键的处理和上面3个 mode 有区别,如果是使用正常的 startActivity 进行的启动,启动顺序A1->A2->A3(singleInstance)->A4,在A4页面摁下back键,返回的是A2,再返回A1,接着再次摁下back键,返回的才是A3;如果是使用 startActivityForResult 启动的,在4.x和之前的系统下,表现和之前是一样的,但是在5.0开始,A1->A2-A3(singleInstance + startActivityForResult)->A4,在A4摁下返回键,返回的是A3->A2->A1,这个需要着重说明一下。
  3.另一个比较重要的是,在4.x和之前的系统下,A1(startActivityForResult)->A2(singleInstance, startActivityForResult)->A3->A4,当A1打开A2之后会立即回调onActivityResult()函数,A2打开A3也会立即回调onActivityResult()函数;但是从5.0开始,A1打开A2和A2打开A3的两种情况下 onActivityResult() 函数都能正常的回调,不会立即回调。
  4.还有一个比较重要的是,在4.x和之前的系统下,A1(startActivityForResult)->A2(singleInstance, startActivityForResult)->A3,生命周期的流程是

A1.onStart()->A1.onResume()(startActivityForResult打开A2)->A1.onPause()
->
A1.onActvitiyResult->A1.onResume()->A1.onPause()->A2.onStart()
->
A2.onResume()->A1.onStop()(startActivityForResult打开A3)->A2.onPause()
->
A2.onActivityResult()->A2.onResume()->A2.onPause()->A3.onStart()
->
A3.onResume()->A2.onStop()

和singleTask一样,也是必须要是startActivityForResult()+5.0之前的系统才会出现,区别就只是A2打开A3的情况不同。

Intent Flag

  Intent的flag有很多,介绍一下吧

FLAG_ACTIVITY_BROUGHT_TO_FRONT
  比方说我现在有A,在A中启动B,在A中Intent中加上这个标记。此时B就是以FLAG_ACTIVITY_BROUGHT_TO_FRONT 这个启动的,在B中再启动C,D(正常启动C,D),如果这个时候在D中再启动B,这个时候最后的栈的情况是 A,C,D,B.。
FLAG_ACTIVITY_CLEAR_TASK
  如果在调用startActivity时传递这个标记,该task栈中的其他activity会先被清空,然后该activity在该task中启动,也就是说,这个新启动的activity变为了这个空task的根activity。所有老的activity都结束掉。该标志必须和FLAG_ACTIVITY_NEW_TASK一起使用。
FLAG_ACTIVITY_CLEAR_TOP
  如果该activity已经在task中存在,并且设置了该task,系统不会启动新的 Activity 实例,会将task栈里该Activity之上的所有Activity一律结束掉,然后将Intent发给这个已存在的Activity。Activity收到 Intent之后,或者在onNewIntent()里做下一步的处理,或者自行结束然后重新创建自己。如果 Activity 在 AndroidMainifest.xml 里将启动模式设置成默认standard模式,并且 Intent 里也没有设置 FLAG_ACTIVITY_SINGLE_TOP,那么他将会结束并且重启;否则则会传递到onNewIntent方法,FLAG_ACTIVITY_CLEAR_TOP 还可以和 FLAG_ACTIVITY_NEW_TASK 配合使用,用来启动一个task栈的根activity,他将会把该栈清空为根状态,比如从notification manager启动activity。
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
  已经废弃,请使用FLAG_ACTIVITY_NEW_DOCUMENT
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
  设置完之后,新的activity将不会添加到当前activity列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。他等同于在XML中指定Activity的属性android:excludeFromRecents=”true”。
FLAG_ACTIVITY_FORWARD_RESULT
  如果设置,并且这个Intent用于从一个存在的Activity启动一个新的Activity,那么,这个作为答复目标的Activity将会传到这个新的Activity中。这种方式下,新的Activity可以调用setResult(int),并且这个结果值将发送给那个作为答复目标的Activity。
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
  一般由系统调用,比如长摁home键从历史记录中启动。
FLAG_ACTIVITY_MULTIPLE_TASK
  这个标识用来创建一个新的task栈,并且在里面启动新的activity(所有情况,不管系统中存在不存在该activity实例),经常和FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一起使用。这上面两种使用场景下,如果没有带上FLAG_ACTIVITY_MULTIPLE_TASK标识,他们都会使系统搜索存在的task栈,去寻找匹配intent的一个activity,如果没有找到就会去新建一个task栈;但是当和FLAG_ACTIVITY_MULTIPLE_TASK一起使用的时候,这两种场景都会跳过搜索这步操作无条件的创建一个新的task。和FLAG_ACTIVITY_NEW_TASK一起使用需要注意,尽量不要使用该组合除非你完成了自己的顶部应用启动器,他们的组合使用会禁用已经存在的task栈回到前台的功能。
FLAG_ACTIVITY_NEW_DOCUMENT
  api 21之后加入的一个标识,用来在intent启动的activity的task栈中打开一个document,和documentLaunchMode效果相等,有着不同的documents的activity的多个实例,将会出现在最近的task列表中。单独使用效果和documentLaunchMode=”intoExisting”一样,如果和FLAG_ACTIVITY_MULTIPLE_TASK一起使用效果就等同于documentLaunchMode=”always”。
FLAG_ACTIVITY_NEW_TASK
  设置此状态,记住以下原则,首先会查找是否存在和被启动的Activity具有相同的亲和性的任务栈(即taskAffinity,注意同一个应用程序中的activity的亲和性在没有修改的情况下是一样的,所以下面的a情况会在同一个栈中),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的activity顺序不变,如果没有,则新建一个栈来存放被启动的activity。
  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),如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的顺序不变。

FLAG_ACTIVITY_NO_ANIMATION
  禁止activity之间的切换动画
FLAG_ACTIVITY_NO_HISTORY
  该Activity将不在stack中保留,用户一离开它,这个Activity就关闭了。
FLAG_ACTIVITY_NO_USER_ACTION
  禁止activity调用onUserLeaveHint()函。onUserLeaveHint()作为activity周期的一部分,它在activity因为用户要跳转到别的activity而退到background时使用。比如,在用户按下Home键(用户的操作),它将被调用。比如有电话进来(不属于用户的操作),它就不会被调用。注意:通过调用finish()时该activity销毁时不会调用该函数。
FLAG_ACTIVITY_PREVIOUS_IS_TOP
  如果给Intent对象设置了这个标记,这个Intent对象被用于从一个存在的Activity中启动一个新的Activity,那么新的这个Activity不能用于接受发送给顶层activity的intent,这个新的activity的前一个activity被作为顶部activity。
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标志也设置的话,那么这个标志将被覆盖。
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  这个标记在以下情况下会生效:1.启动Activity时创建新的task来放置Activity实例;2.已存在的task被放置于前台。系统会根据affinity对指定的task进行重置操作,task会压入某些Activity实例或移除某些Activity实例。我们结合上面的FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET可以加深理解。
FLAG_ACTIVITY_RETAIN_IN_RECENTS
  api21加入。
  默认情况下通过FLAG_ACTIVITY_NEW_DOCUMENT启动的activity在关闭之后,task中的记录会相对应的删除。如果为了能够重新启动这个activity你想保留它,就可以使用者个flag,最近的记录将会保留在接口中以便用户去重新启动。接受该flag的activity可以使用autoRemoveFromRecents去复写这个request或者调用Activity.finishAndRemoveTask()方法。
FLAG_ACTIVITY_SINGLE_TOP
  singleTop一样
FLAG_ACTIVITY_TASK_ON_HOME
  api11加入。
  把当前新启动的任务置于Home任务之上,也就是按back键从这个任务返回的时候会回到home,即使这个不是他们最后看见的activity,注意这个标记必须和FLAG_ACTIVITY_NEW_TASK一起使用。
FLAG_DEBUG_LOG_RESOLUTION
  将log置为可用状态,如果设置了这个flag,那么在处理这个intent的时候,将会打印相关创建日志。
FLAG_EXCLUDE_STOPPED_PACKAGESFLAG_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的效果。
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)方法去实际的变为存在状态。
FLAG_GRANT_PREFIX_URI_PERMISSION
  api21加入。
  当和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用时,uri的许可只用匹配前缀即可(默认为全部匹配)。
FLAG_GRANT_READ_URI_PERMISSIONFLAG_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。
FLAG_RECEIVER_FOREGROUND
  api16添加。
  当发送广播时,允许其接受者拥有前台的优先级,更短的超时间隔。
FLAG_RECEIVER_NO_ABORT
  api19添加
  如果这是一个有序广播,不允许接受者终止这个广播,它仍然能够传递给下面的接受者。
FLAG_RECEIVER_REGISTERED_ONLY
  如果设置了这个flag,当发送广播的时,动态注册的接受者才会被调用,在Androidmanifest.xml 里定义的Receiver 是接收不到这样的Intent 的。
FLAG_RECEIVER_REPLACE_PENDING
  api8添加。
  如果设置了的话,ActivityManagerService就会在当前的系统中查看有没有相同的intent还未被处理,如果有的话,就由当前这个新的intent来替换旧的intent,所以就会出现在发送一系列的这样的Intent 之后,中间有些Intent 有可能在你还没有来得及处理的时候, 就被替代掉了的情况

taskAffinity

  每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该 Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果 Application也没有指明,那么该taskAffinity的值就等于包名。而Task也有自己的affinity属性,它的值等于它的根 Activity的taskAffinity的值。
  TaskAffinity属性主要和SingleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。当TaskAffinity和singleTask启动模式配对使用的时候,他是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。allowTaskReparenting用于配置是否允许该activity可以更换从属task,通常情况二者连在一起使用,用于实现把一个应用程序的Activity移到另一个应用程序的Task中。allowTaskReparenting用来标记Activity能否从启动的Task移动到taskAffinity指定的Task,默认是继承至application中的allowTaskReparenting=false,如果为true,则表示可以更换;false表示不可以。
  如果加载某个Activity的intent,Flag被设置成FLAG_ACTIVITY_NEW_TASK时,它会首先检查是否存在与自己taskAffinity相同的Task,如果存在,那么它会直接宿主到该Task中,如果不存在则重新创建Task。

引用

http://droidyue.com/blog/2015/08/16/dive-into-android-activity-launchmode/
http://blog.csdn.net/lygglobetech/article/details/6600692