AMS之startActivity

趁着周末最后一天的晚上,大概看了startActivity 的总体的流程,目前还非常的不完善,记录下。
看源码是想要回答以及更好的理解下面6个问题。

  1. AMS 是用来做什么的。
  2. Activity 启动的完整流程,如何去寻找需要跳转的Activity 的。
  3. 权限问题是怎么解决的。
  4. Activity 中的token 是什么。
  5. 具体的task 是怎么工作的。
  6. 知道了AMS 启动Activity 的流程以后可以做什么。

主要涉及到的类:

android.app.*

Activity
Instrumentation
ActivityThread
ApplicationThread
ActivityManagerNative
ActivityManagerProxy
ApplicationThreadNative
ApplicationThreadProxy

com.android.server.am.*

ActivityManagerService
ActivityRecord
ActivityStarter
ActivityStack
ActivityStackSupervisor

startActivity 流程分析

最开始这个地方分析的比较混乱,所以重新总结了一下(一些疑惑直接写在了时序图里面)。主要是将startActivity 的过程看成了3个过程:调用端pause 过程、被调用端launch 过程、调用端stop 过程。这个过程也是在应用开发中的流程,比如调用端执行onPause 以后才会执行被调用端的onCreate。

里面的分析流程是在同一个应用中打开的流程,所以没有涉及到Application 的创建的过程。

总体涉及到的进程间通信

总体涉及到了两次IPC 通信。
涉及到的两次IPC

调用端pause 过程

AMS之startActivity调用端pause
12 里面有一个Binder 进程间通信的知识点。

1
2
3
4
5
6
7
8
//ApplicationThreadProxy
public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) throws RemoteException {
//省略代码...
mRemote.transact(SCHEDULE_STOP_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
//省略代码...
}

Binder进程间通信是阻塞的,但是IBinder.FLAG_ONEWAY可以实现非阻塞,意思是调用scheduleStopActivity以后可以直接执行后面的代码而不用等待进程间通信返回的结果。

被调用端launch 过程

AMS之startActivity被调用端launch
17performLaunchActivity里,涉及到了Activity的创建完整过程。(注意这里因为是同一个ApplicationstartActivity所以没有涉及到Application#onCreate)

调用端stop 过程

AMS之startActivity调用端stop
这里Idler涉及到了MessageQueue.IdleHandler的一个机制,代表的是在MessageQueue中还有消息需要执行,但是当前时间点没有消息,会执行IdleHandler的消息(相当于是在空闲的时间点执行消息)。

查找Activity 具体流程

1
2
3
4
5
6
7
8
9
10
// ActivityStackSupervisor.java
ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
try {
return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
PackageManager.MATCH_DEFAULT_ONLY | flags
| ActivityManagerService.STOCK_PM_FLAGS, userId);
} catch (RemoteException e) {
}
return null;
}

最终AppGlobals.getPackageManager()执行ActivityThread.getPackageManager,得到的是PackageManagerService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//PackageManagerService.java
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
try {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForResolve(flags, userId, intent); // 1. 更新flags
enforceCrossUserPermission(Binder.getCallingUid(), userId, // 2. 权限处理
false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
flags, userId); //3. 查找满足条件的Activity
final ResolveInfo bestChoice =
chooseBestActivity(intent, resolvedType, flags, query, userId); //4. 选择最合适的Activity
return bestChoice;
} finally {
}
}

主要是看3和4处的代码,queryIntentActivitiesInternal看名称是通过Intent查找满足条件的所有Activity,而chooseBestActivity则是从所有满足条件的Activity中选择最合适的一个。

queryIntentActivitiesInternal

