Android Intent最全面的解析

有朋友在微信上留言,希望能分享一些Android基础知识。其实没有什么比官方文档更适合学习,但是官网的访问速度慢啊(大家都懂的)!恰巧 刘明渊 投稿了关于Intent的官方译文,这里就推荐给大家。


刘明渊的博客地址:http://blog.csdn.net/vanpersie_9987




Android应用框架鼓励开发者在开发应用时重用组件,本文将阐述如何用组件构建应用程序以及如何用intent将组件联系起来。如需阅读官方原文,请您访问链接:https://developer.android.com/guide/components/index.html


Intent 与 Intent Filters(Intents and Intent Filters)


Intent是一个传递消息的对象,您可以为Intent指定action来启动其他应用组件,Intent使组件之间通信更加便利,并且通信方式有很多,这里列举了主要的三点:


  • 启动Activity:

您可以将intent作为参数调用startActivity()方法启动一个activity。该intent描述了将要启动的目标activity的特性并携带必要的数据信息。您还可以调用startActivityForResult()方法回传信息。


  • 启动Service:

Service用于在后台执行任务,不与用户交互。您可以使用startService()方法执行一次性的操作(比如后在台下载一个文件),这需要Intent参数。   如果组件之间需要向CS结构一样通讯,您可以把Service想成一端,并调用bindService(),这同样需要Intent参数。


  • 传递一个broadcast:

broadcast是一种可以被任何应用程序截获的广播机制。系统会基于当前发生的事件发出各式broadcast(比如设备开机时、开始充电时等),您可以调用sendBroadcast()sendOrderedBroadcast()sendStickyBroadcast()方法发送一条广播,这需要传入Intent参数。


Intent的种类(Intent Types)


  • 显式Intent:

通过指定具体类名启动一个组件。显式Intent一般用于同一应用程序内,因为您可以确定地知道要启动的组件名。另外,Android 5.0以后规定必须显式启动Service


  • 隐式Intent:

当希望启动具备某种特性的组件时,可以使用隐式Intent,隐式Intent无需指定类名,通常用于启动其他应用程序的组件,比如您打算启动一个地图定位的activity。


当您隐式地启动一个service或activity时,Intent会根据其中的内容,匹配其他组件中manifest文件的Intent-filter,启动符合条件的组件,并把Intent中的参数传过去,如果有多个intent-filter满足条件,那么系统会弹出一个对话框,由用户决定启动哪个组件。下面是intent与intent-filters配合启动组件的示意图:

Android Intent最全面的解析
见上图:

1、首先Activity A利用传入的Intent调用startActivity();


2、系统会根据该Intent的条件搜索Android系统中所有匹配的组件;


3、若找到了匹配intent的intent-filters所属的组件(Activity B),则启动该组件,并回调onCreate()方法,同时将Intent传递过去。


intent-filters是manifest文件中组件内部的一个标签,该标签描述了组件具备什么特性,如果您未配置intent-filters,那个该组件只能被显式启动。


创建Intent对象(Building an Intent)


Intent中包含了目标组件需满足的特性。Intent中应包含以下信息:


  • Component name:

目标组件的名字。对于显式启动,这是不可缺省的,您可以使用Intent的构造方法传入组件名称,也可以调用setComponent(), setClass(), setClassName()这些方法传入组件名;若是隐式启动,这是可选的,但intent应包含其他信息(actioncategorydata);


  • Action:

是一个可以指明目标组件行为的字符串。action很大程度上决定了category和data中应传入的信息;您也可以在自己的应用程序组件中指定action,以便让其他应用程序启动自己的组件。对应action中字符串,不建议使用硬编码的形式,而应在所属组件的类中设置为常量。


常见的action有:


ACTION_VIEW:用ACTION _VIEW启动的activity一般可以向用户展示一些信息,比如启动一个相册APP中展示图片的activity,或是启动一个地图APP中展示地址信息的activity。


ACTION_SEND:一般需要向通过ACTION _SEND启动的activity 附带着发送一些信息,这些信息由由目标activity决定该发送给谁,比如社交类APP或是发送邮件的APP。


您可以将action作为参数传入Intent的构造方法或setAction()方法中。


如需定义在自己的组件中定义action,应以应用的包名作为前缀,比如:

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
  • Data:

一个URI对象是一个引用的data的表现形式,或是data的MIME类型;data的类型由Intent的action决定,比如说若action是ACTION_EDIT,那么data的URI应指向一个可编辑的文件。


