App Components : Intents and Intent Filters (译)

API Guides:http://developer.android.com/guide/components/intents-filters.html

Intent是一种消息组件,用来从一个app component向另外一个app component请求一个动作。在系统内部Intent的传递流程如下:

虽然在components中有着灵活变化的通信方式,但有3种最基本的用法:

  1. 启动activity
  2. 启动service
  3. 发布broadcast

Intent的类型主要有两种:

  • 显式的Intent(Explicit intents):显式Intent会通过名字(完整的类名)指定要启动的组建。因为在在自己的程序中,我们知道具体的要启动的activity或者service的类名,所以一般程序内部会使用这种方式来启动。
  • 隐式的Intent(Implicit intents):不会指定具体的要启动的类名,但会定义一种相对通用的action请求以允许其他的程序来处理。

如果使用显示的Intent的话,那么系统会立刻启用Intent指定的系统组件;但如果使用隐式的Intent,Android系统会从系统中查找程序的manifest文件中定义的与intent filters相匹配的intent。如果系统找到一个,那么就会启动相应的component,并传入相应的Intent;如果同时匹配了多个intent,系统会弹出一个dialog,让用户选择一个执行。

Intent filter是在应用程序的manifest文件中的一段表达式,用来表示该系统组件所接收消息的类型。比如说,可以我们可以为activity声明一个intent-filter来允许其他的应用程序直接调用该的activity。因此,如果说一个activity没有设置任何的intent-filter,那么该acitivy只会接收显示intent.

注意:为了保证我们的应用程序的安全,要使用显式intent来启动Service,并且不要为Service声明任何的intent-filter对象。如果我们使用隐士intent来启动service的话是无法保证其安全性的,因为我们无法确定service该如何相应这个intent,而且用户也无法知道哪个service被启动了。从Android5.0(API 21)开始,如果我们通过隐士的intent来调用bindService()的时候,系统会抛出一个异常。

构建一个Intent

Intent包含了必要的信息来让Android系统判断需要启动哪个系统组件(比如一个准确的系统组件的name或者是组件的category),还有一个额外的信息用来保证启动的系统组件可以优雅的处理好接收到的请求(比如action或者data)。

 

Intent一般包含以下的信息:

  • Component name
    • 要启动的组件名称,该项可选,但如果需要使用显式Intent除外。如果没有Component name,那么该intent就是一个隐式intent,由系统来根据附加在该intent上的其他信息,比如action,data,category等,决定哪个系统组件来接收这个intent。如果所以,如果说我们需要在应用程序内部启动特定的系统组件,那么我们就应该指定Component name。
    • 注意:当启动Service的时候,我们必须总是指定component name。要不然的话,我们无法确定会由哪个Service来处理这个intent,并且用户这不会知道哪个Service被启动了。
    • Intent中的该部分内容是一个ComponentName对象,我们可以使用完整名称的类名(包括包名)来制定目标组件。比如:com.example.ExampleActivity。我们可以通过 setComponent(), setClass(),setClassName(), 或者 Intent的构造器来设置组件名称。
  • Action
    • 一个用于指定所要执行的通用动作的字符串(比如view或者pick)。在broadcast的使用情景中,这对应着正在发生并且需要报告的action。Action很大部分定下了intent后面的结构是如何构成的,尤其是在data和extras中包含了什么样的信息。
    • 我们可以在我们的应用程序内部指定我们自己的intents需要使用的actions(或者供其他的程序调用我们应用程序内部的系统组件),但是我们一般情况下还是使用Intent类或者其他的framework类定义的Action常量。这里有一些常用的用来启动activity的actions:
      • ACTION_VIEW:如果我们要启动一应用程序来展示信息,比如要展示一个照片可以启动gallery应用程序,或者在map的应用程序中展示地址信息。
      • ACTION_SEND:还有另外一个名称“share” intent,我们如果有一些消息要在应用程序之间共享。
      • 可以通过查看Intent类来查看共多的通用actions的常量。其他的一些actions可能在Android框架层的其他部分定义,比如Settings中定义了一些启动Settings中特定界面的actions。
    • 我们可以使用setAction()或者Intent的构造函数来指定action。
    • 如果我们要定义我们自己的action,那么一定不要忘记包含自己的包名作为前缀,比如:
      static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
  • Data:
    • 我们所需要处理的数据的MIME格式所对应的URI(一个Uri对象)。数据的类型由intent中的Action属性确定。比如如果一个action是 ACTION_EDIT,那么数据应该包含我们要编辑的文档的URI。
    • 当创建一个intent的时候,除了指定URI之外,指定数据的格式(MIME类型)也是特别重要的。比如,即使URI格式是一样的,但一个能够显示照片的程序,并不能播放一段媒体音乐。所以,如果为我们的数据指定MIME类型,就能够帮助Android系统更好的找到接收处理该Intent的系统组件。然后,有的时候,MIME的类型是从URI中继承来的-尤其是当数据是content:URI,这意味着数据是保存在设备当中,并且由ContentProvider控制的,这时候数据的MIME类型系统是可以探测到的。
    • 如果仅仅需要设置数据的URI,调用setData()。仅仅设置MIME类型,调用setType(),如果两者都要设定,调用setDataAndType()
      • 注意:如果我们要同时设置URI和MIME类型的话,不要调用setData()以及setType(),因此调用setData()或者setType()的时候会将另一个设置为null。这个时候一定要调用setDataAndType().
  • Category:
    • 一个包含了额外的其他用来指定处理此intent的系统组件类型的字符串。一个intent可以包含任意数量的itnent。但大多数intent并不需要包含一个category。这里展示一些常用的CATEGORY。
      • CATEGORY_BROWSABLE:目标Activity允许被web浏览器调用来通过一个链接,比如image或者e-mail来展示数据。
      • CATEGORY_LAUNCHER:该Activity为应用程序的初始化activity,并且会在系统的Launcher中展示出来。
      • 其他的可以参考Intent的类来看下其他的categories。
      • 我们可以通过addCategory()来添加Category。

