您的位置:首页 > 其它

Activity的"singleTask"之谜

2015-11-08 17:15 288 查看
官方文档称 以这种方式启动的Activity总是属于一个任务的根Activity。果真如此吗?本文将为你解开Activity的"singleTask"之谜。

任务(Task)是个什么样的概念

每一个Activity代表一个用户操作, 用户为了完成某个功能而执行的一系列操作就形成了一个Activity序列,这个序列在Android应用程序中就称之为任务,它是从用户体验的角度出发,把一组相关的Activity组织在一起而抽象出来的概念

具体配置请参考官方文档:

http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html

它是这样介绍以"singleTask"方式启动的Activity的:

The system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.

它明确说明,以"singleTask"方式启动的Activity,全局只有唯一个实例存在,因此,当我们第一次启动这个Activity时,系统便会创建一个新的任务,并且初始化一个这样的Activity的实例,放在新任务的底部,如果下次再启动这个Activity时,系统发现已经存在这样的Activity实例,就会调用这个Activity实例的onNewIntent成员函数,从而把它激活起来。从这句话就可以推断出,以"singleTask"方式启动的Activity总是属于一个任务的根Activity。

但是文档接着举例子说明,当用户按下键盘上的Back键时,如果此时在前台中运行的任务堆栈顶端是一个"singleTask"的Activity,系统会回到当前任务的下一个Activity中去,而不是回到前一个Activity中去,如下图所示:



真是坑爹啊!有木有!前面刚说"singleTask"会在新的任务中运行,并且位于任务堆栈的底部,这里在Task B中,一个赤裸裸的带着"singleTask"标签的箭头无情地指向Task B堆栈顶端的Activity Y,刚转身就翻脸不认人了呢!

狮屎胜于熊便,我们来做一个实验吧,看看到底在启动这个"singleTask"的Activity的时候,它是位于新任务堆栈的底部呢,还是在已有任务的顶部。

具体分析过程请参考老罗的<< 解开Android应用程序组件Activity的"singleTask"之谜 >>

到这里,思路就理清了,虽然SubActivity的launchMode被设置为"singleTask"模式,但是它并不像官方文档描述的一样:The system creates a new task and instantiates the activity at the root of the new task,而是在跟它有相同taskAffinity的任务中启动,并且位于这个任务的堆栈顶端,于是,前面那个图中,就会出现一个带着"singleTask"标签的箭头指向一个任务堆栈顶端的Activity Y了。
那么,我们有没有办法让一个"singleTask"的Activity在新的任务中启动呢?答案是肯定的。从上面的代码分析中,只要我们能够进入函数startActivityUncheckedLocked的这个if语句中:

[java] view plaincopy

if (r.resultTo == null && !addingToTask

&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {

// todo: should do better management of integers.

mService.mCurTask++;

if (mService.mCurTask <= 0) {

mService.mCurTask = 1;

}

r.task = new TaskRecord(mService.mCurTask, r.info, intent,

(r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);

if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r

+ " in new task " + r.task);

newTask = true;

if (mMainStack) {

mService.addRecentTaskLocked(r.task);

}

}

那么,这个即将要启动的Activity就会在新的任务中启动了。进入这个if语句需要满足三个条件,r.resultTo为null,launchFlags的Intent.FLAG_ACTIVITY_NEW_TASK位为1,并且addingToTask值为false。从上面的分析中可以看到,当即将要启动的Activity的launchMode为"singleTask",并且调用startActivity时不要求返回要启动的Activity的执行结果时,前面两个条件可以满足,要满足第三个条件,只要当前系统不存在affinity属性值等于即将要启动的Activity的taskAffinity属性值的任务就可以了。

我们可以稍微修改一下上面的AndroidManifest.xml配置文件来做一下这个实验:

[java] view plaincopy

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="shy.luo.task"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".MainActivity"

android:label="@string/app_name"

android:taskAffinity="shy.luo.task.main.activity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name=".SubActivity"

android:label="@string/sub_activity"

android:launchMode="singleTask"

android:taskAffinity="shy.luo.task.sub.activity">

<intent-filter>

<action android:name="shy.luo.task.subactivity"/>

<category android:name="android.intent.category.DEFAULT"/>

</intent-filter>

</activity>

</application>

</manifest>

注意,这里我们设置MainActivity的taskAffinity属性值为"shy.luo.task.main.activity",设置SubActivity的taskAffinity属性值为"shy.luo.task.sub.activity"。重新编译一下程序,在模拟器上把这个应用程序再次跑起来,用“adb shell dumpsys activity”命令再来查看一下系统运行的的任务,就会看到:

[html] view plaincopy

Running activities (most recent first):

TaskRecord{4069c020 #4 A shy.luo.task.sub.activity}

Run #2: HistoryRecord{40725040 shy.luo.task/.SubActivity}

TaskRecord{40695220 #3 A shy.luo.task.main.activity}

Run #1: HistoryRecord{406b26b8 shy.luo.task/.MainActivity}

TaskRecord{40599c90 #2 A com.android.launcher}

Run #0: HistoryRecord{40646628 com.android.launcher/com.android.launcher2.Launcher}

这里就可以看到,SubActivity和MainActivity就分别运行在不同的任务中了。

至此,我们总结一下,设置了"singleTask"启动模式的Activity的特点

1. 设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。

2. 如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: