關於Intent以及IntentFilter的基本知識,大家可以參閱如下資料,
SDK中對Intent與IntentFilter的介紹 ---- 英文
其中文翻譯如下:
Android開發之旅: Intents和Intent Filters(理論部分)
我重點分析一下兩個方面:
第一部分 、Intent以及IntentFilter說明以及匹配規則分析
第一部分 、Intent以及IntentFilter說明以及匹配規則分析
想當初我看Intent相關知識時,對Intent、IntentFilter的理解就很差勁,總覺得系統定義了一個Intent,爲何還要整理個
IntentFilter出來"禍害"廣大程序猿呢?但不解歸不解,在具體使用咱可不能含糊,於是只好依葫蘆畫瓢了,反正絕對還不錯。
一、溫故而知新 :Intent與IntentFilter兩問。
二、它們之間的關係是如何呢?
- public class Intent implements Parcelable, Cloneable {
- private String mAction; //action值
- private Uri mData; //uri
- private String mType; //MimeType
- private String mPackage; //所在包名
- private ComponentName mComponent; //組件信息
- private int mFlags; //Flag標誌位
- private HashSet<String> mCategories; //Category值
- private Bundle mExtras; //附加值信息
- //...
- }
IntentFilter類源碼(部分) 路徑位於:\frameworks\base\core\java\android\content\IntentFilter.java
- public class IntentFilter implements Parcelable {
- //...
- //保存了所有action字段的值
- private final ArrayList<String> mActions;
- //保存了所有Category的值
- private ArrayList<String> mCategories = null;
- //保存了所有Schema(模式)的值
- private ArrayList<String> mDataSchemes = null;
- //保存了所有Authority字段的值
- private ArrayList<AuthorityEntry> mDataAuthorities = null;
- //保存了所有Path的值
- private ArrayList<PatternMatcher> mDataPaths = null;
- //保存了所有MimeType的值
- private ArrayList<String> mDataTypes = null;
- //...
- }
PS :大家可以參詳下Intent與IntentFilter類中不同字段的屬性類型。Intent中屬性類型基本上都是單個類型的,而IntentFilter
屬性都是集合類型的。從這方面思考,更可以加深我們的理解。
三、Intent匹配規則
匹配種類有如下三種:
比較好理解的是,進行匹配時Intent攜帶的Action字段值和Category字段值必須包含在IntentFilter中,否則匹配失敗。
SDK中說明的具體規則如下:
PS :可別說我不會總結出來給大家分享,其實我覺得很多知識都需要自己去嘗試,去努力吸收,只要經過自己的消化,
學到的知識就是自己的了。
上面的規則比較生硬吧。我們去源碼中去看看Intent與IntentFilter的具體匹配方法吧。
該方法是IntentFilter中的match()方法,該方法的內部處理邏輯就是按照上面的規則去判斷的,大家可以仔細體味下,該方法
我們在後面講到Intent解析過程時也會用到。 具體邏輯在代碼中進行了說明。
- public class IntentFilter implements Parcelable {
- //匹配算法,,按照匹配規則進行
- //Intent與該IntentFilter進行匹配時調用該方法參數表示Intent的相關屬性值
- public final int match(String action, String type, String scheme,
- Uri data, Set<String> categories, String logTag){
- //首先、匹配Action字段
- if (action != null && !matchAction(action)) {
- if (Config.LOGV) Log.v(
- logTag, "No matching action " + action + " for " + this);
- return NO_MATCH_ACTION;
- }
- //其次、匹配數據(Uri和MimeType)字段
- int dataMatch = matchData(type, scheme, data);
- //...
- //最後,匹配Category字段值
- String categoryMatch = matchCategories(categories);
- //...
- }
- //...
- }
第二部分、 Intent的解析過程分析
一、引入PackageManager
我們知道Android源碼總是貼心的(不知道有沒有10086貼心),它對外提供了很多借口供應用程序調用,例如AudioManger
(音頻管理)、TelephoneManger(電話管理)、同樣也提供了一個包管理-----PackageManager,通過它我們可以、獲取應用
程序包得信息,例如圖標、Lable標籤等。具體關於PackageManager的使用,可以參考我的另外一篇文章 :
<<Android中獲取應用程序(包)的信息-----PackageManager的使用(一)>>
二、PackageManagerService ---- 重量級選手
前面所說PackageManager不過是個傀儡,所有相關的操作都是由PackageManagerService 完成的。這兒我們簡單的
分析下PackageManagerService 的特性:
①、開機就啓動,由SystemServer進程啓動 ;
②、啓動後它會掃描系統中所有應用程序Apk包下的AndroidManifest.xml文件,然後解析所有的
AndroidManifest.xml文件,繼而形成一個龐大的信息結構樹,並且保存在PackageManagerService 的相關屬性下。
它會掃描這兩個目錄:
/system/app ------------------> 系統應用程序
/data/app ------------------> 第三方應用程序(所有安裝的Apk包都會在該目錄下保存一份拷貝)
掃描完成後,於是所有的信息結構就構建了。PackageManagerService 的四個重要屬性如下:
- class PackageManagerService extends IPackageManager.Stub {
- //...
- //保存了所有Activity節點信息 。 自定義類
- // All available activities, for your resolving pleasure.
- final ActivityIntentResolver mActivities =
- new ActivityIntentResolver();
- //保存了所有BroadcastReceiver節點信息 。 自定義類
- // All available receivers, for your resolving pleasure.
- final ActivityIntentResolver mReceivers =
- new ActivityIntentResolver();
- //保存了所有Service節點信息。 。 自定義類
- // All available services, for your resolving pleasure.
- final ServiceIntentResolver mServices = new ServiceIntentResolver();
- //保存了所有ContentProvider節點信息 , 以Hash值保存
- // Keys are String (provider class name), values are Provider.
- final HashMap<ComponentName, PackageParser.Provider> mProvidersByComponent =
- new HashMap<ComponentName, PackageParser.Provider>();
- //...
- }
- @Override
- public PackageManager getPackageManager() {
- //...
- // Doesn't matter if we make more than one instance.
- return (mPackageManager = new ApplicationPackageManager(this, pm));
- //...
- }
- class ContextImpl extends Context {
- //...
- /*package*/
- static final class ApplicationPackageManager extends PackageManager{
- //...
- @Override
- public ActivityInfo getActivityInfo(ComponentName className, int flags){
- //...
- }
- }
- //...
- }
1、保存數據採用的數據結構
作用:保存了每個<intent-filter>節點信息
ActivityIntentInfo類:繼承至IntentInfo類
作用:保存了<activity />節點下的< intent-filter>節點信息
ServiceIntentInfo:繼承至IntentInfo類
作用:保存了<service />節點下的< intent-filter >節點信息
Activity類:保存了<activity />節點信息
Service類:保存了<service />節點信息
PS:這些都是PackageParser類的內部類 。PackageParser的主要功能就是解析AndroidManifest.xml文件
IntentResolver類:模板類,父類,保存了<activity/>、<service/>、<receiver />節點的共同信息。
ActivityIntentResolver類:繼承至IntentResolver類。
作用:保存了所有<activity/>或者<receiver/>節點信息。(Activity或者BroadcastReceiver信息就是用該自定義類保存的)
ServiceIntentResolver類:繼承至IntentResolver類,保存了
作用:保存了所有<service/>節點信息。(Service信息就是用該自定義類保存的)。
一個簡單的UML圖表示如下:
2、Intent解析採用的算法
不同的數據結構決定了不同的算法,而不同的算法又決定着性能,例如時間複雜度以及空間複雜度等。 在具體講解解析
採用的算法時,我們先理解下這個情景。
假設一個女人決定參加一個相親節目(大家可以理解成《非誠勿擾》),然後她向主辦方提出如下條件:
1、身高 ? 175cm以上 ;
2、 財富 ? 100萬 ;
3、 學歷 ? 本科以上 ;
……
主辦發經理一看,你丫的要求還真高。但客戶是萬能的,該經理也只能去找到滿足這些條件的男人咯。
最開始,該經理是這麼想的,我把所有男的都給遍歷一遍,肯定把滿足這些條件的男人給揪出來。他找啊找,覺得這麼找下去
是不是太二B了(呵呵,你也是這麼想的嗎?)。經理就開始想:“我爲什麼不能根據這些條件把所有男的給分成三個種羣呢?有錢
的男人在一起,高個子的男人在一起,高學歷的男人在一起,這樣查找起來不是更快嗎 ? “
於是,有了如下的劃分: PS, 你是屬於哪一類額 ?
二B算法(沒有分類) 高級點的算法(分類後)
可能大家對這種根據關鍵值分類的好處不能一目瞭然。我們舉個一般例子吧:
假設當前共有100個男的。 其中有錢的有20人,高個子有30人,高學歷男人有10人。
根據第一種算法分類,我們需要比較100次,而第二種算法我們總共只需要比較60次。從整個基數來分析,算法肯定優化了
最後,對不同的分類中查詢的結果進行組合重新排列下,即可得到我們的滿足該女性的要求。
同樣的,在進行Intent匹配時,Android也採用了第二種方法來進行算法匹配。它根據一些關鍵值Action、MimeType、
Schema字段去進行分類。分類之後的集合大致如下:
於是在進行具體匹配時,我們只是需要根據關鍵值從不同集合中獲取即可。
事實上,由於MimeType的通配符(*)的特性,它的匹配可以說是最難的。參考IntentResolver類,真正的關鍵值如下:
- //模板類 F類型可能爲ActivityIntentInfo或ServiceIntentInfo,R對象是ResolverInfo類
- public class IntentResolver<F extends IntentFilter, R extends Object> {
- //保存了所有<intent-filter>節點信息
- //All filters that have been registered.
- private final HashSet<F> mFilters = new HashSet<F>();
- /** All of the MIME types that have been registered, such as "image/jpeg",
- * "image/*", or "{@literal *}/*".
- */
- //關鍵值表示MimeType形如: image/jpeg 、 image/*、/* 類型
- private final HashMap<String, ArrayList<F>> mTypeToFilter
- /**
- * The base names of all of all fully qualified MIME types that have been
- * registered, such as "image" or "*". Wild card MIME types such as
- * "image/*" will not be here.
- */
- //關鍵值表示MimeType形如:image、 image/*、* 類型
- private final HashMap<String, ArrayList<F>> mBaseTypeToFilter
- /**
- * The base names of all of the MIME types with a sub-type wildcard that
- * have been registered. For example, a filter with "image/*" will be
- * included here as "image" but one with "image/jpeg" will not be
- * included here. This also includes the "*" for the "{@literal *}/*"
- * MIME type.
- */
- //這個關鍵字段表示MimeType形如 :image、* 類型
- private final HashMap<String, ArrayList<F>> mWildTypeToFilter
- //All of the URI schemes (such as http) that have been registered.
- //關鍵值字段表示Schema
- private final HashMap<String, ArrayList<F>> mSchemeToFilter
- /**
- * All of the actions that have been registered, but only those that did
- * not specify data.
- */
- //關鍵值字段表示:Action
- private final HashMap<String, ArrayList<F>> mActionToFilter
- //All of the actions that have been registered and specified a MIME type.
- //關鍵值字段表示:Action和MimeType。 即該<intent-filter>節點必須包含action和MimeType
- private final HashMap<String, ArrayList<F>> mTypedActionToFilter
- }
於是,通過這些關鍵字段我們可以去特定集合去查找,最後將結果在重新組合下,那不就萬事大吉了。
最後,我們通過代碼走讀的方式,以一個查詢Activity的信息的方法,帶領大家去熟悉具體流程。該方法原型爲:
//通過給定的intent,查詢所有匹配的Activity組件信息
abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags)
PS:其實查詢Activity、Service、BroadcastReceiver的流程基本上是相同的。
Step 1、獲取PackageManager代理對象,調用該方法:
- PackageManager mPackageManger = this.getPackageManager() ;
- //爲了說明,這兒我們簡單查詢一個Intent對象,即所有應用程序的啓動Activity
- Intent mainIntent = new Intent() ;
- mainIntent.setAction(Intent.ACTION_MAIN);
- mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- mPackageManger.queryIntentActivities(mainIntent, 0);
- //獲取PackageManager對象
- @Override
- public PackageManager getPackageManager() {
- //...
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm != null) {
- // Doesn't matter if we make more than one instance.
- return (mPackageManager = new ApplicationPackageManager(this, pm));
- }
- }
Step 2、該PackageManager代理對象實則爲ApplicatonPackageManager 對象,該對象是ContextIml的內部類。
- @Override
- public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
- try {
- //mPM對象就是PackageManagerService的客戶端
- return mPM.queryIntentActivities(
- intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- flags);
- } catch (RemoteException e) {
- throw new RuntimeException("Package manager has died", e);
- }
- }
Step 3、調用服務端PackageManagerService對象的對應方法。該方法位於PackageManagerService.java類中
- public List<ResolveInfo> queryIntentActivities(Intent intent,
- String resolvedType, int flags) {
- //是否設置了組件ComponetName 信息
- ComponentName comp = intent.getComponent();
- if (comp != null) {
- List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
- ActivityInfo ai = getActivityInfo(comp, flags);
- if (ai != null) {
- ResolveInfo ri = new ResolveInfo();
- ri.activityInfo = ai;
- list.add(ri);
- }
- return list;
- }
- synchronized (mPackages) {
- //是否設置了包名
- String pkgName = intent.getPackage();
- if (pkgName == null) {
- //調用mActivities去查詢
- return (List<ResolveInfo>)mActivities.queryIntent(intent,
- resolvedType, flags);
- }
- PackageParser.Package pkg = mPackages.get(pkgName);
- if (pkg != null) {
- return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent,
- resolvedType, flags, pkg.activities);
- }
- return null;
- }
- }
首先、該方法判斷IntentComponentName是否存在,如果存在則爲顯示匹配了,直接返回特定組件相關信息;
其次、判斷是否設置了包名,即packageName,如果沒有指定包名,則查詢所有的應用程序包去找匹配的組件信息。如果
指定了packageName,則去指定包下去查找;
接着,調用特定的類繼續查找。由於我們找的是Activity組件信息,因此去ActivityIntentResolver類去查找。
Step 4、調用mActivities自定義類去查找
- //調用父類IntentResolver方法去查找
- public List queryIntent(Intent intent, String resolvedType, int flags) {
- mFlags = flags;
- return super.queryIntent(intent, resolvedType,
- (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0);
- }
該過程只是簡單的調用了父類IntentResolver去查找。
Step5 、進入IntentResolver類去真正的實現查找,該方法爲於IntentResolver.java類中。
- public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {
- String scheme = intent.getScheme();
- //用來保存查找到的組件信息,如Activity等
- ArrayList<R> finalList = new ArrayList<R>();
- //根據關鍵值去特定集合查詢到的一個可能結果
- ArrayList<F> firstTypeCut = null;
- ArrayList<F> secondTypeCut = null;
- ArrayList<F> thirdTypeCut = null;
- ArrayList<F> schemeCut = null;
- //首先是否制定的數據類型 MimeType
- // 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("*")) {
- //匹配特定的MimeType
- if (resolvedType.length() != slashpos+2|| resolvedType.charAt(slashpos+1) != '*') {
- firstTypeCut = mTypeToFilter.get(resolvedType);
- secondTypeCut = mWildTypeToFilter.get(baseType);
- }
- //...
- }
- }
- //根據模式去匹配特定的集合
- if (scheme != null) {
- schemeCut = mSchemeToFilter.get(scheme);
- }
- //可能的話在去匹配Action所在集合
- if (resolvedType == null && scheme == null && intent.getAction() != null) {
- firstTypeCut = mActionToFilter.get(intent.getAction());
- }
- //對我們前面通過關鍵字查詢的一個集合,在此循環遍歷匹配,將匹配到的結果保存在finalList集合中
- if (firstTypeCut != null) {
- buildResolveList(intent, debug, defaultOnly,
- resolvedType, scheme, firstTypeCut, finalList);
- }
- if (secondTypeCut != null) {
- buildResolveList(intent, debug, defaultOnly,
- resolvedType, scheme, secondTypeCut, finalList);
- }
- if (thirdTypeCut != null) {
- buildResolveList(intent, debug, defaultOnly,resolvedType, scheme, thirdTypeCut, finalList);
- }
- if (schemeCut != null) {
- buildResolveList(intent, debug, defaultOnly,
- resolvedType, scheme, schemeCut, finalList);
- }
- //根據IntentFilter的一些優先級進行排序
- sortResults(finalList);
- return finalList;
- }
buildResolveList()方法的主要作用是將可能的集合在循環遍歷,將匹配的結果值保存在finalList集合中。
該方法爲於IntentResolver.java類中,方法原型如下:
- //通過前面關鍵字查找的可能集合,循環遍歷進行匹配,匹配成功就加入到dest集合中,即finalList集合中
- private void buildResolveList(Intent intent, boolean debug, boolean defaultOnly,
- String resolvedType, String scheme, List<F> src, List<R> dest) {
- Set<String> categories = intent.getCategories();
- final int N = src != null ? src.size() : 0;
- boolean hasNonDefaults = false;
- int i;
- for (i=0; i<N; i++) {
- F filter = src.get(i);
- int match;
- //是否已經加入到匹配結果中去了,不允許重複添加
- // Do we already have this one?
- if (!allowFilterResult(filter, dest)) {
- continue;
- }
- //調用Intent-filter方法去匹配該Intent信息
- match = filter.match(
- intent.getAction(), resolvedType, scheme, intent.getData(), categories, TAG);
- //匹配成功,就存放在finalList集合中
- if (match >= 0) {
- if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
- //調用子類的newResult()方法去返回一個ResolvInfo對象
- final R oneResult = newResult(filter, match);
- if (oneResult != null) {
- dest.add(oneResult);
- }
- } else {
- hasNonDefaults = true;
- }
- } else {
- //...
- }
- }
- //...
- }
這個函數的邏輯判斷如下:
首先、通過給定的關鍵字去特定集合查詢一個可能的匹配集合,然後將這些集合信息保存在如下集合中:
ArrayList<F>firstTypeCut =null;
ArrayList<F>secondTypeCut =null;
ArrayList<F>thirdTypeCut =null;
ArrayList<F>schemeCut =null;
然後、連續四次調用buildResolveList()去進行匹配。每次調用結束後,參數finalList保存的是匹配結果的累加值,所以這
四次調用過程中finalList集合包含的結果是一次累加的過程。當然了,四次連續調用buildResolveList()的次序可以不分先後。
最後、調用sortResults()從匹配集合中進行一些排序等。
總結:第二部分通過重點介紹了PackageManagerService的功能匹配Intent採用的數據結構和算法,也只是簡單入了下門,希
望大家能參考源碼,認真理解Intent匹配過程。