前言
这篇文章主要是介绍一下Android Intent,并且从Android源码的角度对Intent查询匹配过程进行分析。
Intent介绍
Intent的中文是“意图”的意思,而意图是一个非常抽象的概念,那么在Android的编码设计中,如何实例化意图呢?因此Android系统明确指定一个Intent可由两方面属性来衡量。
主要属性:包括Action和Data。其中Action用于表示该Intent所表达的动作意图,Data用于表示该Action所操作的数据。
次要属性:包括Category、Type、Component和Extras。其中Category表示类别,Type表示数据的MIME类型,Component可用于指定特定的Intent的响应者(例如指定intent为某个包下的某个class类),Extras用于承载其他的信息。
Android系统中主要有两种类型的Intent,显示Intent(Explicit Intent)和隐式Intent(Implicit Intent)。
Explicit Intent:这类Intent明确指明了要找哪个Component。在代码中可以通过setClassName或者setComponent来锁定目标对象。
Implicit Intent:这类Intent不明确指明要启动哪个Component,而是设置Action、Data、Category让系统来筛选出合适的Component。
接下来,写两个代码示例,来介绍一下Explicit Intent和Implict Inent。首先是Explicit Intent:
1
2
3
4
5
6
7
8
9
10
11
12
|
private void startExplicitIntentWithComponent() {
Intent intent = new Intent();
ComponentName component = new ComponentName( "com.example.photocrop" , "com.example.photocrop.MainActivity" );
intent.setComponent(component);
startActivity(intent);
}
private void startExplicitIntentWithClassName() {
Intent intent = new Intent();
intent.setClassName( "com.example.photocrop" , "com.example.photocrop.MainActivity" );
startActivity(intent);
}
|
但是,从源码里面去看,发现setClassName也是借助了ComponentName实现了Explicit Intent。源码如下:
1
2
3
4
|
public Intent setClassName(String packageName, String className) {
mComponent = new ComponentName(packageName, className);
return this ;
}
|
然后,在给出一个Implict Intent的代码示例。我这里用一个Activity标注一些Intent Filter为例,然后在写一个Intent用于启动它。
1
2
3
4
5
6
7
|
<activity
android:name= ".SendIntentType" >
<intent-filter >
<action android:name= "justtest" />
<category android:name= "justcategory" />
</intent-filter>
</activity>
|
在当前应用的AndroidManifest.xml中,给SendIntentType类增加了intent-filter,action的名字为“justtest”,category的名字为“justcategory”。启动该Activity的代码如下:
1
2
3
4
5
6
|
private void startImplictIntent() {
Intent intent = new Intent();
intent.setAction( "justaction" );
intent.addCategory( "justcategory" );
startActivity(intent);
}
|
系统在匹配Implict Intent的过程中,将以Intent Filter列出的3项内容为参考标准,具体步骤如下:
- 首先匹配IntentFilter的Action,如果Intent设置的action不满足IntentFilter的Action,则匹配失败。如果IntentFilter未设定Action或者设定的Action相同,则匹配成功。
- 然后检查IntentFilter的Category,匹配方法同Action的匹配相同,唯一例外的是当Category为CATEGORY_DEFAULT的情况。
- 最后检查Data。
Activityi信息的管理
从上面的分析可以看出,系统的匹配Intent的过程中,首先需要管理当前系统中所有Activity信息。Activity的信息是PackageManagerService在扫描APK的时候进行收集和管理的。相关源码如下:
1
2
3
4
5
6
7
8
9
|
// 处理该package的activity信息
N = pkg.activities.size();
r = null ;
for (i = 0 ; i < N; i++) {
PackageParser.Activity a = pkg.activities.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName, a.info.processName,
pkg.applicationInfo.uid);
mActivities.addActivity(a, "activity" );
}
|
上面代码中,有两个比较重要的数据结构,如下图所示。
结合代码和上图的数据结构,可知:
mAcitivitys为ActivityIntentResolver类型,是PKMS的成员变量,用于保存系统中所有与Activity相关的信息。此数据结构内部也有一个mActivities变量,它以ComponentName为key,保存PackageParser.Activity对象。
从APK中解析得到的所有和Acitivity相关的信息(包括XML中声明的IntentFilter标签)都由PackageParser.Activity来保存。
前面代码中调用addActivity函数完成了私有信息的公有化。addActivity函数的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public final void addActivity(PackageParser.Activity a, String type) {
final boolean systemApp = isSystemApp(a.info.applicationInfo);
mActivities.put(a.getComponentName(), a);
final int NI = a.intents.size();
for ( int j = 0 ; j < NI; j++) {
PackageParser.ActivityIntentInfo intent = a.intents.get(j);
if (!systemApp && intent.getPriority() > 0 && "activity" .equals(type)) {
// 非系统APK的priority必须为0
intent.setPriority( 0 );
}
addFilter(intent);
}
}
|
接下来看一下addFilter函数。函数源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public void addFilter(F f) {
// mFilters保存所有IntentFilter信息
mFilters.add(f);
int numS = register_intent_filter(f, f.schemesIterator(),
mSchemeToFilter, " Scheme: " );
int numT = register_mime_types(f, " Type: " );
if (numS == 0 && numT == 0 ) {
register_intent_filter(f, f.actionsIterator(),
mActionToFilter, " Action: " );
}
if (numT != 0 ) {
register_intent_filter(f, f.actionsIterator(),
mTypedActionToFilter, " TypedAction: " );
}
}
|
这里又出现了几种数据结构,它们的类似都是ArrayMap<String, F[ ]>,其中F为模板参数。
- mSchemeToFilter:用于保存uri中与scheme相关的IntentFilter信息。
- mActionToFilter:用于保存仅设置Action条件的IntentFilter信息。
- mTypedActionToFilter:用于保存既设置了Action又设置了Data的MIME类型的IntentFilter信息。
了解了大概的数据结构之后,我们来看一下register_intent_filter的函数实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private final int register_intent_filter(F filter, Iterator<String> i,
ArrayMap<String, F[]> dest, String prefix) {
if (i == null ) {
return 0 ;
}
int num = 0 ;
while (i.hasNext()) {
String name = i.next();
num++;
addFilter(dest, name, filter);
}
return num;
}
|
然后又是一个addFilter函数,明显是一个函数重载,我们来看一下这个addFilter的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) {
F[] array = map.get(name);
if (array == null ) {
array = newArray( 2 );
map.put(name, array);
array[ 0 ] = filter;
} else {
final int N = array.length;
int i = N;
while (i > 0 && array[i- 1 ] == null ) {
i--;
}
if (i < N) {
array[i] = filter;
} else {
F[] newa = newArray((N* 3 )/ 2 );
System.arraycopy(array, 0 , newa, 0 , N);
newa[N] = filter;
map.put(name, newa);
}
}
}
|
其实代码还是很简单的,如果F数组存在,则判断容量,不够则扩容,够的话就找到位置插入。如果F数组不存在,则创建一个容量为2的数组,将0号元素赋值为该filter。
Intent匹配查询分析
客户端通过ApplicationPackageManager输出的queryIntentActivities函数向PackageManagerService发起一次查询请求,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent,
int flags) {
return queryIntentActivitiesAsUser(intent, flags, mContext.getUserId());
}
/** @hide Same as above but for a specific user */
@Override
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
int flags, int userId) {
try {
return mPM.queryIntentActivities(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags,
userId);
} catch (RemoteException e) {
throw new RuntimeException( "Package manager has died" , e);
}
}
|
可以看到,queryIntentActivities的真正实现是在PackageManagerService.java中,函数代码如下:
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
|
public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId))
return Collections.emptyList();
enforceCrossUserPermission(Binder.getCallingUid(), userId, false , "query intent activities" );
ComponentName comp = intent.getComponent();
if (comp == null ) {
if (intent.getSelector() != null ) {
intent = intent.getSelector();
comp = intent.getComponent();
}
}
if (comp != null ) {
// Explicit的Intent,直接根据component得到对应的ActivityInfo
final List<ResolveInfo> list = new ArrayList<ResolveInfo>( 1 );
final ActivityInfo ai = getActivityInfo(comp, flags, userId);
if (ai != null ) {
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
list.add(ri);
}
return list;
}
// reader
synchronized (mPackages) {
final String pkgName = intent.getPackage();
if (pkgName == null ) {
// Implicit Intent
return mActivities.queryIntent(intent, resolvedType, flags, userId);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null ) {
// 指定了包名的Intent
return mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities, userId);
}
return new ArrayList<ResolveInfo>();
}
}
|
可以看到,Explicit Intent的实现较为简单,我们重点来看一下Implict Intent实现。Implicit Intent调用了queryIntent方法,我们来看一下queryIntent的实现代码:
1
2
3
4
5
6
|
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, int userId) {
if (!sUserManager.exists(userId))
return null ;
mFlags = flags;
return super .queryIntent(intent, resolvedType, (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0 , userId);
}
|
继续跟踪到IntentResolver.java的queryIntent方法,源码如下:
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
77
|
public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
int userId) {
String scheme = intent.getScheme();
ArrayList<R> finalList = new ArrayList<R>();
// 最多有4轮匹配操作
F[] firstTypeCut = null ;
F[] secondTypeCut = null ;
F[] thirdTypeCut = null ;
F[] schemeCut = null ;
// If the intent includes a MIME type, then we want to collect all of
// the filters that match that MIME type.
if (resolvedType != null ) {
int slashpos = resolvedType.indexOf( '/' );
if (slashpos > 0 ) {
final String baseType = resolvedType.substring( 0 , slashpos);
if (!baseType.equals( "*" )) {
if (resolvedType.length() != slashpos+ 2
|| resolvedType.charAt(slashpos+ 1 ) != '*' ) {
// Not a wild card, so we can just look for all filters that
// completely match or wildcards whose base type matches.
firstTypeCut = mTypeToFilter.get(resolvedType);
secondTypeCut = mWildTypeToFilter.get(baseType);
} else {
// We can match anything with our base type.
firstTypeCut = mBaseTypeToFilter.get(baseType);
secondTypeCut = mWildTypeToFilter.get(baseType);
}
// Any */* types always apply, but we only need to do this
// if the intent type was not already */*.
thirdTypeCut = mWildTypeToFilter.get( "*" );
} else if (intent.getAction() != null ) {
// The intent specified any type ({@literal *}/*). This
// can be a whole heck of a lot of things, so as a first
// cut let's use the action instead.
firstTypeCut = mTypedActionToFilter.get(intent.getAction());
}
}
}
// If the intent includes a data URI, then we want to collect all of
// the filters that match its scheme (we will further refine matches
// on the authority and path by directly matching each resulting filter).
if (scheme != null ) {
schemeCut = mSchemeToFilter.get(scheme);
}
// If the intent does not specify any data -- either a MIME type or
// a URI -- then we will only be looking for matches against empty
// data.
if (resolvedType == null && scheme == null && intent.getAction() != null ) {
firstTypeCut = mActionToFilter.get(intent.getAction());
}
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
if (firstTypeCut != null ) {
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, firstTypeCut, finalList, userId);
}
if (secondTypeCut != null ) {
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, secondTypeCut, finalList, userId);
}
if (thirdTypeCut != null ) {
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, thirdTypeCut, finalList, userId);
}
if (schemeCut != null ) {
buildResolveList(intent, categories, debug, defaultOnly,
resolvedType, scheme, schemeCut, finalList, userId);
}
sortResults(finalList);
return finalList;
}
|
具体的查询匹配过程是由buildResolveList函数完成了。查询的匹配实现我就不贴代码了,大家自己去查询看就好了。