首先看queryIntentActivitiesInternal,如下。

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
//PackageManagerService.java
private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, int userId) {
// 省略代码
if (comp != null) { // 1. comp != null 代表的是只有一个结果
final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
final ActivityInfo ai = getActivityInfo(comp, flags, userId); // 1.1 获取到Activity 的信息
if (ai != null) {
final ResolveInfo ri = new ResolveInfo(); // 封装成resolveInfo 信息
ri.activityInfo = ai;
list.add(ri);
}
return list;
}
// reader
boolean sortResult = false; // 2. comp == null,可能找到多个可以匹配的Activity
boolean addEphemeral = false;
boolean matchEphemeralPackage = false;
List<ResolveInfo> result;
final String pkgName = intent.getPackage();
synchronized (mPackages) { // mPackages 代表的是手机中所有app 的包信息,是一个ArrayMap,key 是包名,value 是PackageParser.Package
if (pkgName == null) {
// 省略代码...
// Check for results in the current profile.
result = filterIfNotSystemUser(mActivities.queryIntent(
intent, resolvedType, flags, userId), userId); // 2.1 获取到了匹配的数据
// 省略代码...
} else {
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
result = filterIfNotSystemUser(
mActivities.queryIntentForPackage(
intent, resolvedType, flags, pkg.activities, userId), // 2.2 如果设置了package,那么就从那个Package 中去寻找
userId);
} else {
// 省略代码...
}
}
}
// 省略代码...
if (sortResult) {
Collections.sort(result, mResolvePrioritySorter); // 最终按照优先级排序
}
return result;
}

简化了很多代码,主要分成两种情况,如果已经设置了ComponentName(已经知道了包名和打开的Activity),可以直接找到。看第7行代码就行。

1
2
3
4
5
6
7
8
9
//PackageManagerService.java
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
// 省略代码...
synchronized (mPackages) {
PackageParser.Activity a = mActivities.mActivities.get(component); // ArrayMap 保存着一组<component, Activity>的键值对
// 省略代码...
}
return null;
}

直接看第5行代码,通过ComponentNameArrayMap找到相应的相应的Activity

总结:mActivities这个对象保存在PackageManagerService中,里面存储了系统中所有的Activity(键值对保存着),在设置了ComponentName的情况下寻找Activity直接使用map.get

回到上面的queryIntentActivitiesInternal,在comp == null的情况,首先看到27行,里面直接调用了IntentResolver.queryIntent。在里面会根据resolvedType(与设置的数据有关)、schemeactioncatrgories来处理。在看到34行,通过mPackages已经过滤的数据一次的数据来处理的。

总结:在ComponentName不为null 的情况下,使用ActivityIntentResolver里面已经保存的数据(例如:mActionToFilter 保存了相应Action 对应的所有的数据Activity 的信息)来查询满足的数据。

chooseBestActivity

选择最合适的Activity过程,通过上面的步骤查到了所有满足的Activity,但实际上一次只能打开一个Activity,所以需要去判断,这里已经把分析写在了注释。

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
//PackageManagerService.java
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, List<ResolveInfo> query, int userId) {
if (query != null) {
final int N = query.size();
if (N == 1) { // 1. 只有一个的情况,直接返回第一个
return query.get(0);
} else if (N > 1) {
final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
// If there is more than one activity with the same priority,
// then let the user decide between them.
ResolveInfo r0 = query.get(0);
ResolveInfo r1 = query.get(1);
// 省略代码
if (r0.priority != r1.priority
|| r0.preferredOrder != r1.preferredOrder
|| r0.isDefault != r1.isDefault) { // 2.第一个和第二个优先级不一样的情况,直接返回第一个
return query.get(0);
}
//省略代码
ResolveInfo ri = findPreferredActivity(intent, resolvedType, // 3. 如果已经选择并保存了应该使用哪个Activity 的情况,直接选择优先打开的那个
flags, query, r0.priority, true, false, debug, userId);
if (ri != null) {
return ri;
}
ri = new ResolveInfo(mResolveInfo); // 4. 如果都没有,则打开一个选择界面。mResolveInfo 代表的是一个选择的界面
ri.activityInfo = new ActivityInfo(ri.activityInfo);
ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
// 省略代码
return ri;
}
}
return null;
}

总结:通过Intent查询最终打开的Activity的过程是通过PackageManagerService来完成的。PackageManagerService中的mPackagesmActivities分别表示了系统中Package以及Activity的数据(想要非常清楚的明白这些值是怎么来的还需要去看PackageManagerService的工作原理)。还有一个应用开发的问题是在自定义intent-filter的时候最好是加上<category android:name="android.intent.category.DEFAULT"/>

权限问题

ActivityNotFoundException这个比较容易出现的错误是在Instrumentation#checkStartActivityResult。没有在AndroidManifest中声明Activity
然后就是权限错误了。ActivityStackSupervisor#checkStartAnyActivityPermission有具体的判断逻辑。主要记录下有两点需要注意的地方。

  • android:exported="true"可以让其他app 打开自己的Activity
  • 如果有必要可以自己声明权限。

