1、Intent types
Intent有两种类型,显式Intent和隐式Intent。
使用显式Intent启动的目标组件特别明确,而隐式Intent则需要能够匹配组件在AndroidManifest.xml中声明的<intent-filter>信息。
2、Intent Filter
当通过隐式Intent启动Activity时,该Intent需要同时匹配Activity在AndroidManifest.xml中声明的<intent-filter>中的<action>、<data>和<category>信息,否则匹配失败。(必须是<action>、<data>、<category>三者都匹配才算匹配成功)
2-1、Action test
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.VIEW" />
...
</intent-filter>
<intent-filter>中可以声明0或者多个<action>元素。
Intent中的Action必须能与<intent-filter>中声明的<action>之一匹配。(匹配是指<action>字符串相等,区分大小写。)
如果Intent-Filter中未声明任何<action>,则任何Intent都将匹配失败。但是,如果Intent中未声明Action,则该Intent可以匹配有<action>的Intent-Filter(只要有<action>声明即可,无论声明的是什么)。
2-2、Category test
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
...
</intent-filter>
<intent-filter>中可以声明0或者多个<category>元素。
Intent中的每一个Category必须能与<intent-filter>中的某一<category>匹配。如果Intent中没有Category,则总成是匹配成功(不管<intent-filter>中声明了什么<category>--未声明任何<category>或声明有<category>)。
注意:Android对于传入startActivity()和startActivityForResult()的隐式Intent会自动加入CATEGORY_DEFAULT category,所以如果某个Activity要想能够响应隐式Intent,则必须要在<intent-filter>中声明“android.intent.category.DEFAULT” cateogry。
2-3、Data test
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ... />
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
...
</intent-filter>
<intent-filter>中可以声明0或者多个<data>元素。
每个<data>元素由URI和mimeType两部分组成。URI由scheme,host,port和path属性组成。
<scheme>://<host>:<port>/<path>
content://com.example.project:200/folder/subfolder/etc
scheme:content
host:com.example.project
port:200
path:folder/subfolder/etc
URI中的这些属性都是可选的,但是存在线性依赖关系:
- If a scheme is not specified, the host is ignored.
- If a host is not specified, the port is ignored.
- If both the scheme and host are not specified, the path is ignored.
当Intent中的URI与<intent-filter>中的URI匹配时,仅比较<intent-filter>URI中声明的属性部分,如果Intent中的URI属性能够与<intent-filter>中声明的属性都匹配,则认为匹配成功,如:
- If a filter specifies only a scheme, all URIs with that scheme match the filter.
- If a filter specifies a scheme and an authority but no path, all URIs with the same scheme and authority pass the filter, regardless of their paths.
- If a filter specifies a scheme, an authority, and a path, only URIs with the same scheme, authority, and path pass the filter.
Note: A path specification can contain a wildcard asterisk (*) to require only a partial match of the path name.
注意:path中可以包含通配符(*),以部分匹配路径名(path name)。
The data test compares both the URI and the MIME type in the intent to a URI and MIME type specified in the filter. The rules are as follows:
- An intent that contains neither a URI nor a MIME type passes the test only if the filter does not specify any URIs or MIME types.
- An intent that contains a URI but no MIME type (neither explicit nor inferable from the URI) passes the test only if its URI matches the filter's URI format and the filter likewise does not specify a MIME type.
- An intent that contains a MIME type but not a URI passes the test only if the filter lists the same MIME type and does not specify a URI format.
- An intent that contains both a URI and a MIME type (either explicit or inferable from the URI) passes the MIME type part of the test only if that type matches a type listed in the filter. It passes the URI part of the test either if its URI matches a URI in the filter or if it has a
content:
orfile:
URI and the filter does not specify a URI. In other words, a component is presumed to supportcontent:
andfile:
data if its filter lists only a MIME type.
This last rule, rule (4), reflects the expectation that components are able to get local data from a file or content provider. Therefore, their filters can list just a data type and don't need to explicitly name the content:
and file:
schemes. The following example shows a typical case in which a <data>
element tells Android that the component can get image data from a content provider and display it:
<intent-filter>
<data android:mimeType="image/*" />
...
</intent-filter>
Filters that specify a data type but not a URI are perhaps the most common because most available data is dispensed by content providers.
3、Intent matching
The PackageManager
has a set of query...()
methods that return all components that can accept a particular intent and a similar series of resolve...()
methods that determine the best component to respond to an intent. For example,queryIntentActivities()
returns a list of all activities that can perform the intent passed as an argument, and queryIntentServices()
returns a similar list of services. Neither method activates the components; they just list the ones that can respond. There's a similar method, queryBroadcastReceivers()
, for broadcast receivers.
以介绍queryIntentActivities()
方法为例,声明如下:
/**
* Retrieve all activities that can be performed for the given intent.
*
* @param intent The desired intent as per resolveActivity().
* @param flags Additional option flags to modify the data returned. The
* most important is {@link #MATCH_DEFAULT_ONLY}, to limit the
* resolution to only those activities that support the
* {@link android.content.Intent#CATEGORY_DEFAULT}. Or, set
* {@link #MATCH_ALL} to prevent any filtering of the results.
* @return Returns a List of ResolveInfo objects containing one entry for
* each matching activity, ordered from best to worst. In other
* words, the first item is what would be returned by
* {@link #resolveActivity}. If there are no matching activities, an
* empty list is returned.
*/
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,
@ResolveInfoFlags int flags);
3-1、新建两个Activity(仅仅设置了一个布局,每个布局包含一个TextView)
public class IntentFilterATestActivity extends AppCompatActivity {
private static final String TAG = "IntentFilterATestActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_filter_a_test);
}
}
public class IntentFilterBTestActivity extends AppCompatActivity {
private static final String TAG = "IntentFilterBTestActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_filter_b_test);
}
}
3-2、在AndroidManifest.xml文件中声明
<activity
android:name=".intentFilter.IntentFilterATestActivity"
android:label="@string/intent_filter_test_a">
<intent-filter>
<action android:name="com.example.cxc.intentFilter.testA" />
<!--Note:此处未声明android.intent.category.DEFAULT-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
</intent-filter>
</activity>
<activity
android:name=".intentFilter.IntentFilterBTestActivity"
android:label="@string/intent_filter_test_b">
<intent-filter>
<action android:name="com.example.cxc.intentFilter.testA" />
<!--Note:声明android.intent.category.DEFAULT-->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
3-3、构建一个隐式Intent,action为“com.example.cxc.intentFilter.testA”,调用queryIntentActivities()
方法,并打印出结果:
private void onQueryIntentBtnClick() {
Log.d(TAG, "-->onQueryIntentBtnClick()--");
Intent testAIntent = new Intent();
testAIntent.setAction("com.example.cxc.intentFilter.testA");
List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(testAIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfoList != null) {
for (ResolveInfo info : resolveInfoList) {
Log.d(TAG, "-->info:" + info);
}
}
}
Log如下:
12-01 20:44:00.136 20800-20800/com.example.cxc.fullscreendemo D/MainActivity: -->onQueryIntentBtnClick()--
12-01 20:44:00.140 20800-20800/com.example.cxc.fullscreendemo D/MainActivity: -->info:ResolveInfo{415fc78 com.example.cxc.fullscreendemo/.intentFilter.IntentFilterBTestActivity m=0x108000}
可以看出仅有IntentFilterBTestActivity可以成功匹配,而IntentFilterATestActivity未能成功匹配。
3-4、修改queryIntentActivities(Intent intent, @ResolveInfoFlags int flags) 第二个参数的值为 MATCH_ALL,两个Activity均可以匹配成功。
private void onQueryIntentBtnClick() {
Log.d(TAG, "-->onQueryIntentBtnClick()--");
Intent testAIntent = new Intent();
testAIntent.setAction("com.example.cxc.intentFilter.testA");
// List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(testAIntent, PackageManager.MATCH_DEFAULT_ONLY);
List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(testAIntent, PackageManager.MATCH_ALL);
if (resolveInfoList != null) {
for (ResolveInfo info : resolveInfoList) {
Log.d(TAG, "-->info:" + info);
}
}
}
12-01 20:50:54.468 21069-21069/com.example.cxc.fullscreendemo D/MainActivity: -->onQueryIntentBtnClick()--
12-01 20:50:54.472 21069-21069/com.example.cxc.fullscreendemo D/MainActivity: -->info:ResolveInfo{415fc78 com.example.cxc.fullscreendemo/.intentFilter.IntentFilterBTestActivity m=0x108000}
12-01 20:50:54.473 21069-21069/com.example.cxc.fullscreendemo D/MainActivity: -->info:ResolveInfo{d193f51 com.example.cxc.fullscreendemo/.intentFilter.IntentFilterATestActivity m=0x108000}
3-5、利用隐式Intent启动Activity,设置action为"com.example.cxc.intentFilter.testA",发现直接启动了IntentFilterBTestActivity。IntentFilterATestActivity不能匹配是因为未声明
<category android:name="android.intent.category.DEFAULT" />
如2-2中所述:Android对于传入startActivity()和startActivityForResult()的隐式Intent会自动加入CATEGORY_DEFAULT category,所以如果某个Activity要想能够响应隐式Intent,则必须要在<intent-filter>中声明“android.intent.category.DEFAULT” cateogry。
private void onStartActivityBtnClick() {
Log.d(TAG, "-->onStartActivityBtnClick()--");
Intent testAIntent = new Intent();
testAIntent.setAction("com.example.cxc.intentFilter.testA");
startActivity(testAIntent);
}
如果删除IntentFilterBTestActivity后,再点击启动Activity,App会Crash ,Log如下:
12-01 21:05:54.084 21721-21721/com.example.cxc.fullscreendemo D/MainActivity: -->onStartActivityBtnClick()--
12-01 21:05:54.094 21721-21721/com.example.cxc.fullscreendemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.cxc.fullscreendemo, PID: 21721
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.cxc.intentFilter.testA }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1981)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1636)
at android.app.Activity.startActivityForResult(Activity.java:4751)
at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:65)
at android.app.Activity.startActivityForResult(Activity.java:4691)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:711)
at android.app.Activity.startActivity(Activity.java:5112)
at android.app.Activity.startActivity(Activity.java:5080)
at com.example.cxc.fullscreendemo.MainActivity.onStartActivityBtnClick(MainActivity.java:119)
at com.example.cxc.fullscreendemo.MainActivity.lambda$onCreate$5(MainActivity.java:50)
at com.example.cxc.fullscreendemo.-$$Lambda$MainActivity$g05uWFutla-jKz0xN25UbjJeWwE.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:6291)
at android.view.View$PerformClick.run(View.java:24931)
at android.os.Handler.handleCallback(Handler.java:808)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7425)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
12-01 21:05:54.100 1272-15087/? W/ActivityManager: Force finishing activity com.example.cxc.fullscreendemo/.MainActivity
所以,使用隐式Intent启动Activity时,最好利用resolveActivity()判断一下系统中是否有满足条件的Activity,以防Crash:
private void onStartActivityBtnClick() {
Log.d(TAG, "-->onStartActivityBtnClick()--");
Intent testAIntent = new Intent();
testAIntent.setAction("com.example.cxc.intentFilter.testA");
if (getPackageManager().resolveActivity(testAIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
startActivity(testAIntent);
}
}
如果两个Activity 都满足条件,则系统会弹窗让用户选择启动哪个Activity :
示例中仅仅对<action>的匹配规则进行了简单的实验,<category>和<data>的匹配规则可以在此基础上进行实验。
Reference:https://developer.android.com/guide/components/intents-filters