当创建一个Intent时,除了为data指定URI以外,还应该指定data的MIME类型,比如说,一个用于展示图片的activity是不能用来放音乐的,如果您要启动这个activity,就需要将data的MIME类型指定为"image/png"、"image/jpeg"等。有些时候,从data的URI中就能推断出MIME的类型,比如当一个URI的schema是"content://"时,表明该URI指向了设备内部的一个文件并由ContentProvider管理着,系统可以根据该文件推断出data的MIME类型。


您可以调用setData()方法设置URI,调用setType()方法设置MIME类型,或调用setDataAndType()方法同时设置URI和MIME类型。


请注意:如果您需要同时设置URI和MIME类型,只能调用setDataAndType()方法,而不能分别调用setData()和setType(),因为调用setData()时会首先将setType()中的内容置空,反之亦然( they each nullify the value of the other)


  • Category:

是一个字符串,表示目标组件的附加信息,大部分intent不需要category。以下是依稀而常用的category:


CATEGORY_BROWSABLE:表示目标activity可以被网页上的某个链接启动,如图片activity或e-mail信息activity。


CATEGORY_LAUNCHER:目标activity是任务栈的第一个activity,也就是应用程序的启示activity。


您可以将category参数传入addCategory()方法中


上述的参数(component name, action, data, and category)代表了intent的属性,通过这些参数,系统可以筛选出符合条件的目标组件。除此之外,intent还可以包含下列参数,与上面的参数不同的是,系统不会使用这些参数来筛选目标组件


  • Extras:

一些intent可以携带的附加信息,以键值对的形式存储。可以使用putExtra()方法将键值对信息传入,也可以将键值对信息放在Bundle对象中,再通过将Bundle对象传入putExtra()中。


Intent类中封装了许多 " EXTRA_* "形式的标准extra,如果想封装自己的extra键,请您以应用程序的报名作为前缀,比如:

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
  • Flags:

该参数可以为intent添加元数据(meta-data),flag可以指导系统以何种方式启动一个activity、是否将启动的activity放在该应用的任务栈中,等等。


隐式Intent的例子(Example implicit intent)


请注意:若系统中没有满足隐式Intent的目标组件,则应用将崩溃(crash),所以首先应判断,再调用startActivity()。


以下是一个通过隐式intent启动一个“发送信息的Activity”的例子:

// Create the text message with a string
Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage); sendIntent.setType("text/plain");

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {    startActivity(sendIntent); }

如果有一个目标组件满足intent,则启动该组件;若有多个满足intent的目标组件,则系统弹出一个列表以供选择。


使用应用选择器(Forcing an app chooser)


正如向上面说,系统中可能存在多个目标组件满足隐式intent,这时会弹出一个列表供用户选择,有些时候,用户希望每次都启动一个相同的组件(比如用户每次都想启动chrome浏览器而不是系统自带的浏览器),这时只需要勾选“不再询问”选项就行了,下次再启动时,列表将不再弹出;还有些时候,用户每次都需要对列表中的Activity进行筛选,比如启动用于分享的Activity,用户希望每次分享到不同的平台,这时需要调用Intent.createChooser()方法以保证每次都弹出选择列表,如下所示:

Intent sendIntent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {    startActivity(chooser); }

接收隐式Intent(Receiving an Implicit Intent)


通过在manifest文件中配置intent-filter标签中的action, data, and category,可以设置筛选信息,只有同时符合上述三个标签设置的筛选信息,Intent才能开启您的应用程序组件:


  • action标签:

可匹配Intent中的action参数。


  • data标签:

可匹配Intent中的data参数(URI地址以及MIME 类型)。


  • category标签:

可匹配Intent中的category 参数。

请注意:如组件需要被隐式启动,必须配置CATEGORY_DEFAULT


如想隐式启动一个分享的Activity,则目标Activity如下配置:

<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</
activity>

一个intent-filter中可以包含多个 action, data, category 标签。


若组件仅希望通过本应用启动,可将组件中的exported属性设为false。

请注意:为了避免隐式intent匹配上了您的Service组件,请不要在service中配置intent-filter(Service必须显式启动)


intent-filter举例(Example filters)


下面是一个社交APP的manifest文件示例:

<activity android:name="MainActivity"> 
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</
activity>