在App1 中声明权限egos.app1.zzz,并设置MainActivity3
需要权限。

1
2
3
4
5
6
7
8
9
10
11
12
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="egos.app1">
<permission
android:name="egos.app1.zzz"/>
<application>
<activity
android:name=".MainActivity3"
android:exported="true"
android:permission="egos.app1.zzz"
android:theme="@style/AppTheme.NoActionBar"/>
</application>
</manifest>

在App2 中申请权限,那么在App2 中就可以打开App1 的MainActivity3

1
2
3
4
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="egos.app2">
<uses-permission android:name="egos.app1.zzz"/>
<manifest/>

实际的操作中通常设置android:protectionLevel="signature",以及会在两个App 中都声明permission(只在一个App 中声明,可能导致没有声明的App 先安装的话找不到这个权限,没办法注册)。

token 是什么

token 创建的地方

1
2
3
4
5
//ActivityStarter#startActivityLocked
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
options, sourceRecord);

TokenActivityRecord的内部类,在里面创建的。

1
2
//ActivityRecord 的构造函数中
appToken = new Token(this, service);
1
2
//Token 的定义
static class Token extends IApplicationToken.Stub

所以到这里,token 代表的是Activityecord$Token的对象,也是一个Binder对象。Token中主要保存的是所处的Activityecord信息以及对应的ActivityManagerService的信息。

token 使用的地方

token 到底是什么还是需要看使用的地方,再回到最开始调用Instrumentation#execStartActivity的地方。

1
2
3
4
5
6
//Instrumentation#execStartActivity
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);

token 被传递进去了。(仔细看上面startActivity的地方其实就已经觉得很奇怪了,调用的时候都不知道到底是哪个Activity发起的startActivity,其实这就是token 的作用)
继续跟进代码,ActivityManagerProxy#startActivity,然后ActivityManagerNative#onTransact,再到ActivityMAnagerService#startActivity。跟着上面的时序图跑,最终在ActivityStarter#startActivityLocked找到了Token 的使用。

1
2
//ActivityStarter#startActivityLocked
sourceRecord = mSupervisor.isInAnyStackLocked(resultTo);

即通过token 拿到了调用端的Activity的相关信息。
总结:正如token 字面意思一样,代表的是Activity的标识。里面记录了Activity的相关信息,在IPC 的时候可以通过Token 找到一个对应的Activity的信息。

任务栈的工作原理

任务栈的用处(个人小总结):

  • 回到上一个页面:最基本的back 键回到上一个界面。
  • 方便复用:比如singleTask。
  • 方便回收内存:内存不够的时候优先回收看不见的Activity