上面所描述的属性,包括Component name,Action,data(Uri,MIME type),Category定义了一个intent的典型特征。通过上面的信息,Android系统能够找到要启动的系统组件。

然而,一个intent可以携带的信息并不仅仅只有上面所列出的,还有其他的信息,但并不会对查找对应的component产生影响。

  • Extras:Key-Value对。可以承载一些额外的信息,比如data URIs。
    • 通过putExtra()方法来添加。或者也可以创建一个Bundle对象来包含这些信息,然后把这个Bundle放进Intent中去。
    • Intent类为标准数据类型指定了一些经常使用的EXTRA_*常量。但我们可以为自己的程序制定声明自己的数据。没用过
      static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

       

  • Flags:可以用来展示如何启动一个activity(比如activity应该输入哪个task),对应的activity启动之后,如何处理(比如是否要让其在最近的activities中显示)。详细的可以参考setFlag()方法。

如何使用显示Intent

使用显示的Intent,必须要指定Component Name,其他的属性都可以不设置,比如:

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

如何使用隐式Intent

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

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

强制显示应用程序选择器

有的时候,我们需要应用程序必须弹出应用程序的选择界面来供用户选择,可以按照下面的方法:

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.createChoose(Intent,String)的方法包装了原有的Inent。

接收隐式Intent

如果需要接收一个隐式的Intent,那么需要在程序的menifest file中配置对应的<intent-filter>,每个intent-filter都需要指定接收的intent的action,data,category。如果一个Intent的数据与我们的intent-filter相匹配的话,那么系统就会调用相应的Intent
系统会根据此配置将其发送到相应的位置。
注意,显式intent不会关心intent-filter中设置的内容,因为其本身已经制定Component Name,不需要额外的信息来查找要启动的系统组件。
每一个系统组件都应该指定与其所能做的工作相对应的intent-filter。比如,在gallery的一个activity可能会有两个filters:一个用来查看图片,一个用来编辑图片。当该activity启动的时候,会根据intent的内容来决定要如何处理。
每一个intent filter都是通过应用程序中的manifest中,对应的系统组件之内的<intent-filter>元素来定义的。在其中我们可以为其指定action元素,或者更多。
  • <action>:定义了intent接收到 action类型,在name属性中。该属性必须是一个action字符串,而不是类的常量。
  • <data>:定义了接收的数据类型,使用一个或者更多的属性来识别data URI的不同片段,比如scheme,host,port,path以及MIME类型。
  • <category>:定义了intent接收的category类型,在name属性中。同action一样,比如为字符串。
    • 需要注意的是:为了能够使得隐式Intent能够通过 startActivity() and startActivityForResult() 启动组件,比如要在其中的cagegory中添加CATEGORTY_DEFAULT,否则的话,该组件不会被系统识别。
