Intent

Intent作用

Intent是一个消息传递对象,可使用它来请求操作(同一应用或其他应用均可)。

启动Activity

Intent对象描述了要启动的Activity,并携带了任何必要的数据。

将Intent对象传递给Context#startActivity()便可启动Activity实例。

如果希望在Activity完成后收到结果,就调用Context#startActivityForResult()

启动服务

Intent对象描述了要启动的Service,并携带了任何必要的数据。

将Intent对象传递给Context#startService()便可启动Service实例。

将Intent对象传递给Context#bindService()使组件绑定到指定Service上。

发送广播

调用Context类的sendBroadcast()sendOrderedBroadcast()sendStickyBroadcast()等方法,传入Intent对象,便可发送广播。

Intent类型

显式Intent

显式Intent:按名称(完全限定类名)指定要启动的组件。

使用显式Intent启动组件时,系统会立即启动Intent对象中指定的应用组件。

隐式Intent

隐式Intent:不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。

使用隐式Intent启动组件时,系统会将Intent的内容与设备上所有应用的Manifest中声明的IntentFilter进行比较,从而找到要启动的相应组件。

  • 如果Intent与IntentFilter匹配,则系统将启动该组件,并向其传递Intent对象。

  • 如果多个IntentFilter兼容,则系统会显示一个对话框,由用户决定最终要启动的应用组件。

Intent的组成

Intent是由ComponentName、Action、Data、Category、Extras和Flags组成。

ComponentName、Action、Data和Category表示Intent的既定特征,Android通过这些属性来解析应当启动哪个应用组件,而Extras和Flags是不影响应用组件的解析。

ComponentName

ComponentName为要启动的组件名称。

这是可选项,但也是构建显式Intent的一项重要信息。如果没有组件名,则Intent是隐式的。

可通过以下API来设置Intent的组件名。

  • Intent的构造函数Intent(Context packageContext, Class<?> cls)

  • Intent类的Intent setClass(Context context, Class<?> cls)

  • Intent类的Intent setComponentName(ComponentName component)。ComponentName包含要启动的组件的包名、类等信息。

Action

Action为指定要执行的通用操作的字符串。

可通过以下API来设置Intent的操作。

  • Intent的构造函数Intent (String action)

  • Intent类的Intent setAction(String action)

Data

Data为待操作数据(Uri对象)、数据的MIME类型。

调用Intent#setData(Uri data)设置Uri数据,调用Intent#setType(String type)设置MIME类型,调用Intent#setDataAndType(Uri data, String type)同时设置二者。

注意,setData()setType()会相互抵消彼此的值,应该始终使用setDataAndType()来同时设置它们。

Category

Category表示要启动的组件类型的附加信息的字符串。

调用Intent#addCategory(String category)来添加类别。

Extras

Extras携带完成请求操作所需的附加信息的键值对。

使用各种Intent#putExtra()方法传入键和值来添加extra数据。

也可使用Intent#putExtras()将Bundle传入Intent中。

Flags

Flags充当Intent元数据的标志。

可使用Flags来指示系统如何启动Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的Activity列表)。

调用Intent#addFlags()Intent#setFlags()来设置Intent的标志。

构建Intent

显式Intent示例

要创建显式Intent,需要明确地指定ComponentName,其他属性均为可选项。

Intent intent = new Intent(context, MainActivity.class);
context.startActivity(intent);
1
2

隐式Intent示例

Intent不需要明确指定被启动对象的组件信息,而指定其Action、Category、Data。

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
startActivity(sendIntent);
1
2
3
4
5

如果当前设备上没有任何应用的Activity组件能处理这个startActivity()中的隐式Intent,此调用将会失败,且应用会崩溃。可以使用Intent#resolveActivity()来判断是否有对应的Activity组件能处理该隐式Intent,再安全地调用startActivity()

if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}
1
2
3

应用选择器

如果有多个应用响应隐式Intent,系统会弹出应用选择器,让用户可以选择要使用的应用,并可以将其设置为该操作的默认选项,之后不再显示选择器。

可使用Intent类的static Intent createChooser (Intent target, CharSequence title)来包装隐式Intent,使其每次都强制弹出应用选择器。

Service与隐式Intent

使用隐式Intent启动服务存在安全隐患:

  • 开发者无法确定哪些服务将响应Intent。

  • 用户无法看到哪些服务被启动。

从Android 5.0(API 21)开始,Service必须要采用显式Intent来启动,否则会报错Service Intent must be explicit

Google官方推荐使用为Intent对象设置PackageName和Action的方式来启动Service。

Intent intent = new Intent();
intent.setAction("<Action>");
intent.setPackage("<PackageName>");
context.startService(intent);
1
2
3
4

也可以将隐式Intent转换成显式Intent来启动Service。

