前言
我們都知道,啓動Activity有兩種方式,即隱式啓動和顯式啓動。從優先級來說,顯式啓動要優先於隱式啓動。隱式啓動的優點是使用上的靈活性。因此,掌握隱式啓動中IntentFilter的匹配規則就至關重要了。
對一個Activity而言,可以在AndroidManifest文件中指定多個IntentFilter,Intent只要能夠匹配其中任意一個就算匹配成功。在一個IntentFilter中,可以有多個action、category和data。所有的action組成action類別。同理,所有的category和data分別組成category類別和data類別。Intent需要和action類別、category類別和data類別全部匹配成功,纔算是和這個IntentFilter匹配成功。針對不同的類別,有着不同的匹配規則,具體細節見下文。
action的匹配規則
直觀來說,action是一個字符串。Android系統提供了一些預定義的action,它們都有着自己的含義,我們可以直接使用。當然,我們也可以定義自己的action,以便滿足我們自己的業務需求。在一個IntentFilter中,可以有多個action。Intent只要能和其中任意一個 action匹配就算是匹配成功。否則,匹配失敗。
category的匹配規則
category也是一個字符串,Android系統同樣爲我們提供了一些預定義的category。當然,我們也可以定義自己的category。category的匹配規則和action有所不同。一個IntentFilter中可以有多個category,並不要求Intent全部匹配。但是,要求Intent中添加的任意一個category,都必須已經包含在IntentFilter中了,否則匹配失敗。
需要注意的是,即使不爲Intent添加category,在調用startActivity
或者startActivityForResult
方法後,Android系統都會爲Intent添加一個預定義的category,即android.intent.category.DEFAULT
。因此,我們需要爲Activity的IntentFilter指定這個category,否則會造成匹配失敗。
data的匹配規則
相比action和category,data的匹配規則要稍微複雜一點。首先,要明白data分爲URI和mimeType兩部分。一個典型的data結構如下所示:
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string">
</data>
android:mimeType
屬性指定了mimeType部分,主要是指定媒體類型。如text/plain、video/*等。URI部分則由剩餘的屬性共同組成。一個URI的結構如下:
scheme://host:port/[path|pathPrefix|pathPattern]
具體的屬性含義如下:
- scheme:URI的協議名,如http、file、content等,必須指定。
- host:URI的主機名,如www.coding.com,必須指定。
- port:URI的端口名,可選。
- path、pathPrefix和pathPattern:共同組成URI的路徑內容。path和pathPattern都表示完整路徑。不同之處在於pathPattern可以指定通配符
*
。pathPrefix則表示路徑前綴。
一個IntentFilter可以包含多個data,Intent只要能夠匹配其中任何一個data就算是匹配成功。對於單個data而言,Intent需要匹配所有內容纔算是匹配成功。
需要注意的是,如果一個data沒有指定URI部分,而只是指定了mimeType部分,這個data也是有默認URI的。URI部分會被Android系統指定爲file和content。例如一個data如下:
<data android:mimeType="text/plain"></data>
如果我們要匹配這個data,就需要在Intent中指定URI爲file或者content才行。如下所示:
Intent intent=new Intent();
intent.setDataAndType(Uri.parse("file://"),"text/plain");
需要注意的是,應該使用setDataAndType
方法爲Intent設置URI和mimeType。因爲如果單獨使用setData
和setType
,它們會相互清除對方設置的值。
一個完整的匹配示例
IntentFilter如下所示:
<intent-filter>
<action android:name="com.codingending.action.test_1"></action>
<action android:name="com.codingending.action.test_2"></action>
<category android:name="com.codingending.category.test_1"></category>
<category android:name="com.codingending.category.test_2"></category>
<category android:name="android.intent.category.DEFAULT"></category>
<data android:mimeType="text/plain"></data>
</intent-filter>
如果想要匹配上述IntentFilter,可以採取下面的方式:
Intent intent=new Intent();
intent.setAction("com.codingending.action.test_1");
intent.addCategory("com.codingending.category.test_1");
intent.setDataAndType(Uri.parse("file://"),"text/plain");
或者:
Intent intent=new Intent();
intent.setAction("com.codingending.action.test_2");
intent.addCategory("com.codingending.category.test_1");
intent.addCategory("com.codingending.category.test_2");
intent.setDataAndType(Uri.parse("file://"),"text/plain");
特殊的IntentFilter
在Android存在一個特殊的IntentFilter,如下所示:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
這個IntentFilter的作用是標識其所在的Activity是應用的入口Activity,即主Activity。這是應用啓動後呈現給用戶的第一個Activity。
最佳實踐
使用隱式啓動方式最大的缺點是可能匹配失敗造成應用出現ForceClose錯誤,這樣會給用戶不好的使用體驗。因此,在使用隱式啓動方式時,最好先查詢一下是否存在能夠匹配的Activity。只有存在能夠匹配的Activity時,才真正地去啓動它。要達到這一目的,可以使用Intent的resolveActivity
方法。或者,也可以使用PackageManager的resolveActivity
或queryIntentActivities
方法。它們的方法原型如下所示:
Intent:
public ComponentName resolveActivity(PackageManager pm);
packageManager:
public abstract ResolveInfo resolveActivity(Intent intent, int flags);
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);
resolveActivity
和queryIntentActivities
的不同之處在於,前者會返回最佳的匹配結果,而後者將返回所有匹配成功的結果。
對於這兩個方法中的flags參數,可以選擇MATCH_DEFAULT_ONLY
或者MATCH_ALL
,一般選擇前者。如果使用MATCH_DEFAULT_ONLY
參數,匹配過程中將會忽略category中不含android.intent.category.DEFAULT
的Activity,因爲這些Activity無法被啓動。原因前文已經說過。
通過判斷這些方法的返回值是否爲null,可以知道是否能夠匹配成功。這樣處理後,可以避免應用出現ForceClose錯誤,也優化了用戶體驗。