<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>, or <category>,但如果我们这样做的话,只有在这里面的信息全部匹配的时候,对应的component才会被系统调用。

如果我们我们需要处理多种类型的intents,但仅仅是指定的action,data,category组合,那么我们需要创建多个intent-filter。

一个隐式的inent要看和filter是否匹配,会一次匹配上面的3个元素。

注意:为了避免不小心运行了其他的应用程序的Service,我们始终要记住使用显式Intent启动Service,并不要为Service设置任何intent-filter。

笔记:对于activities,intent-filter必须在manifest文件中声明。但broadcast receivers所使用的intent-filter可以通过调用 registerReceiver()unregisterReceiver()来实现。

限制component的访问权限

使用intent filter并不是一个安全的方法来避免其他的应用程序来启动我们的系统组件。虽然intent filters限制了一个系统组件仅仅会相应设置的一种或者几种对应的隐式intent,但其他的应用程序如果开发者知道你的系统组件的component names的话,仍然可以启动。因此为了保证仅仅我们自己的程序能够启动相应的组件,我们应该为相应的component设置exported标签为false。

例子

<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <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>

 

Using a Pending Intent

PendingIntent是对Intent的封装。使用PendingIntent的主要目的之一在于保证其他的应用程序能够获得对应的权限,就好像这个intent是从我们自己的程序中自己的进程中调用的一样。
PendingIntent主要的使用于:
  • Notification中,用户点击Notification,然后系统的NotificationManager运行这个Intent
  • App Widget。当用户点击桌面上的小控件的时候,桌面程序执行的Intent。
  • Alarm。以后在某个特定时间去执行的Intent,比如Android的AlarmManager在固定时间去执行的这个Intent。
由于在系统中,大量的PendingIntent在设计的时候其目的是相同的,因此必须为了不同的PendingIntent中的不同的组件类型设置不同的PendingIntent。有以下3中调用。
根据API Guide的文档,除非应用程序是在接受来自其他的app的PendingIntents,那么以上的方法应该是使用的所有的方法。

Intent Resolution

注意下data的识别原则:
每个intent-filter可以包含0个或者多个data元素,比如说:
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ... />
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
...
</intent-filter>
每一个<data>元素都可以确定一个URI结构和数据类型(MIME media type)。有几个分开的属性,scheme,host,port,path。
<scheme>://<host>:<port>/<path>
content://com.example.project:200/folder/subfolder/etc
每一个属性都是可选的,但是遵循以下的线性规则:
  • 如果scheme没有确定,那么host会被忽略。
  • 如果host没有确定,那么port会被忽略。
  • 如果scheme和host都没有被设定,path会被忽略。
在对URI验证的时候,是按照以下的思路进行:
  • 如果filter仅仅确定了scheme,那么所有有用同样的scheme的URIs会被match
  • 如果确定了scheme和authority,但是没有path;那么所有的URIs会比对schme和authority,忽略path
  • 如果scheme,authority,path都确定了,那么只有所有的全部一样的时候才会被确定。
Note: A path specification can contain a wildcard asterisk (*) to require only a partial match of the path name.
如果在intent-filter中同时设定了URI和MIME类型,那么会按照以下的规则判断:
  1. 如果一个intent既不包含URI,也不包含MIME,那么仅仅会通过没有指定URI或者MIME的filter。
  2. 如果一个intent包含URI,但不包含MIME(既不单独指定,也无法从URI中获取),那么仅仅会通过指定了URI并且match,而MIME又没有配置的filter。
  3. 如果一个intent包含了MIME类型,但是不包含URI,那么仅仅会通过包含了MIME并且match,而且不包含URI的filter
  4. 如果一个intent即包含URI,也包含MIME(可以单独设定,也可以是从URI中获取),那么MIME部分会判断是不是符合,对于URI部分,如果URI match 或者 在filter不识别URI的时候,URI包含content:或者file:,那么会认为match。换句话说,一个filter仅支持MIME类型的时候,组件是默认支持content:以及file:的。

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注