從Android源碼剖析Intent查詢匹配的實現(xiàn)
前言
這篇文章主要是介紹一下Android Intent,并且從Android源碼的角度對Intent查詢匹配過程進行分析。
Intent介紹
Intent的中文是“意圖”的意思,而意圖是一個非常抽象的概念,那么在Android的編碼設計中,如何實例化意圖呢?因此Android系統(tǒng)明確指定一個Intent可由兩方面屬性來衡量。
主要屬性:包括Action和Data。其中Action用于表示該Intent所表達的動作意圖,Data用于表示該Action所操作的數(shù)據(jù)。
次要屬性:包括Category、Type、Component和Extras。其中Category表示類別,Type表示數(shù)據(jù)的MIME類型,Component可用于指定特定的Intent的響應者(例如指定intent為某個包下的某個class類),Extras用于承載其他的信息。
Android系統(tǒng)中主要有兩種類型的Intent,顯示Intent(Explicit Intent)和隱式Intent(Implicit Intent)。
Explicit Intent:這類Intent明確指明了要找哪個Component。在代碼中可以通過setClassName或者setComponent來鎖定目標對象。
Implicit Intent:這類Intent不明確指明要啟動哪個Component,而是設置Action、Data、Category讓系統(tǒng)來篩選出合適的Component。
接下來,寫兩個代碼示例,來介紹一下Explicit Intent和Implict Inent。首先是Explicit Intent:
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);
}
但是,從源碼里面去看,發(fā)現(xiàn)setClassName也是借助了ComponentName實現(xiàn)了Explicit Intent。源碼如下:
public Intent setClassName(String packageName, String className) {
mComponent = new ComponentName(packageName, className);
return this;
}
然后,在給出一個Implict Intent的代碼示例。我這里用一個Activity標注一些Intent Filter為例,然后在寫一個Intent用于啟動它。
<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的代碼如下:
private void startImplictIntent() {
Intent intent = new Intent();
intent.setAction("justaction");
intent.addCategory("justcategory");
startActivity(intent);
}
系統(tǒng)在匹配Implict Intent的過程中,將以Intent Filter列出的3項內(nèi)容為參考標準,具體步驟如下:
- 首先匹配IntentFilter的Action,如果Intent設置的action不滿足IntentFilter的Action,則匹配失敗。如果IntentFilter未設定Action或者設定的Action相同,則匹配成功。
- 然后檢查IntentFilter的Category,匹配方法同Action的匹配相同,唯一例外的是當Category為CATEGORY_DEFAULT的情況。
- 最后檢查Data。
Activityi信息的管理
從上面的分析可以看出,系統(tǒng)的匹配Intent的過程中,首先需要管理當前系統(tǒng)中所有Activity信息。Activity的信息是PackageManagerService在掃描APK的時候進行收集和管理的。相關(guān)源碼如下:
// 處理該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");
}
上面代碼中,有兩個比較重要的數(shù)據(jù)結(jié)構(gòu),如下圖所示。

結(jié)合代碼和上圖的數(shù)據(jù)結(jié)構(gòu),可知:
mAcitivitys為ActivityIntentResolver類型,是PKMS的成員變量,用于保存系統(tǒng)中所有與Activity相關(guān)的信息。此數(shù)據(jù)結(jié)構(gòu)內(nèi)部也有一個mActivities變量,它以ComponentName為key,保存PackageParser.Activity對象。
從APK中解析得到的所有和Acitivity相關(guān)的信息(包括XML中聲明的IntentFilter標簽)都由PackageParser.Activity來保存。
前面代碼中調(diào)用addActivity函數(shù)完成了私有信息的公有化。addActivity函數(shù)的代碼如下:
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)) {
// 非系統(tǒng)APK的priority必須為0
intent.setPriority(0);
}
addFilter(intent);
}
}
接下來看一下addFilter函數(shù)。函數(shù)源碼如下:
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: ");
}
}
這里又出現(xiàn)了幾種數(shù)據(jù)結(jié)構(gòu),它們的類似都是ArrayMap<String, F[ ]>,其中F為模板參數(shù)。
- mSchemeToFilter:用于保存uri中與scheme相關(guān)的IntentFilter信息。
- mActionToFilter:用于保存僅設置Action條件的IntentFilter信息。
- mTypedActionToFilter:用于保存既設置了Action又設置了Data的MIME類型的IntentFilter信息。
了解了大概的數(shù)據(jù)結(jié)構(gòu)之后,我們來看一下register_intent_filter的函數(shù)實現(xiàn):
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函數(shù),明顯是一個函數(shù)重載,我們來看一下這個addFilter的實現(xiàn):
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數(shù)組存在,則判斷容量,不夠則擴容,夠的話就找到位置插入。如果F數(shù)組不存在,則創(chuàng)建一個容量為2的數(shù)組,將0號元素賦值為該filter。
Intent匹配查詢分析
客戶端通過ApplicationPackageManager輸出的queryIntentActivities函數(shù)向PackageManagerService發(fā)起一次查詢請求,代碼如下:
@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的真正實現(xiàn)是在PackageManagerService.java中,函數(shù)代碼如下:
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,直接根據(jù)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的實現(xiàn)較為簡單,我們重點來看一下Implict Intent實現(xiàn)。Implicit Intent調(diào)用了queryIntent方法,我們來看一下queryIntent的實現(xiàn)代碼:
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);
}
繼續(xù)跟蹤到IntentResolver.java的queryIntent方法,源碼如下:
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函數(shù)完成了。查詢的匹配實現(xiàn)我就不貼代碼了,大家自己去查詢看就好了。
相關(guān)文章
JavaSE實戰(zhàn)之酒店訂房系統(tǒng)的實現(xiàn)
這篇文章主要為大家詳細介紹了如何利用JavaSE實現(xiàn)酒店訂房系統(tǒng),文中的示例代碼講解詳細,對我們學習JavaSE開發(fā)有一定的幫助,需要的可以參考一下2022-07-07
SSH結(jié)合jquery實現(xiàn)三級聯(lián)動效果
這篇文章主要為大家詳細介紹了SSH結(jié)合jquery實現(xiàn)三級聯(lián)動效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01
java 將數(shù)據(jù)加載到內(nèi)存中的操作
這篇文章主要介紹了java 將數(shù)據(jù)加載到內(nèi)存中的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
關(guān)于Java中String創(chuàng)建的字符串對象內(nèi)存分配測試問題
這篇文章主要介紹了Java中String創(chuàng)建的字符串對象內(nèi)存分配測試,給大家詳細介紹了在創(chuàng)建String對象的兩種常用方法比較,通過示例代碼給大家介紹的非常詳細,需要的朋友可以參考下2021-07-07
java報錯之springboot3+vue2項目web服務層報錯總結(jié)
java入門學習,隨手記錄一下開發(fā)過程中產(chǎn)生的報錯,有些錯誤是網(wǎng)上搜索再加上自己嘗試,隨手引用了一些其他人的記錄,也是留給自己看的,或是希望能對其他初學者有幫助2023-06-06