public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
    // Retrieve all services that can match the given intent
    PackageManager pm = context.getPackageManager();
    List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
 
    // Make sure only one match was found
    if (resolveInfo == null || resolveInfo.size() != 1) {
        return null;
    }
 
    // Get component info and create ComponentName
    ResolveInfo serviceInfo = resolveInfo.get(0);
    String packageName = serviceInfo.serviceInfo.packageName;
    String className = serviceInfo.serviceInfo.name;
    ComponentName component = new ComponentName(packageName, className);
 
    // Create a new intent. Use the old one for extras and such reuse
    Intent explicitIntent = new Intent(implicitIntent);
    // Set the component to be explicit
    explicitIntent.setComponent(component);
    return explicitIntent;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
final Intent intent = new Intent();
intent.setAction("<Action>");
final Intent eintent = new Intent(createExplicitFromImplicitIntent(context, intent));
context.startService(eintent);
1
2
3
4

IntentFilter

应用中的组件要接收隐式Intent,必须在Manifest中使用<intent-filter>来为该组件声明一个或多个IntentFilter。

每个IntentFilter根据Intent的Action、Category和Data指定应用组件所能接受的Intent。在<intent-filter>中,通过以下三个元素中的一个或多个指定要接受的Intent。

  • <action>:声明接受的Intent操作。

  • <category>:声明接受的Intent类别。

  • <data>:使用一个或多个指定数据URI各个方面(scheme、host、port、path等)和MIME类型的属性,声明接受的数据类型。

显式Intent始终会传递给目标组件,而隐式Intent仅当匹配Intent过滤器成功后,方可传递给目标组件。

IntentFilter的特点:

  • 一个应用组件可有多个IntentFilter。

  • 一个IntentFilter可含有0、一个或多个action。

  • 一个IntentFilter可含有0、一个或多个data。

  • 一个IntentFilter可含有0、一个或多个category。

入口Activity的IntentFilter配置如下:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
1
2
3
4

Intent解析

系统根据Action、Category和Data这三个方面来将Intent和IntentFilter进行比较,从而选择匹配的应用组件。

一个Intent需要同时匹配一个IntentFilter的action、category和data才算匹配成功。

即使一个应用组件有多个IntentFilter,但一个Intent只要成功匹配任意一个IntentFilter即可。

Action测试

只要Intent中的Action与IntentFilter中任一Action相同就可通过测试。

因为IntentFilter可以包含0个action,所以Intent找不到任何匹配项,于是所有Intent都无法匹配这个Filter。

Category测试

Intent中的每个category都要在IntentFilter中找到对应的category,才会通过测试。

系统会自动将CATEGORY_DEFAULT类别应用于传递给startActivity()startActivityForResult()的所有隐式Intent。因此,如需要Activity接收隐式 Intent,则必须将android.intent.category.DEFAULT的类别包括在其IntentFilter中。

Data测试

<data>可指定URI数据和MIME类型。

URI数据包含scheme、host、port和path等属性,一个完整的URI为<scheme>://<host>:<port>/<path>。每个属性都是可选的,但存在线性依赖关系:

  • 如果未指定scheme,则会忽略host。

  • 如果未指定host,则会忽略port。

  • 如果未指定scheme和host,则会忽略path。

Intent中的URI仅与IntentFilter中包含的部分URI进行比较,如:

  • 如果IntentFilter仅指定scheme,则具有相同scheme的所有URI都与它匹配。

  • 如果IntentFilter指定scheme、host和port,但未指定path,则具有相同scheme、host和port的所有URI都与它匹配。

  • 如果IntentFilter指定scheme、host、port和path,则具有相同scheme、host、port和path的所有URI才与它匹配。

Data测试会将Intent中的URI和MIME类型与IntentFilter中的URI和MIME类型进行比较。规则如下:

  • 仅当IntentFilter未指定任何URI或MIME类型时,不含URI和MIME类型的Intent才会通过测试。

  • 对于包含URI但不含MIME类型(既未显式声明,也无法通过URI推断得出)的Intent,仅当其URI与IntentFilter的URI匹配、且IntentFilter同样未指定MIME类型时,才会通过测试。

  • 仅当IntentFilter列出相同的MIME类型且未指定URI格式时,包含MIME类型、但不含URI的 Intent才会通过测试。

  • 如果IntentFilter只声明MIME类型,则假定组件支持content:file:的URI。

Intent匹配

可利用Intent类中的resolve*方法查询满足条件的Activity组件。

  • ComponentName resolveActivity (PackageManager pm)

  • ActivityInfo resolveActivityInfo (PackageManager pm, int flags)

可利用PackageManager类中的query*方法查询满足某隐式Intent的目标组件。其中,参数it表示要匹配的Intent,参数flags表示要匹配的标志位(建议传入PackageManager.MATCH_DEFAULT_ONLY来匹配category为android.intent.category.DEFAULT的目标组件)。

  • List<ResolveInfo> queryIntentActivities(Intent it, int flags)匹配Activity。

  • List<ResolveInfo> queryIntentServices(Intent it, int flags)匹配Service。

  • List<ResolveInfo> queryBroadcastReceivers(Intent it, int flags)匹配BroadcastReceiver。

  • List<ResolveInfo> queryIntentContentProviders(Intent it, int flags)匹配ContentProvider。