IntentFilter匹配规则

本文详细介绍了Android中IntentFilter的匹配规则,包括Action、Category和Data的测试。在隐式Intent启动Activity时,Intent需要与AndroidManifest.xml中声明的Intent-Filter匹配。Action必须匹配,Category至少匹配一个,Data部分则涉及URI和MIME类型的比较。通过示例代码展示了如何创建和匹配Intent,强调了在使用隐式Intent时确保有符合条件的Activity,否则可能导致应用崩溃。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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: or file: URI and the filter does not specify a URI. In other words, a component is presumed to support content: and file: 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

GitHub:https://github.com/cxcbupt/FullscreenDemo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值