<
activity android:name="ShareActivity">    <intent-filter>        <action android:name="android.intent.action.SEND"/>        <category android:name="android.intent.category.DEFAULT"/>        <data android:mimeType="text/plain"/>    </intent-filter>    <intent-filter>        <action android:name="android.intent.action.SEND"/>        <action android:name="android.intent.action.SEND_MULTIPLE"/>        <category android:name="android.intent.category.DEFAULT"/>        <data android:mimeType="application/vnd.google.panorama360+jpg"/>        <data android:mimeType="image/*"/>        <data android:mimeType="video/*"/>    </intent-filter>
</
activity>

action为 “android.intent.action.MAIN”表示该Activity是应用的主入口,且无需配置data。


category为 “android.intent.category.LAUNCHER”表示该activity的启动图标(通过icon属性配置)应添加到系统的launcher中,若未配置icon,则会使用application标签下的icon。

以上两个属性应成对出现。


如需隐式启动ShareActivity,仅需匹配一个intent-filter就行了。


使用Pending Intent(Using a Pending Intent)


PendingIntent是一个包装Intent的类,主要用于实现Intent的延时启动,PendingIntent的主要使用场合:


  • 包装一个Notification的启动Intent;

  • 包装一个App Widget的Intent(按Home键启动的activity);

  • 包装一个延时启动的activity(如AlarmManager)。


使用PendingIntent启动的activity无需使用startActivity()就能启动,您应当使用对应组件的方法启动相应组件:


  • 通过PendingIntent.getActivity()启动一个activity;

  • 通过PendingIntent.getService()启动一个Service;

  • 通过PendingIntent.getBroadcast()启动一个BroadcastReceiver;


解析Intent(Intent Resolution)


目标组件通过以下三点匹配相应的Intent:


  • The intent action;

  • The intent data (both URI and data type);

  • The intent category。


  • 匹配Action(Action test)

intent filter可定义零到多个action标签:

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />  
   ...
</intent-filter>

intent需要匹配上其中一个action标签。如果intent-filter中没有action标签,则intent无需action就能匹配。


  • 匹配Category(Category test)

intent filter可定义零到多个category标签:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

intent中的定义的每一个category都需要匹配上intent-filter中的category标签,反之不成立(intent-filter中的category标签可能比intent中的定义的category多)。所以无论intent-filter中是否定义了category标签,未添加category的intent总能匹配上该intent-filter。

请注意:通过startActivity()或startActivityForResult()方法隐式启动的intent中,将自动被添加一个CATEGORY_DEFAULT的category,所以若您希望自己的activity能够被隐式启动,则需要在intent-filter中添加一个android.intent.category.DEFAULT的category标签。


  • 匹配Data(Data test)

intent filter可定义零到多个data标签:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

每个data标签都能设置mimeType和URI 结构,其中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。


每一部分在data标签中都不是必须定义的,但存在一个线性依赖:


  • 若scheme 未指定,则host被忽略;

  • 若host未指定,则port被忽略;

  • 若scheme和host均未指定,则path被忽略;


在intent中添加的data只需要匹配一部分intent-filter中的data(URI匹配):


  • 若filter只定义了scheme,则intent的data定义的URI中只要包含了相同的scheme,就能匹配;

  • 若filter只定义了scheme和host,则intent的data定义的URI中只要包含了相同的scheme和host,就能匹配;

  • 若filter只定义了scheme、host和port,则intent的data定义的URI中只要包含了相同的scheme、host和port,就能匹配;


intent-filter匹配intent的data中URI和mimeType类型的规则如下:


1. 如果intent-filter中未指定data,则未添加data的intent可以匹配;

2. 如果intent-filter中指定了URI,但未指定mimeType,则按照上一段的规则匹配(intent中也应未指定mimeType);

3. 如果intent-filter中指定了mimeType,而未指定URI,则可以匹配intent中指定了相同mimeType,而未指定URI的组件;

4. 如果intent-filter中同时指定了mimeType和URI,则:

  • intent中添加的mimeType只要能匹配上intent-filter中的某一个mimeType,就能匹配上mimeType部分;

  • intent中添加的URI则按照上一段中的URI匹配规则,就能该匹配上URI部分;特别地:若intent中的URI的scheme 指定为content: 或者 file:,那么即便intent-filter中未定义URI,也能匹配成功。换句话说:包含content: 或者 file: 的URI总是能匹配上只定义了mimeType的intent-filter





如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:


Android Intent最全面的解析