最近在跟进一个问题,如下描述:
在launcher启动一个程序A(默认launchMode为standard)后,不关闭,然后退到后台,使用另一个程序B,通过getLaunchIntentForPackage(A包名)的方式获取到启动A的Intent,但启动后程序A重新创建了一个一个main activity的实例,但如果通过launcher启动则无问题;反之,如果一开始从程序B中启动程序A,则如果再通过launcher启动,则也会创建一个main activity的实例。
一开始首先考虑了以下因素造成的影响:
1. pid
在程序B启动A后,B退出,再重启B,再启动A,则没有重新创建,否定。
2. userId
考虑跟程序B(或launcher 的userid)不一样导致,使用B启动A,再重装B,再启动A,没有重新创建,否定。
3. packageName
考虑不同程序(包名)导致,使用B启动A,再修改B包名重装再启动A,没有重新创建,否定。
4. launch_flag
没有特别,一般都为0x1020000或0x1060000。
5. taskId
两次启动的A的 activity 都在同一个task中。
百思不得其解,没办法还得看源码,先看activity的启动流程:
1 | W/System.err(150): java.lang.Exception: resumeTopActivityLocked |
这是从ActivityStack::resumeTopActivityLocked方法中打出来的执行栈,总的执行顺序为:1
Activity::startActivityForResult -> Instrumentation::execStartActivity -> ActivityManagerNative::startActivity -> ActivityManagerService::startActivity -> ActivityStack::startActivityMayWait -> ActivityStack::startActivityLocked -> ActivityStack::resumeTopActivityLocked
其中ActivityManagerNative::startActivity 到 ActivityManagerService 经过的是Anrdoid的Binder驱动的。
这是从launcher中点击一个程序启动activity的主流程(可以参考http://blog.csdn.net/luoshengyang/article/details/6689748)。
先要了解为什么从同一启动源中启动程序则主activity就不会创建,于是也同样捕捉了A启动后再从launcher resume activity的过程:
1 | W/System.err(150): java.lang.Exception: resumeTopActivityLocked |
这是触发activity resume的流程,可以明显看出与启动的栈是不一样的,于是从不同的一个方法开始跟踪 ActivityStack::startActivityUncheckedLocked。
以下源码都来是4.1.2。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
final int userId = r.userId;
int launchFlags = intent.getFlags();
......
boolean addingToTask = false;//假如addingToTask为true表示需要在task顶创建一个新实例
boolean movedHome = false;
TaskRecord reuseTask = null;
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 (r.resultTo == null) {
ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
? findTaskLocked(intent, r.info)
: findActivityLocked(intent, r.info);
if (taskTop != null) {
......
if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
taskTop = resetTaskIfNeededLocked(taskTop, r);
}
......
if ((launchFlags &
(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
== (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
......
} else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
......
} else if (r.realActivity.equals(taskTop.task.realActivity)) {
// realActivity为将要启动的这个ActivityRecord对应的ComponentName
// 假如将要启动的activity与此时task的最顶activity一致则为true
if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
&& taskTop.realActivity.equals(r.realActivity)) {
logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task);
if (taskTop.frontOfTask) {
taskTop.task.setIntent(r.intent, r.info);
}
taskTop.deliverNewIntentLocked(callingUid, r.intent);
} else if (!r.intent.filterEquals(taskTop.task.intent)) {
//假如发起这次startActivity的Intent与task顶的intent不相等,则应该重新在task顶创建一个新的实例
//!!这里就是这次问题的原因!!
addingToTask = true;
sourceRecord = taskTop;
}
} else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
//如果launchFlags里不包含重置task,则也需要重新创建实例
addingToTask = true;
sourceRecord = taskTop;
} else if (!taskTop.task.rootWasReset) {
taskTop.task.setIntent(r.intent, r.info);
}
if (!addingToTask && reuseTask == null) {
if (doResume) {
resumeTopActivityLocked(null, options);// resume top activity
} else {
ActivityOptions.abort(options);
}
return ActivityManager.START_TASK_TO_FRONT;
}
}
}
}
当addingToTask为false,且reuseTask为空时,且doResume为true时,就可以resume起一个对应task中顶层的activity了。其中最重要的是addingToTask变量,该变量很大程序上决定了是否要在Task上创建一个activity的实例。其为true有两个可能:
1. 此次startActivity中使用的intent与此前启动activity使用的intent不相同;
2. 启动的flags中不包含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED位;
由于使用默认的getLaunchIntentForPackage方法获取到的intent是带有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标志位的,查看launcher的代码(packages/apps/Launcher2/src/com/android/launcher2/ShortcutInfo.java中的setActivity方法,flag在Launcher.java的completeAddApplication方法中传入,其中默认带有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标志位),可知,无论是从程序B启动A,还是从launcher启动,launchFlag中都带有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标志位,故第二个可能不存在。
考虑第一种可能,一开始我首先怀疑的就是这个intent中的某些值不一样,主要集中在launchFlag之中,但这里对比r.intent与taskTop.task.intent不相同,用的是intent中的filterEquals方法,此方法的实现为:
1 | public boolean filterEquals(Intent other) { |
由上可知,对比一个intent是否相等,需要其action,data,type,package,component,categories都要相等。由于没有耐性一个个尝试,于是把两个intent都打印出来,结果为:
1 | E/ActivityManager(150): r.intent = Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10600000 pkg=cn.itrunner.demo cmp=cn.itrunner.demo/.DemoActivity } |
由于flag不在比较的范围,故一眼可以看出,将要启动的Intent里,多了一个pkg,即mPackage变量对比失败了。
由此,基本可以证明,在launcher中启动A,再在B中启动,为什么会重新启动了一个activity实例,因为使用getLaunchIntentForPackage创建的intent中包含mPackage变量值,但launcher中创建的intent中不包含,所以在launcher中启动A,即使使用了别的launcher,只要intent的生成方式一样,就可以保证activity可以正常resume,但使用getLaunchIntentForPackage创建的 intent 启动就会新建一个activity实例。反之亦然。
按此逻辑,些getLaunchIntentForPackage生成intent的方式换成与launcher同样的方式:
1 | Intent intent = new Intent(Intent.ACTION_MAIN); |
就可以使得无论是从launcher还是从程序B中启动,都可以从不同的启动源中resume activity了。
假如会启动一个activity,则会执行ActivityStack::startActivityUncheckedLocked接下来的代码:
1 | startActivityLocked(r, newTask, doResume, keepCurTransition, options); |