Android中,每个Activity都需要从属于一个Task,这个Task是Android相对于Activity的一个概念,Task是一组相关联的Activity的集合,它存在于一个叫back stack的数据结构中,而framework层调度Activity都是通过管理这个back stack的数据结构来完成的,所以可以就理解为framework层是通过任务栈的形式来管理调度Activity的,所以就直接理解为Task就是一个任务栈就得了。栈的数据结构是先进后出。
任务栈是可以跨应用的,这样就能保证用户操作的连贯性,比如说你在自己的APP的ActivityA中需要调用到邮件软件的一个SendActivity来发送邮件(这里可以通过scheme方式来调用),这两个应用是属于不同App的,被系统放在同一个Task中,当用户用户发完邮件按返回键时,直接返回到ActivityA,这样确保了用户有一个流畅的用户体验。
同一个任务栈中的Activity可以来自不同应用,同一个应用中的Activity也可能存在于不同的任务栈中。
启动: 每次启动Activity都会在任务栈中新建一个Activity对象
回退: 点击返回依次在当前任务栈中依据先进后出规则弹栈
启动: 这个模式中,例如要启动一个Activity,如果当前所在的任务栈的栈顶中已经有了这个Activity,那么就会直接复用这个Activity(这时生命周期从onNewIntent开始而不是onCreate),如果当前所在任务栈的栈顶不是该Activity,则直接新建一个Activity实例,并且压入栈顶。
回退: 点击返回依次在当前任务栈中依据先进后出规则弹栈
启动: 如果在栈中已经有该Activity的实例,就重用该实例(这时生命周期从onNewIntent开始而不是onCreate)。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈(也就是被销毁掉)。如果栈中不存在该实例,framework在启动该Activity时会把它标示为可在一个新任务中启动的状态,至于是否在一个新任务中启动,还要受其他条件的限制(后说)。 正常如果什么其他操作都没做,则将该实例压入任务栈中。
回退: 如果是属于复用了已有的Activity的情况,那么启动时被移出栈的Activity实例就不再展示,因为已经被销毁了。所以整个逻辑也就是点击返回按钮时依次在当前任务栈中依据先进后出的规则弹栈。
启动: 如果一个Activity设置了singleInstance模式,那么当启动这个Activity时,会为该Activity实例新建一个任务栈用于存放该Activity,并且当多次启动Activity时,都是复用的同一个Activity实例,整个新建的任务栈中只会存在一个Activity,也就是设置了singleInstance的那个。
回退: 这种模式下,会产生大于1个的任务栈,则回退的情况是先将展示在前台的Activity所处的任务栈按先入后出的规则弹栈,直到弹完了再依次按启动先后顺序也是按先展示后销毁的规则去弹其他任务栈,直到所有的Activity全部弹出。
(以下红色背景Activity为当前页面展示的Activity)
回退时展示页面为singleInstance模式的Activity
回退时展示页面为非singleInstance模式的Activity
其实,不管回退时当前展示页面是何种模式的Activity,每次点击返回时都是先将当前展示页面所处的任务栈中的Activity弹栈,点击到当前任务栈中Activity全部弹完了,接着点击回去弹最后面展示出来的不是属于本任务栈的Activity所对应的任务栈(很拗口,看下图)。依次一直到任务退出。
AndroidManifest.xml
通过android:launchMode="模式"
来声明启动模式,如果不写默认是standard
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
常见的Intent Flag以下几种,不同类型来启动Activity的话表现不同:
为了更好的理解这几个FLAG,需要知道Activity的taskAffinity属性。
这个属性在官方文档中说是一个判断亲属关系或者是判断类别什么的东西,我自己的理解就是,每个task有一个task id是唯一的,而这个taskAffinity则是task的名称,并且它也是唯一的。这么说吧,如果两个Activity的taskAffinity相同,并且两个Activity的启动模式都是singleTask,那么这两个Activity必定属于同一个task中。
当不给Activity指定taskAffnity时,默认以根Activity的包名作为taskAffinity属性值。所有Activity除非指定了taskAffinity值,否则都是默认的。
上面说的不知道能不能理解的了,也只是给了一个结论而已。一步步来看看实验以下:
现在创建一个项目,里面有两个Activity,分别是MainActivity和SecondActivity。其manifest文件如下:
首先我们启动了MainActivity,然后在MainActivity中启动SecondActivity。
Intent intent = new Intent("com.lianwh.fragmentdemo.CUSTOM_ACTION");
startActivity(intent);
分别打印出两个Activity所在的Task id:getTaskId()
结果如下:
E/lianwenhong: >>>>>>main task id:4928
E/lianwenhong: >>>>>>second task id:4928
可见默认情况下,如果不指定taskAffinity也没有指定启动时flag的情况下,默认都是在当前当前页面所处的Task中。接下来我们改变一下,启动SecondActivity时使用FLAG_ACTIVITY_NEW_TASK模式:
Intent intent = new Intent("com.lianwh.fragmentdemo.CUSTOM_ACTION");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
这时候我们再来看看打印结果:
E/lianwenhong: >>>>>>main task id:4928
E/lianwenhong: >>>>>>second task id:4928
可见这时候指定了FLAG_ACTIVITY_NEW_TASK和没有指定是一样的,为什么呢?接下来我们再做一个操作,给SecondActivity指定一个taskAffinity:
这时MainActivity的taskAffinity为默认的,也就是这个类本身的包名,而SecondActivity的taskAffinity是我们指定的com.lianwh.fragmentdemo.second,我们这时候再从MainActivity --> SecondActivity试一下,结果是:
E/lianwenhong: >>>>>>main task id:4928
E/lianwenhong: >>>>>>second task id:4929
看到没有,变了。SecondActivity运行在了一个新的Task中,如果你用过adb shell dumpsys activity,那你就会知道,这个Activity其实是运行在了affinity为com.lianwh.fragmentdemo.second的这个Task中。
所以,我们可以得出结论:
FLAG_ACTIVITY_NEW_TASK和launchMode="singleTop"是一样的,例如要启动一个SecondActivity来说,系统的执行步骤大概是酱紫的:
1.检测当前系统中是否存在affinity为com.lianwh.fragmentdemo.second的Task(如果SecondActivity没有指定affinity就是默认的和第一个启动的Activity相同affinity)
2.如果不存在:新建一个affinity为com.lianwh.fragmentdemo.second的Task,并将SecondActivity对象加入该Task中
3.如果存在:那么会检测该Task中是否已经有SecondActivity对象,如果有,直接复用该对象,把它以上的其他Activity全部移除(这时生命周期走onNewIntent());如果没有,则将该SecondActivity对象压入栈顶(生命周期还是从onCreate()开始走)
接着再来一个小扩展,如果在刚在的基础上,我们新建一个ThirdActivity,它的launchMode=standard,不指定Affinity。这时如果再从SecondActivity中启动ThirdActivity,那么会发现,ThirdActivity和SecondActivity处于同一个Task中。
E/lianwenhong: >>>>>>main task id:4928
E/lianwenhong: >>>>>>second task id:4929
E/lianwenhong: >>>>>>third task id:4929
所以可知,当不指定affinity的情况下,除了singleInstance外,其他的情况下默认是当前展示页面处于哪个Task中,则该页面启动新的Activity都是处于该Task中。
接下来我们来讲讲singleInstance与affinity的爱恨情仇:
当一个Activity设置为singleInstance的时候,那么有两个特征:
那么当一个Activity是被指定为singleInstance时,这时候通过该Activity启动其他Activity会是什么情况呢,被压入哪个Task中呢?
当OneActivity的launchMode=“singleInstance”,那么OneActivity启动一个OtherActivity对象,OtherActivity会被自动变成singleTask模式,这时候OtherActivity的启动流程就和上面讲的singleTask启动模式一模一样:
.1. 只要系统中任何一个Task中存在了这个Activity对象,那么都直接复用这个Activity对象(生命周期走onNewIntent)
.2. 如果系统所有的Task中都不存在这个Activity,则会直接新建一个Task,并将该Activity对象压入新建的这个Task
.3. 只要指明了是singleInstance这种启动模式,那么该Activity必然是单独处于一个Task中,并且该Task中仅有它一个对象,其他的Activity不会压入该Task中。
官方文档中提到,可以把不同应用中的activity的taskAffinity设置成相同的值,这样的话这两个activity虽然不在同一应用中,却会在运行时分配到同一Task中。由此我们可以验证:
保留上一个项目,然后我们再建一个新的项目:fragmentdemo1
类信息如下:
可以看出,OtherActivity的启动模式被设置为singleTask,并且taskAffinity属性被设置为com.lianwh.fragmentdemo.second,这和第一个项目中的SecondActivity的affinity相同。现在将这两个应用安装在设备上。执行以下操作:
TaskRecord{412370c0 #4 A com.lianwh.fragmentdemo.second}
Intent { cmp=com.lianwh.fragmentdemo/.SecondActivity }
Hist #4: ActivityRecord{412f5ba0 com.lianwh.fragmentdemo1/.OtherActivity}
Intent { flg=0x400000 cmp=com.lianwh.fragmentdemo1/.OtherActivity }
ProcessRecord{412adb28 479:com.lianwh.fragmentdemo1/10044}
Hist #3: ActivityRecord{4125c880 com.lianwh.fragmentdemo/.SecondActivity}
Intent { cmp=com.lianwh.fragmentdemo/.SecondActivity }
ProcessRecord{41218e48 463:com.lianwh.fragmentdemo/10043}
TaskRecord{412f0f60 #5 A com.lianwh.fragmentdemo1}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.lianwh.fragmentdemo1/.MainActivity }
Hist #2: ActivityRecord{413045a8 com.lianwh.fragmentdemo1/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.lianwh.fragmentdemo1/.MainActivity }
ProcessRecord{412adb28 479:com.lianwh.fragmentdemo1/10044}
TaskRecord{412c5928 #3 A com.lianwh.fragmentdemo}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.lianwh.fragmentdemo/.MainActivity }
Hist #0: ActivityRecord{41250850 com.lianwh.fragmentdemo/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.lianwh.fragmentdemo/.MainActivity }
ProcessRecord{41218e48 463:com.lianwh.fragmentdemo/10043}
上面可以看出,一个TaskRecord是一个任务,其下的每一个Intent下的都是一个Activity,验证了我们的观点。
注意看的是:com.lianwh.fragmentdemo.second中,两个Activity属于两个不同的进程:
ProcessRecord{412adb28 479:com.lianwh.fragmentdemo1/10044}
ProcessRecord{41218e48 463:com.lianwh.fragmentdemo/10043}
我们也可以在一个应用中启动另一个应用中的Activity,这时候如果启动的Activity设置了singleTask并且与本应用启动它的这个Activity中的Activity的affinity相同,那么他俩属于同一个任务栈,这样就能做到操作更加顺滑,就好像在同一应用中。
由此可见,Task不仅可以跨应用,还可以跨进程。
一般应用场景:
singleTop, 用于一些比较频繁打开的场景,比如那些只影响当前页面的跳转而不影响其他页面的情况。比如一些app的推送,推过来之后跳的是同一个页面。如果我们点击进去之后,一会儿又有一条新的推送过来了,我们又点开,这时我们想要回退的时候不再一级一级回退了,就用这个。典型的就是网上常说的新闻客户端推送,推送点击之后跳到阅读页面,完了还推点击还是阅读页面。
singleTask,经常用在主页中,当应用点击到主页时,我们希望页面回退时应该是直接退出应用,而不是再回退到上一个界面然后再一级一级往下关。
singleInstance,这个模式往往用在一些具有工具属性的应用上,比如地图,闹钟,日历,邮件这些的,就是那些需要与程序分离开的页面。例如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。
https://blog.csdn.net/zhangjg_blog/article/details/10923643