從Android源碼剖析Intent查詢匹配的實(shí)現(xiàn)
前言
這篇文章主要是介紹一下Android Intent,并且從Android源碼的角度對(duì)Intent查詢匹配過程進(jìn)行分析。
Intent介紹
Intent的中文是“意圖”的意思,而意圖是一個(gè)非常抽象的概念,那么在Android的編碼設(shè)計(jì)中,如何實(shí)例化意圖呢?因此Android系統(tǒng)明確指定一個(gè)Intent可由兩方面屬性來衡量。
主要屬性:包括Action和Data。其中Action用于表示該Intent所表達(dá)的動(dòng)作意圖,Data用于表示該Action所操作的數(shù)據(jù)。
次要屬性:包括Category、Type、Component和Extras。其中Category表示類別,Type表示數(shù)據(jù)的MIME類型,Component可用于指定特定的Intent的響應(yīng)者(例如指定intent為某個(gè)包下的某個(gè)class類),Extras用于承載其他的信息。
Android系統(tǒng)中主要有兩種類型的Intent,顯示Intent(Explicit Intent)和隱式Intent(Implicit Intent)。
Explicit Intent:這類Intent明確指明了要找哪個(gè)Component。在代碼中可以通過setClassName或者setComponent來鎖定目標(biāo)對(duì)象。
Implicit Intent:這類Intent不明確指明要啟動(dòng)哪個(gè)Component,而是設(shè)置Action、Data、Category讓系統(tǒng)來篩選出合適的Component。
接下來,寫兩個(gè)代碼示例,來介紹一下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實(shí)現(xiàn)了Explicit Intent。源碼如下:
public Intent setClassName(String packageName, String className) { mComponent = new ComponentName(packageName, className); return this; }
然后,在給出一個(gè)Implict Intent的代碼示例。我這里用一個(gè)Activity標(biāo)注一些Intent Filter為例,然后在寫一個(gè)Intent用于啟動(dòng)它。
<activity android:name=".SendIntentType"> <intent-filter > <action android:name="justtest"/> <category android:name="justcategory"/> </intent-filter> </activity>
在當(dāng)前應(yīng)用的AndroidManifest.xml中,給SendIntentType類增加了intent-filter,action的名字為“justtest”,category的名字為“justcategory”。啟動(dòng)該Activity的代碼如下:
private void startImplictIntent() { Intent intent = new Intent(); intent.setAction("justaction"); intent.addCategory("justcategory"); startActivity(intent); }
系統(tǒng)在匹配Implict Intent的過程中,將以Intent Filter列出的3項(xiàng)內(nèi)容為參考標(biāo)準(zhǔn),具體步驟如下:
- 首先匹配IntentFilter的Action,如果Intent設(shè)置的action不滿足IntentFilter的Action,則匹配失敗。如果IntentFilter未設(shè)定Action或者設(shè)定的Action相同,則匹配成功。
- 然后檢查IntentFilter的Category,匹配方法同Action的匹配相同,唯一例外的是當(dāng)Category為CATEGORY_DEFAULT的情況。
- 最后檢查Data。
Activityi信息的管理
從上面的分析可以看出,系統(tǒng)的匹配Intent的過程中,首先需要管理當(dāng)前系統(tǒng)中所有Activity信息。Activity的信息是PackageManagerService在掃描APK的時(shí)候進(jìn)行收集和管理的。相關(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"); }
上面代碼中,有兩個(gè)比較重要的數(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)部也有一個(gè)mActivities變量,它以ComponentName為key,保存PackageParser.Activity對(duì)象。
從APK中解析得到的所有和Acitivity相關(guān)的信息(包括XML中聲明的IntentFilter標(biāo)簽)都由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:用于保存僅設(shè)置Action條件的IntentFilter信息。
- mTypedActionToFilter:用于保存既設(shè)置了Action又設(shè)置了Data的MIME類型的IntentFilter信息。
了解了大概的數(shù)據(jù)結(jié)構(gòu)之后,我們來看一下register_intent_filter的函數(shù)實(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; }
然后又是一個(gè)addFilter函數(shù),明顯是一個(gè)函數(shù)重載,我們來看一下這個(gè)addFilter的實(shí)現(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); } } }
其實(shí)代碼還是很簡單的,如果F數(shù)組存在,則判斷容量,不夠則擴(kuò)容,夠的話就找到位置插入。如果F數(shù)組不存在,則創(chuàng)建一個(gè)容量為2的數(shù)組,將0號(hào)元素賦值為該filter。
Intent匹配查詢分析
客戶端通過ApplicationPackageManager輸出的queryIntentActivities函數(shù)向PackageManagerService發(fā)起一次查詢請(qǐng)求,代碼如下:
@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的真正實(shí)現(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得到對(duì)應(yīng)的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的實(shí)現(xiàn)較為簡單,我們重點(diǎn)來看一下Implict Intent實(shí)現(xiàn)。Implicit Intent調(diào)用了queryIntent方法,我們來看一下queryIntent的實(shí)現(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ù)完成了。查詢的匹配實(shí)現(xiàn)我就不貼代碼了,大家自己去查詢看就好了。
- Android 正則表達(dá)式驗(yàn)證手機(jī)號(hào)、姓名(包含少數(shù)民族)、身份證號(hào)
- Android 中使用ContentObserver模式獲取短信用正則自動(dòng)填充驗(yàn)證碼
- Android判斷11位手機(jī)號(hào)碼的方法(正則表達(dá)式)
- Android正則表達(dá)式
- Android中手機(jī)號(hào)、車牌號(hào)正則表達(dá)式大全
- Android 搜索結(jié)果匹配關(guān)鍵字且高亮顯示功能
- Android實(shí)現(xiàn)自動(dòng)匹配關(guān)鍵字并且標(biāo)紅功能
- Android Java實(shí)現(xiàn)余弦匹配算法示例代碼
- Android編程中號(hào)碼匹配位數(shù)修改的方法
- Android編程開發(fā)中的正則匹配操作示例
相關(guān)文章
Java靜態(tài)內(nèi)部類實(shí)現(xiàn)單例過程
這篇文章主要介紹了Java靜態(tài)內(nèi)部類實(shí)現(xiàn)單例過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Spring Boot實(shí)戰(zhàn)之發(fā)送郵件示例代碼
本篇文章主要介紹了Spring Boot實(shí)戰(zhàn)之發(fā)送郵件示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03SpringBoot打Jar包在命令行運(yùn)行流程詳解
這篇文章主要介紹了SpringBoot打Jar包在命令行運(yùn)行流程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09spring boot2結(jié)合mybatis增刪改查的實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于spring boot2結(jié)合mybatis增刪改查的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot2具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09java 出現(xiàn)Zipexception 異常的解決辦法
這篇文章主要介紹了java 出現(xiàn)Zipexception 異常的解決辦法的相關(guān)資料,出現(xiàn) java.util.zip.ZipException: error in opening zip file 異常的原因及解決方法,需要的朋友可以參考下2017-08-08Java TCP網(wǎng)絡(luò)通信協(xié)議詳細(xì)講解
TCP/IP是一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議,它會(huì)保證數(shù)據(jù)不丟包、不亂序。TCP全名是Transmission?Control?Protocol,它是位于網(wǎng)絡(luò)OSI模型中的第四層2022-09-09