Activity启动模式的那点事

要说Activity是Android四大组件最重要的一点也不为过,因为用户最能直观感受的也就是Activity。在讲启动模式之前有两个概念要先理一理,Stack和Task。

Task

Activity是显示以及用户直接进行交互的组件,我们可以简单理解为许多个Activity组合在一起就是一个app。而这么多的Activity就在一个或多个Task中。

Stack

Activity存在于Task中,而Task有先后之分,比如可以从相机中打开相册,也可以从相册中打开相机。那么这样也就导致了Activity的显示有先后顺序,在Android系统中用来管理Activity的显示顺序就是Activity Stack,即Activity栈。一般情况下,Activity都遵循栈的规则FILO,最早展现的Activity位于栈的最底部,而位于栈顶的Activity就是当前显示的Activity。


不同的启动模式会影响Activity的显示顺序,也就是今天要讲的。改变Activity的启动模式可以在AndroidManifest中设置,也可以在代码中通过设置Intent的Flag来实现。


AndroidManifest中设置:

#android:launchMode

launchMode是被提到最多的用来改变Activity栈内状态的方式,一共有四种状态(standard、singleTop、singleTask、singleInstance

·standard:标准模式。每次启动Activity都会重新创建一个Activity实例并压入上一个启动Activity的Task栈顶(除非Intent中设置了FLAG_ACTIVITY_NEW_TASK)。

·singleTop:栈顶唯一模式。这个模式与standard的唯一区别就是当要启动的Activity位于栈顶时,不会再重新创建一个新的Activity实例,并且将新的Intent传入到已有实例的onNewIntent方法中。

·singleTask:栈内唯一模式。顾名思义,栈内唯一的模式就是只要当前Task栈内存在目标Activity的实例,启动时都不会重新创建新的实例,而是将Intent传入到已有实例的onNewIntent方法中,并将目标实例之上的Activity全都移出栈。假设我们依次启动A、B、C、D,然后在D中启动B,其中B是singleTask模式的,那么此时Stack中的情况就是A、B,因为在A、B、C、D的时候启动B的时候,B已经存在,所以就将B之上的Activity全部移出栈。

·singleInstance:单实例模式。这种模式永远只会创建一个目标Activity实例,并且该实例会单独占有一个Task栈,并且不允许其它Activity压入该Task栈。做个实验,依次启动A、B、C、D四个Activity,然后在D中再启动B,其中B是singleInstance模式,其它都是标准模式,然后打印一下栈内情况。


依次启动A、B、C、D四个Activity,我们可以看到A、C、D是在一个986e4cd的Task栈内,B独占一个8cd3460的Task,在Stack中的情况依旧是A、B、C、D,符合FILO的规则;


D中再启动B,这下更清晰了,Stack中的情况是A、C、D、B,而且两个Task的情况也没有变,依旧是A、C、D在一个Task中,B单独一个Task。D在启动 B的时候,B实例已经存在了,所以并没有重新创建Activity,而是将B移动到Stack的栈顶。

此时如果按照返回键来逐级返回,Activity的出栈顺序就是B、D、C、A。


#android:taskAffinity

taskAffinity可以理解为Activity的Task归属,每个Activity都有taskAffinity属性,默认情况下与上一个启动它的Activity相同,且第一个Activity的taskAffinity值就是包名,具有相同taskAffinity的Activity会在同一个Task中。但是单纯在AndroidManifest中设置这个属性并不会生效,必须与其他属性搭配使用。

搭配一:launchMode设置成singleTask,或者Intent的Flag设置成FLAG_ACTIVITY_NEW_TASK。这两个的效果是一样的,这样启动的时候,会先判断目标Activity的taskAffinity的Task是否存在,如果存在,就直接压入;如果不存在,先创建Task,再压入。判断Task是否存在的时候不仅仅是在当前的应用内寻找,不同应用间如果taskAffinity相同,只要Task存在,就能被压入。假设这么一种情况,app_a先打开A_Activity,app_b再打开B_Activity,如果B_Activity的taskAffinity与A_Activity一致,那么打开app_a,就会发现B_Activity在app_a中显示。

搭配二:android:allowTaskReparenting属性设置true,这个放到后面具体讲。


#android:allowTaskReparenting

是否允许Task的转移,承接上面的搭配二来仔细说说这个属性,按照字面意思就是是否允许Activity从所属的Task转移去另一个Task。

做个实验。先启动app_a,包名为com.aaa.abc,第一个Activity为A_Activity;然后切换到后台启动app_b,包名为com.bbb.abc,第一个Activity为MainActivity,然后启动NewActivity,其taskAffinity为"com.aaa.abc",并且将allowTaskReparenting设置为true。这时候打印Task的日志我们可以发现,com.aaa.abc的Task内只有A_Activity;com.bbb.abc的Task中的情况是MainActivity、NewActivity。

这时候将app_b切换到后台,将app_a切换到前台 ,显示在前台的是app_b的NewActivity。打印日志发现com.aaa.abc的Task中的情况是A_Activity、NewActivity,com.bbb.abc的Task中只有一个MainActivity。也就是说设置了NewActivity被转移到了目标taskAffinity的栈内,也就是com.aaa.abc的Task中。


#android:clearTaskOnLaunch

启动时清空Task。一般情况下,如果应用已经启动了,那么再次点击Launcher图标,会进入到最近一次显示的Activity。但是clearTaskOnLaunch设置为true的情况下,每次点击Launcher图标,都会将Task内的Activity全部清空,然后再启动第一个Activity,所以我们也能猜到这个属性只能对根Activity设置才有用。


#android:alwaysRetainTaskState

长期保留Task状态。一般而言,app在后台停留太久,再次打开的时候,系统会清理Task内的Activity,然后再启动根Activity,就像重新启动一样。alwaysRetainTaskState设置为true的情况下,系统会长期保留Task的状态,也就是再次启动的时候依旧显示最后一个Activity。


#android:finishOnTaskLaunch

Task切换到前台的时候是否销毁。android:finishOnTaskLaunch设置为true的情况下,当Task重新切换到前台时,设置该属性的Activity会销毁并移出栈。当finishOnTaskLaunch和allowTaskReparenting都设置成true的情况下优先使用finishOnTaskLaunch。虽然理论上可以这么理解,但是我在自己写demo测试的时候发现finishOnTaskLaunch与taskAffinity和singleTask一起使用的时候会变得很诡异。

比如Task内情况为A、B、C、D,B设置finishOnTaskLaunch为true,D同时设置单独的taskAffinity和singleTask。然后将Task重新切换到前台,此时Task的情况为A、D、C。

再比如Task内情况为A、B、C、D,B设置finishOnTaskLaunch为true,C同时设置单独的taskAffinity和singleTask。然后将Task重新切换到前台,此时Task的情况为C、D、A。

对此我不太能理解,先占个坑以后理解力的时候再回来补上。如果此时看到这篇文章的你知道是什么原因的话希望能赐教。




Intent中设置:

在Intent中设置flags也能影响Activity在的状态。不过在讲各种flags之前先看两个方法,addFlags方法会覆盖原来的flags,所以要设置多个flag的时候就要使用addFlags方法。

    public Intent setFlags(int flags) {
        mFlags = flags;
        return this;
    }
    public Intent addFlags(int flags) {
        mFlags |= flags;
        return this;
    }


#FLAG_ACTIVITY_BROUGHT_TO_FRONT

这个flag一般不是由应用的代码来设置的,当Activity的launchMode设置成singleTask时系统会默认设置成这个flag。


#FLAG_ACTIVITY_CLEAR_TASK

设置了这个flag的Activity在启动的时候会将Task里的Activity全部清空,然后变成根Activity。单独设置没有效果,要设置singleTask或者FLAG_ACTIVITY_NEW_TASK才有效。

#FLAG_ACTIVITY_CLEAR_TOP

设置了这个flag的Activity在启动的时候会判断Task内是否已经有目标Activity的实例存在,如果存在,就将目标Activity之上的Activity全部移除。我看很多博客和一些资料书上都说目标Activity如果存在就不会重新创建,Intent通过目标实例onNewIntent方法传递。这样理解其实是不对的,这里要分两种情况:
1、目标Activity的启动模式是标准模式,并且在同一个Intent中没有加上FLAG_ACTIVITY_SINGLE_TOP,这样目标Activity就会重新创建,走onCreate方法。
2、目标Activity的启动模式是标准之外的另三种启动模式之一,或者同一个Intent加上了FLAG_ACTIVITY_SINGLE_TOP,这样目标Activity不会重新创建,onNewIntent方法会被调用。

#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

在API21之后都被替换成FLAG_ACTIVITY_NEW_DOCUMENT。

#FLAG_ACTIVITY_FORWARD_RESULT

一般情况下我们在销毁一个Activity时想要带点数据到上一个界面会采用startActivityForResult的方式来启动这个Activity,而这个flag能够达到跨级传递的效果。A->B->C,B启动C时设置FLAG_ACTIVITY_FORWARD_RESULT,A的onActivityResult能收到C的Intent数据。当然A启动B的时候必须要用startActivityForResult来启动,同时因为B不能在接收result了,所以B要用startActivity启动B,或者在启动B时的requestCode小于0。


#FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

这个flag一般不是由应用的代码来设置的,当Activity是从历史记录中被拉起的,那么系统会默认设置该flag。(一般长按home键等能够调出历史记录)

#FLAG_ACTIVITY_MULTIPLE_TASK

这个flag必须与FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK搭配使用,Activity在启动的时候总是会重新创建一个Task。


#FLAG_ACTIVITY_NEW_DOCUMENT

占坑


#FLAG_ACTIVITY_NEW_TASK

设置这个flag,目标Activity会成为一个Task的根Activity,但是单独设置没有效果,具体的搭配在文中很多地方都有提到。


#FLAG_ACTIVITY_NO_ANIMATION

设置了这个flag的Activity在启动的时候不会出现转场动画。


#FLAG_ACTIVITY_NO_HISTORY

设置了这个flag的Activity不会保留在Stack中。A->B->C,其中B设置了FLAG_ACTIVITY_NO_HISTORY,那么此时的栈内只有A、C。因为B不会在栈内存活,所以B的onActivityForResult也永远不会被调用到。这个效果等同于在AndroidManifest中设置android:noHistory="true"。


#FLAG_ACTIVITY_NO_USER_ACTION

当Activity进行跳转时会调用到onUserLeaveHint方法,但是我们如果想要在一些非主观意识跳转的时候(比如闹钟、电话等)不回调到这个方法,那么就可以设置这个flag。

#FLAG_ACTIVITY_PREVIOUS_IS_TOP

占坑


#FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

占坑


#FLAG_ACTIVITY_REORDER_TO_FRONT

设置了这个flag的Activity在启动时候会判断栈内是否已经有该Activity的实例存在,如果存在,就把该Activity移到栈顶,并且不改变其它Activity的先后顺序。A->B->C->D,其中B设置了这个flag,D再启动B,这时候栈内的情况是A、C、D、B。


#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

    /**
     * If set, the new activity is not kept in the list of recently launched
     * activities.
     */
    public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 0x00800000;
从源码注释中的意思是如果设置了这个flag的话,目标Activity不会保留在最近启动的Activity栈内,但是我自己调试的时候发现设置了 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS的Activity依然存在于栈内,所以我也不是很明白这个flag到底什么作用。


#FLAG_ACTIVITY_SINGLE_TOP

等同于android:launchMode="singleTop"

#FLAG_ACTIVITY_TASK_ON_HOME

设置这个flag的Activity在启动的时候会移动到当前Home Activity的Task的顶部,然后再按返回键回退就能看到Home界面了。但是这个flag必须搭配FLAG_ACTIVITY_NEW_TASK来使用,并且taskAffinity要单独设置不一样的值。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值