TaskAffinity & LaunchMode
在说TaskAffinity和LaunchMode之前,先说一下Task吧。
既然要说Task,我们先猜想一下,Android的设计者为什么会推出Task这个概念。
假想一下现在一个用户在使用一个app,而这个app需要调用浏览器。如果用户在浏览器界面点击back键后回到桌面,那么用户的体验当然不会好。因为人们一般都会希望,从哪里来,点击back键后就回到哪里去。也就是用户希望在浏览器点击back键后,还是回到调用浏览器的app中。
面对这样的需求,推出Task的概念也就是很自然的事了。Task就相当于Activity的集合,用户如果在做某件事时需要调用几个不同app的Activity,那这几个Activity都应该是在一个task中,这个task在Android中是用back stack来管理的,遵循“first in last out”的规则。
既然我们知道了需要将应用的Activity用task来管理,那将Activity用task管理的依据是什么呢?这就要说到<activity>
的android:taskAffinity
属性了。android:taskAffinity
就是将Activity归入task的依据,默认情况下,这个属性的值和<application>
的android:taskAffinity
相同,而<application>
的android:taskAffinity
默认就是包名。
android:taskAffinity
通常和LaunchMode配合使用。Android中,activity有四种LaunchMode:standard, singleTop, singleTask, singleInstance。
standard和singleTop没什么可说的,接下来就说说singleTask和singleInstance吧~
google的官方文档对singleTask有个这样的说明:
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.
也就是说,如果要开启的activity没有被实例化,系统会为新开一个Task来装这个要开启的activity。但我们在实际开发中往往发现事实不是这样。下面我们就要用一个demo验证一下。
demo的流程比较简单,就是用MainActivity开启SecondAct,而SecondAct的launchMode是singleTask,然后再两个activity中打印出taskId。
<activity
android:name="com.lzf.tasktest.SecondAct"
android:launchMode="singleTask"/>
Log.i(TAG , "MainActivity taskId = " + getTaskId());
可以看到,MainActivity和SecondAct在同一个Task。事实证明:launchMode为singleTask的Activity,启动后不一定就在一个新的Task中。这是为什么呢?原来,如果一个Activity的launchMode是singleTask,FrameWork只是会将这个Activity标记为“可以新开一个Task”,至于是否新开一个Task,还得看android:taskAffinity
。下面我们验证一下,别的什么都不改,只是将SecondAct的android:taskAffinity
设为com.lzf.tasktest1
,看看运行结果吧~
可以看到,这次SecondAct是在一个新的Task中。有了现象看,我们就该分析一下为什么两次的结果会不同了。两次编码上的区别只有SecondAct的android:taskAffinity
。第一次没有设置,那么这个属性的值就默认为应用的包名(com.lzf.tasktest
),因此MainActivity和SecondAct的android:taskAffinity
就一样,运行在同一个Task中。第二次将SecondAct设置为com.lzf.tasktest1
了,他们就运行在不同Task中了。所以说singleTask在FrameWork眼中只是一个标志位,它告诉FrameWork可以给这个Activity开一个Task。至于开不开,FrameWork还需要看看android:taskAffinity
,才能决定是否给这个Activity新开一个Task。(当然,之前还要判断一下Task和Activity实例是否存在)
接下来再说说singleInstance。singleInstance相比于singleTask,做得更绝一点。只要Activity的launchMode是singleInstance,FrameWork就会为Activity新开一个Task(在实例化之前同样也有个判断是否存在的过程)。所以如果将上面的demo改一下,将SecondAct设为singleInstance,那么不管怎样,系统都会为SecondAct新开一个Task(判断是否有实例的情况忽略不讲)。那么问题来了,如果我在SecondAct再启动一个ThirdAct呢?如果ThirdAct的launchMode为standard,taskAffinity不设置,ThirdAct会在哪个Task中呢?
可以看到,ThirdAct会和MainActivity在同一个Task,而不是和SecondAct在同一个task。这就有点奇怪了,因为如果A启动B,B默认是和A在同一个Task,但我们看到的现象是“A启动B,B启动C,但C和A在同一个Task”。所以我们大胆猜想,虽然我们没有给C设置launchMode,但由于B是singleTask,因此C的launchMode默认为singleTask。我们来验证一下。我们将ThirdAct的android:taskAffinity
设置为com.lzf.tasktest2
,然后看看运行效果。
ThirdAct在另外一个Task中了!~不过孤证不成立,我们还是进源码找找证据吧,
public class ActivityStack {
final int startActivityUncheckedLocked(ActivityRecord r,
ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
int grantedMode, boolean onlyIfNeeded, boolean doResume) {
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
int launchFlags = intent.getFlags();
// We'll invoke onUserLeaving before onPause only if the launching
// activity did not explicitly state that this is an automated launch.
mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
// 省略
ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
!= 0 ? r : null;
// If the onlyIfNeeded flag is set, then we can do this if the activity
// being launched is the same as the one making the call... or, as
// a special case, if we do not know the caller then we count the
// current top activity as the caller.
if (onlyIfNeeded) {
// 省略
}
if (sourceRecord == null) {
// 省略
} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// 省略
} else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
// 省略
}
if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// 省略
}
boolean addingToTask = false;
if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
(launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// If bring to front is requested, and no result is requested, and
// we can find a task that was started with this same
// component, then instead of launching bring that one to the front.
if (r.resultTo == null) {
// See if there is a task to bring to the front. If this is
// a SINGLE_INSTANCE activity, there can be one and only one
// instance of it in the history, and it is always in its own
// unique task, so we do a special search.
ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
? findTaskLocked(intent, r.info)
: findActivityLocked(intent, r.info);
if (taskTop != null) {
// 省略
}
}
}
// 省略
if (r.packageName != null) {
// If the activity being launched is the same as the one currently
// at the top, then we need to check if it should only be launched
// once.
ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity)) {
// 省略
}
}
} else {
// 省略
}
boolean newTask = false;
// Should this be considered a new task?
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);
// 省略
newTask = true;
if (mMainStack) {
mService.addRecentTaskLocked(r.task);
}
} else if (sourceRecord != null) {
// 省略
} else {
// 省略
}
startActivityLocked(r, newTask, doResume);
return START_SUCCESS;
}
}
代码比较多,不过可以看到,如果由A启动B,A的launchMode为singleInstance或singleTask,那对B的处理就和B的launchMode为singleTask一样。
if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
(launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE)
总结一下,Task存在的意义主要是为了用户体验,让用户在跨应用做某件事时更有连贯性。将Activity分入Task的依据是android:taskAffinity
,但将Activity分入哪个Task的依据不仅仅是android:taskAffinity
,还与Activity的launchMode紧密相关。