可以通过adb shell dumpsys activity activities入手。具体的流程是ActivityManagerService#dumpActivitiesLocked,到ActivityStackSupervisor#dumpActivitiesLocked,再到ActivityStack#dumpActivitiesLocked,再到TaskRecord#dump,最后ActivityRecord#dump

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
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #0:
mFullscreen=true
mBounds=null
Task id #496
mFullscreen=true
mBounds=null
mMinWidth=-1
mMinHeight=-1
mLastNonFullscreenBounds=null
* TaskRecord{de235a2 #496 I=com.google.android.apps.nexuslauncher/.NexusLauncherActivity U=0 StackId=0 sz=1}
userId=0 effectiveUid=u0a14 mCallingUid=0 mUserSetupComplete=true mCallingPackage=null
intent={act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.google.android.apps.nexuslauncher/.NexusLauncherActivity}
realActivity=com.google.android.apps.nexuslauncher/.NexusLauncherActivity
autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=1 mTaskToReturnTo=1
rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{5608242 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t496}]
askedCompatMode=false inRecents=true isAvailable=true
lastThumbnail=null lastThumbnailFile=/data/system_ce/0/recent_images/496_task_thumbnail.png
stackId=0
hasBeenVisible=true mResizeMode=RESIZE_MODE_RESIZEABLE isResizeable=false firstActiveTime=1513676204884 lastActiveTime=1513676204884 (inactive for 29s)
* Hist #0: ActivityRecord{5608242 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t496}
packageName=com.google.android.apps.nexuslauncher processName=com.google.android.apps.nexuslauncher
launchedFromUid=0 launchedFromPackage=null userId=0
app=ProcessRecord{dd45228 2110:com.google.android.apps.nexuslauncher/u0a14}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.google.android.apps.nexuslauncher/.NexusLauncherActivity }
frontOfTask=true task=TaskRecord{de235a2 #496 I=com.google.android.apps.nexuslauncher/.NexusLauncherActivity U=0 StackId=0 sz=1}
taskAffinity=null
realActivity=com.google.android.apps.nexuslauncher/.NexusLauncherActivity
baseDir=/system/priv-app/NexusLauncherPrebuilt/NexusLauncherPrebuilt.apk
dataDir=/data/user/0/com.google.android.apps.nexuslauncher
stateNotNeeded=true componentSpecified=false mActivityType=1
compat={480dpi} labelRes=0x7f0c0076 icon=0x7f030000 theme=0x7f120001
config={1.0 310mcc260mnc [zh_CN_#Hans,en_US] ldltr sw360dp w360dp h568dp 480dpi nrml port finger -keyb/v/h -nav/h s.7}
taskConfigOverride={1.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}
taskDescription: iconFilename=null label="null" color=fff5f5f5
launchFailed=false launchCount=1 lastLaunchTime=-29s757ms
haveState=false icicle=null
state=RESUMED stopped=false delayedResume=false finishing=false
keysPaused=false inHistory=true visible=true sleeping=false idle=true mStartingWindowState=STARTING_WINDOW_NOT_SHOWN
fullscreen=true noDisplay=false immersive=false launchMode=2
frozenBeforeDestroy=false forceNewConfig=false
mActivityType=HOME_ACTIVITY_TYPE
waitingVisible=false nowVisible=true lastVisibleTime=-29s20ms
connections=[ConnectionRecord{3856a7d u0 CR IMP WACT com.google.android.googlequicksearchbox/com.google.android.apps.gsa.nowoverlayservice.DrawerOverlayService:@33068d4}]
resizeMode=RESIZE_MODE_RESIZEABLE
Running activities (most recent first):
TaskRecord{de235a2 #496 I=com.google.android.apps.nexuslauncher/.NexusLauncherActivity U=0 StackId=0 sz=1}
Run #0: ActivityRecord{5608242 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t496}
mResumedActivity: ActivityRecord{5608242 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t496}
mFocusedActivity: ActivityRecord{5608242 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t496}
mFocusedStack=ActivityStack{2f46233 stackId=0, 1 tasks} mLastFocusedStack=ActivityStack{2f46233 stackId=0, 1 tasks}
mSleepTimeout=false
mCurTaskIdForUser={0=496}
mUserStackInFront={}
mActivityContainers={0=ActivtyContainer{0}A}
mLockTaskModeState=NONE mLockTaskPackages (userId:packages)=
0:[]
mLockTaskModeTasks[]

根据dump 的文件一层层看下去,Display #代表的是一个ActivityStackSupervisor#ActivityDisplayStack #代表的是一个ActivityStackTask id #代表的是一个TaskRecord,最后Hist #代表的是一个ActivityRecord。这里附上一张网上的图,来源四大组件之ActivityRecordStack组成图
做应用开发主要是需要能够从这个dump 里面发现自己的问题,所以比较重要的是TaskRecordActivityRecordTaskRecord代表的是常说的任务栈,ActivityRecord则是任务栈中的某个Activity。
最后再看看应用开发中比较特殊的singleTask 和singleInstance。singleTask 代表的是如果在一个TaskRecord里面创建一个ActivityRecord的实例时候发现这个TaskRecord已经有了这个实例那么就会直接使用这个TaskRecord里面的实例,不再创建新的ActivityRecord。singleInstance 则代表的更强的意思,一个singleTask 的实例会单独的保存在一个TaskRecord中。

总结:任务栈的内容在应用开发过程中是一个比较重要的内容,比如app 的主页一般都是需要设置成singleTask 的。

总结

写完以后发现自己分析的还是非常的粗糙(特别是对比网上大牛的博客),但是觉得自己的目的达到了,站在应用开发的角度,去寻找了几个之前没有找到答案的问题。