Android 深刻解析 Activity 的 launchMode 启动模式,Intent Flag,taskAffinity

  最近看到一篇文章讲launchMode,想到之前的一次面试,就问了这一个问题,最基本的你们都知道,可是详细的我就迷糊了,最终失败了,因此在此总结一下,但愿可以帮助一下你们html

LaunchMode

  launchMode分为四种:

  android

standard

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


  • 本应用打开,新建立的activity实例放入本应用,即发送intent的task栈的顶部,这个比较简单

  • 跨引用打开,这里有一个须要注意的地方是跨应用打开的时候会在 Recent app 页面显示两个独立项,可是此时它们两个 Activity 仍然是在一个栈中,很是感谢@梦想编制楠灬 的指正,跨应用打开在 Standard 模式下也是在一个栈中,虽然在 Recent app 页面是两个页面:

    这里写图片描述

    可是问题来了,又一次我偶然发现使用浏览器打开手机上的bilibili则是在同一个 Recent App 项中:

    这里写图片描述

    使用命令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在同一个activity栈中,VideoDetailsActivity被打开在了BrowserActivity的task栈中,这是怎么回事了,最后我发现有一个intent的flag变量有该做用:FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,只要将该flag设置进intent中就会将跨应用的activity打开在同一个 Recent app 中,感兴趣的能够试一下。shell

singleTop

  

  singleTop其实和standard几乎同样,和standard算一组,使用singleTop的Activity也能够建立不少个实例。惟一不一样的就是,若是调用的目标Activity已经位于调用者的Task的栈顶,则不建立新实例,而是使用当前的这个Activity实例,并调用这个实例的onNewIntent方法。

  这个使用场景比较少,可使用的例子好比用户已经在当前activity,用户点击一条推送消息以后也须要跳转到当前activity,那么为了不activity的重复打开,则须要将该activity设置为singleTop而且复写onNewIntent便可。

  若是是外部程序启动singleTop的Activity,在Android 5.0以前新建立的Activity会位于调用者的Task中,5.0及之后会放入新的Task中,这点和standard同样。后端

singleTask

  使用singleTask启动模式的Activity 在系统中 只会存在一个实例。若是这个实例已经存在,intent就会经过onNewIntent传递到这个Activity,而且将栈中该activity之上的activity清除(销毁过程会调用Activity生命周期回调),若是不存在新的Activity实例将被建立。

  这里写图片描述api

 实际使用状况也分为两组:浏览器


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


  • 跨应用启动,因为整个系统只能存在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中,并位于栈顶位置。ide


注意若是使用了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中,可是在任务管理器中,仍是显示的一个

  这里写图片描述

  须要特别注意的是:

  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。

引用


droidyue.com/blog/2015/0…

blog.csdn.net/lygglobetec…

相关文章
相关标签/搜索