我们都知道Activity有4种启动模式:Standard、SingleTop、SingleTask、SingleInstance
今天我们说任务栈,主要是想说的是SingleTask这种启动模式相关的内容
也许很多人会觉得SingleTask这种启动模式有什么好说的呢?不就是某个activity的启动模式为SingleTask的话,如果它已存在任务栈中,那么会将其移至栈顶,而且将它之上的所以activity都出栈处理。
是的,笔者一直以来也都这么理解的,甚至于面试被问到也是这么回答的,这么说是没错的,但事实上SingleTask单单只是这样子的吗?
如果你知道taskAffinity和allowTaskReparenting这两个属性的话,就会发现,其实里面还是大有文章的,今天我们就重点来说说这个。
先总结一下几个相关的基础知识:
1)如果activity不设置启动模式的话,默认Standard
2)如果activity的启动模式为Standard,那么会从当前任务栈中启动activity,并置于当前任务栈栈顶。注意:这里是“当前任务栈”,而不是默认初始任务栈
3)设置启动模式有两种方式:intent.addflag和manifest,如果同时设置的话,intent.addflag优先级高于manifest
4)每个activity都有taskAffinity属性,如果没有显式的指明taskAffinity,那么就等于Application指明的taskAffinity,而如果Application也没有指明,那么该taskAffinity的值就等于应用的包名
5)如果activity的启动模式为SingleTask,不设置taskAffinity的话,该activity仍然属于默认的务栈
6)如果activity的启动模式为SingleTask,我们可以通过设置taskAffinity来为其指定任务栈,该属性的值是一个字符串,可以是任意字符串,但至少要包含一个“.”,否则都无法安装运行,因为这属于manifest出错,日志会显示:Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED]
7)如果activity的启动模式为SingleTask,我们设置了taskAffinity值为“.myNewTask”,启动时流程是这样子的:
a)判断当前任务栈taskAffinity是否为“.myNewTask”,如果是的话,直接将该activity置于当前任务栈栈顶
b)如果当前任务栈taskAffinity不为“.myNewTask”,则检查是否存在一个任务栈taskAffinity值为“.myNewTask”,如个存在,则将该任务栈移到前台,并将该activity置于栈顶
c)如果并不存在一个任务栈taskAffinity值为“.myNewTask”,那么将会为该activity创建一个新的任务栈,该任务栈显示在前台
好啦,上面基本已经把taskAffinity及相关的知识说清楚了,下面我们还是用实际例子来进行验证吧:
我们定义了3个activity,manifest配置如下:
<activity android:name=".ui.activity.demo.task.TaskA1Activity"/>
<activity android:name=".ui.activity.demo.task.TaskA2Activity"
android:launchMode="singleTask"/>
<activity android:name=".ui.activity.demo.task.TaskB1Activity"
android:launchMode="singleTask"
android:taskAffinity=".myNewTask"/>
然后,为了能让结果更新明显,我们需要打印一下任务栈的相关信息,所以申请一下权限(笔者用模拟器测试时,没申请权限也照样能获取到任务栈的信息,但还是写一下的好):
<!--获取Activity任务栈 权限-->
<uses-permission android:name="android.permission.GET_TASKS" />
然后我们来看看TaskA1Activity的主要代码:
findViewById(R.id.startBT).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(TaskA1Activity.this,TaskB1Activity.class);
startActivity(intent);
}
});
// 获取activity任务栈
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningTaskInfo info = manager.getRunningTasks(1).get(0);
LogUtil.loge("当前栈id:"+info.id+",当前栈activity个数:"+info.numActivities);
界面非常简单,代码也非常简单,无非就是一个按钮启动另一个activity,然后获取一下当前任务栈的id及当前任务栈的activity个数,其他两个activity也是如此,这里就不再贴出代码了。
我们的启动顺序及对应的log如下:
1)从mainActivity启动了TaskA1Activity
当前栈id:41,当前栈activity个数:2
这个很正常,当前栈id为41,有两个activity:mainActivity和TaskA1Activity
2)TaskA1Activity点击按钮启动了TaskB1Activity
当前栈id:42,当前栈activity个数:1
因为TaskB1Activity启动模式为singleTask,且设置了taskAffinity为“.myNewTask”,因不存在这样的一个任务栈,所以系统会新建一个任务栈,id为42,这个栈只有一个activity就是TaskB1Activity。
这里插一下:任务栈的id是一个integer的数据类型,自增长的(从上面41到42也可说明这一点)
3)TaskB1Activity点击按钮启动了TaskA2Activity
当前栈id:41,当前栈activity个数:3
TaskA2Activity虽然设定了启动模式为singleTask,但没有设置taskAffinity,那么它属于Application默认的任务栈,即与TaskA1Activity同一任务栈,所以id为41,算上之前2个activity,共3个activity
4.1)如果一直按back键直至完全退出
现象:从TaskA2Activity直接回到TaskA1Activity,然后回到mainActivity,再回到TaskB1Activity
说明:第3步中启动了TaskA2Activity后,默认的任务栈至于前台,而将TaskB1Activity的任务栈移至后面
4.2)如果TaskA2Activity点击按钮启动了TaskB1Activity
现象:并没有打印到相关的日志
说明:不是创建新的TaskB1Activity,而将TaskB1Activity所在的任务栈移到前台来
4.3)查看最近任务
现象:发现最近任务中,有两个我们应用的图标,看起来有点像是启动了两个应用
说明:实际上是我们应用启动了两个任务栈而已,如果我们将TaskB1Activity设置:android:excludeFromRecents="true",那么该任务栈将被排除在最近任务外,那么我们将看不到两个应用的图标了。
4.4)按下home键,从桌面点击APP再次启动,一直按back键直至完全退出
现象:从TaskA2Activity直接回到TaskA1Activity,然后回到mainActivity,再返回就是桌面,发现TaskB1Activity不见了
说明:首先从home上启动的程序都带有FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET这样的一个标记位,而这个标记位的意思是:重置任务栈时清除该activity,所以,当你按下home键后从桌面点击APP再次启动,此时发生了reset task,故当前APP只有一个默认的任务栈,所以一直按back键直至完全退出看不到TaskB1Activity了。
当然,如果你再次启动APP,从mainActivity启动TaskA1Activity,从TaskA1Activity启动TaskB1Activity,你会发现这里TaskB1Activity是之前的那个任务栈移到前台而已,因为没有打印出相关任务栈信息,也就是说并不是重新创建的。
好啦,说了这么多,总算轮到allowTaskReparenting,这个说真的,比较复杂且比较难以理解,其实前面说了那么多,也是为了能够说清楚这个东东,虽然平时用到的不多,但我们还是了解一下吧。
首先说明一下,allowTaskReparenting是用来标记activity能否从一个Task迁移到另一个Task,是不是很神奇啊?当然,这个迁移是有条件,下面我们总结一下这个迁移的条件:
1)activity的taskAffinity属性为“.X”,且activity目前不属于“.X”任务栈中
2)activity的allowTaskReparenting属性为“true”
3)发生了Task Reset
只有满足上面3点条件,才会发生activity迁移到“.X”任务栈中。
好啦,老规矩,我们还是举例进行说明,对TaskA1Activity进行相应的配置,使其能够完成在Task中迁移,配置文件更改为:
<activity android:name=".ui.activity.demo.task.TaskA1Activity"
android:taskAffinity=".myNewTask"
android:allowTaskReparenting="true"/>
<activity android:name=".ui.activity.demo.task.TaskA2Activity"
android:launchMode="singleTask"/>
<activity android:name=".ui.activity.demo.task.TaskB1Activity"
android:launchMode="singleTask"
android:taskAffinity=".myNewTask"/>
其实与上文相比,我们给TaskA1Activity指定了taskAffinity为".myNewTask",与TaskB1Activity一样,同时设置了allowTaskReparenting为true,当然TaskA1Activity没设置启动模式,故默认为Standard,即启动后它属于默认的初始任务栈,这样已经满足了上面说的前两个条件了,而最后一个条件Task Reset,其实上面也已经说了,只需按下home键,然后从桌面启动APP即会发生Task Reset。
接下来,我们进行下面的操作:
1)从mainActivity启动TaskA1Activity,从TaskA1Activity启动TaskB1Activity
到这里还是跟之前的一样,TaskA1Activity与mainActivity同处于默认初始任务栈,而TaskB1Activity处于".myNewTask"所在的任务栈。
2)按下home键,从桌面启动APP
你会发现,咦!直接到了mainActivity啦,TaskA1Activity呢?
然后你从最近任务列表中找到另一个任务栈的图标,点击后展现的是TaskB1Activity,这没什么奇怪的,但当你按下back后,发现出现的居然是TaskA1Activity!
也就是说,此时TaskA1Activity和TaskB1Activity同处于".myNewTask"所在的任务栈。
至此,说明当我们上面定的3个条件同时满足时,发生了activity从一个Task迁移到另一个Task中。
好啦,大概也就这样子啦!
假如你问我啰里啰嗦说了这么多有什么用呢?能否给个实际的应用场景呢?
我会说:呵呵,存在总是合理的!就算暂时没用到,你不觉得了解一下这些技术细节还是蛮有意思的吗?
另外:上面说到的从桌面启动APP会发生Task Reset,其实上面也说了,如果你这样设置:intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET),那么也照样会发生Task Reset,既然如此,我们是不是可以做一些其他特殊的应用呢?
接下来就是你猜啦O(∩_∩)O,欢迎大家在评论区里共同探讨!
PS:对了,当你打开源码Intent类时,你会发现,里面单单Flag就有几十种之多,数都数不过来,幸好我们常用的也就那几种,找到上面说的Flag:FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,如下:
/**
* @deprecated As of API 21 this performs identically to
* {@link #FLAG_ACTIVITY_NEW_DOCUMENT} which should be used instead of this.
*/
@Deprecated
public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
正如注释所说的:FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET在API 21的时候已经被FLAG_ACTIVITY_NEW_DOCUMENT代替了,但是,当你找到它的时候,你会发现,哈哈:
public static final int FLAG_ACTIVITY_NEW_DOCUMENT = FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;
网友评论