【Android】12.1 Intent基本概念

分类:C#、Android、VS2015;

创建日期:2016-02-23

一、简介

Intent:意图,含义就是你想利用它调用哪个组件实现相关的功能,比如调用相机组件实现拍照、调用Contact组件获取联系人信息等。

在Android系统的四个核心组件中,除了Content provider以外,其他三个核心组件(Activity、Services、Broadcast receiver)实际上都是被一个叫做Intent的异步消息来激活的。

通过传递Intent对象调用的这些组件功能时,这些组件可能是安卓系统自身提供的,也可能是你自定义的。

二、Intent对象的组成

Android系统设计的一个独特方面是,任何程序都可以启动其它程序中的组件。例如,如果你想让用户使用相机设备捕捉一个相片,如果有另外一个程序专门做这件事,那么你的程序就可以直接调用它,而不是你自己再去开发一个拍照的activity。换言之,你可以简单地启动相机程序中拍照的activity。当拍照完成,相片就会返回给你的程序供你使用。从用户的角度来看,就好像相机组件就是你程序的一部分。

 

实际上,你在程序中调用一个组件,其实就是让Andoid自动启动这个程序的进程并在启动的进程中实例化这个组件所需要的类。例如,如果你的程序启动了相机程序里的activity去拍照,这个activity实际上是运行在相机程序的进程里,而不是在你自己的程序内去显式创建一个进程去启动它。

你在桌面应用程序中用C#编写代码时,操作系统除了自己去维护所有进程及其访问权限以外,系统另外又专门提供了一个Progress类让你去启动和管理进程,并让你通过参数给Progress对象传递进程需要调用的类的实例和其他信息,至于什么时候启动进程,什么时候停止进程,可以由你自己根据情况去决定。而在Android应用程序中,它仅仅是提供一个StartActivity()方法,让你通过调用该方法去启动另一个活动(一个活动实际上就是一个进程),并让你在该方法的参数中用Intent去传递进程需要的信息,而启动和停止进程(即启动、暂停和停止活动)则由Androd系统根据情况自己去处理(也可以这么理解:为了避免进程管理越来越复杂,它干脆就不给你那么多的控制权了)。另外,桌面应用程序将进程中调用每个实现独立功能的方法称为启动一个线程,而Android则把在活动中调用类中每个实现独立功能的方法称为调用一个组件,这些类有些是系统自己实现的(称为隐式Intent,就是你通过Intent告诉它这个组件是谁就行了,至于调用的是哪个类中的哪个方法你不用管),另外你还可以自定义类(称为显式Intent,就是你必须告诉它调用的是你自定义的哪个类以及类中的哪个方法)。

 

由于Android系统把所有程序都分别运行在每个独立的进程中,并使用权限配置来限制对其它程序的访问,所以你的程序不能从其它程序中直接激活这个组件。或者说,要激活一个其它程序中的组件,你必须向Android系统发送一个消息,这个消息通过你的intent对象来启动指定的组件,然后系统就会为你激活这个组件。

总之,在Android系统中,Intent的作用就是负责把不同的独立的组件在运行期绑定在一起然后让系统根据情况去调用(可以把它们理解为从其它组件中请求动作的消息),无论这些组件是属于你的程序还是属于其它的程序都是如此。

一个Intent对象可包含如下信息:

  • 组件名:要激活的组件
  • 动作:该组件要执行的动作
  • 数据:动作操作的数据
  • 类别:被执行的动作的附加信息
  • 扩展域:被执行的动作的额外附加信息(一系列“键/值”对)
  • 标志:告诉Android如何启动Activity

下面分别介绍这些概念。

1、组件名(ComponentName)

指处理Intent的包含完整的自定义命名空间的组件的名字。

  • Intent调用的组件名所在的命名空间不需要与在manifest文件里设置的命名空间完全匹配。
  • Intent调用的组件名是可选的。换句话说,也可以不给它指定名字,此时Android会自动给它分配一个组件名(使用intent的其它信息来本地化合适的目标)。

(1)在布局文件指定组件名

在布局文件(axml或xml文件)中,有两种方式指定组件名:

第1种方式是用android:name来指定。

第2种方式是用class来指定,此时intent对象会将其分派到指定的类的实例。例如(注意用C#开发时没有必须是小写字母的要求):

<fragment class="MyDemos.SrcDemos.ch1104FragmentTitles"

……

</FrameLayout>

其中,MyDemos.SrcDemos是命名空间,ch1104FragmentTitles是该命名空间下的类名。通过这种方式指定后,它就会自动创建该类的实例,并将intent分派到类的实例中。

(2)在.cs文件中获取和设置组件名

除了在布局文件中设置组件名以外,还可以在C#代码中通过Intent实例的Component属性获取组件的名字。例如:

    Intent intent = new Intent(this,typeof(ch1104FragmentTitles));

    string className = intent.Component.ClassName;

    string packageName = intent.Component.PackageName;

也可以通过调用intent.SetComponent()的办法来指定组件名,不过,一般不去指定它,除非你觉得有必要这么做。这就像你本来有个姓名,而你又觉得有必要再起个笔名一样。

2、动作(Action)

动作(Action)定义了intent的结构,包括数据和扩展域。例如,调用的方法名、参数、返回值等。由于这个原因,使用动作名的好办法是为动作类指定命名空间,以便明确区分不同的动作,不至于让这个动作名和其他的动作名混淆。

Intent类定义了很多动作常量,这些常量都是字符串类型,例如:

    string action = Intent.ActionCall;

    Intent intent = new Intent(action);

可在.cs文件中右击Intent类查看有哪些常量,也可以键入“Intent.”后通过下拉框查看有哪些动作常量,这些常量都是以“Action”开头的。例如:

var call = new Intent(Intent.ActionCall);

下面是一些常用的动作常量(注意:用C#表示这些动作常量时,去掉了分隔单词的下划线,并将每个单词的首字母改为大写,其他字母改为小写,例如Intent.ActionCall,这比都是用大写字母的单词来描述看起来直观多了。

下表列出了一些动作常量及其活动(Activity)描述:

image

下一节介绍“隐式启动Activity”时,我们再列出更多的常量。

3、数据(Data)

动作需要的数据一般用URI和对应的MIME类型来表示。例如:

如果动作域是ActionEdit,则数据需要提供包含所编辑的文档的URI。

如果动作是ActionCall,则数据需要的是要拨出的号码(用tel: URI表示)。

如果动作是ActionView,则数据是目标URI(用http: URI表示)。

实际上,很多时候数据类型都可以通过URI来自动推断,但是content:URI比较特殊,它表示这个数据被本地化到设备上并通过一个内容提供程序来控制。

可通过调用Indent实例的SetData()方法指定Intent的数据,例如:

    var call = new Intent(Intent.ActionCall);

    call.SetData(Android.Net.Uri.Parse("tel:" + translatedNumber));

    StartActivity(call);

4、类别(Category)

类别(Category)是指包含附加信息的字符串(前缀都是Category),信息是指需要处理intent的组件的类型。任何类型的数字描述都可以作为附加的类别放入到intent对象内。

就像对动作定义了一些常量一样,intent类也定义一些类别常量(可在.cs文件中右击Intent类查看有哪些以Category开头的常量)。例如:

image

详细信息可阅读intent类的描述,里面有完整的类别列表。

AddCategory()向Intent实例中添加一个类别,RemoveCategory()删除之前添加的类别,GetCategories()获取当前所有的类别。例如:

    Intent intent = new Intent();

    intent.AddCategory(Intent.CategoryAppBrowser);

5、扩展域(Extras)

利用组件扩展还可以为Intent对象附加一个或多个【“键/值”对】信息。例如,一个ActionTimezoneChanged(即:ACTION_TIMEZONE_CHANGED)的intent有一个time-zone扩展域指明新的时区,ActionHeadsetPlug(即:ACTION_HEADSET_PLUG)有一个state扩展的状态域指明耳机插入或拔出,name也有一个域来指明耳机的类型。

如果你自定义一个动作(比如SHOW_COLOR),那么这个颜色值应该在扩展【“键/值”对】里来设置。

Intent对象提供了一系列的Put...()方法来插入各种类型的数据和一系列Get...()方法来获取各种类型的数据。对Bundle对象来说,这些方法是并行执行的。

另外,还可以使用PutExtras()方法和GetExtras()方法把数据作为Bundle读取和插入。

6、标志

标志告诉Android如何启动Activity(例如:Activity属于哪个任务),启动后如何处理(例如:是否属于当前Activity的列表)。这些标志都是在intent类里定义的。

二、Intent过滤器及其匹配规则

Intent过滤器(IntentFilter类的实例)负责过滤所提供的组件功能。或者说,Android系统在启动组件前,必须知道这个组件具有哪些功能,而组件所具有的功能就是通过intent过滤器来实现的。

安卓解析Intent过滤器时,它将按下面的原则来匹配:

  • 任何不匹配的信息都将被过滤掉。
  • 没有指定动作的过滤器可匹配任何的Intent,但是,没有指定类别的过滤器只能匹配没有类别的Intent。
  • Intent数据Uri的每一部分将与data过滤器中的每个属性(协议、主机名、路径名、MIME类型)都进行匹配,只要有一个不匹配,就会被过滤掉。
  • 如果匹配了多个结果,则先根据<intent-filter>中定义的优先级进行排序,然后再选择优先级最高的那个匹配结果。

总之,Intent过滤器是根据动作(Action)、类别(Category)和数据(Data)来声明的一种过滤筛选机制。Android通过解析Intent过滤器,就可以把不满足条件的组件过滤掉,然后筛选出满足条件的组件,供应用程序调用。

 

从实现原理上来说,Intent过滤器非常类似于ASP.NET MVC中的URL匹配规则,区别仅仅在于:ASP.NET MVC是根据匹配到的URL找到对应的网页;安卓则是根据匹配到的Uri找到对应的组件。

 

1、Intent过滤器的构成

为了通知系统哪个组件过滤器可以处理intent,可通过在配置中同时包含多个intent过滤器的办法来实现。这样就可以让每个过滤器的功能都可以通过组件来区分。

Intent过滤器开启了可以接收隐式intent的组件类型,如果组件没有声明intent过滤器,它仅仅可以接收显式的intent。如果没有显式指明目标组件的intent,此时进程会通过intent过滤器来测试intent对象,并以此来决定潜在的接收者。

通过Intent扩展和Intent标志,可解决无法确定哪个组件接收intent的问题。

Intent过滤器没有可靠的安全性。当它打开一个接收处理显式intent的组件,它并没有阻止隐式intent。尽管一些过滤器限制组件可以处理的动作和数据,但是有时也可以把不同的动作和数据放入一个显式的intent,并把组件作为目标。

intent过滤器从3个方面测试intent对象:

  • 动作(Action)
  • 数据(URI和数据类型)
  • 类别(Category)

每个Intent过滤器都有动作域、数据域和intent对象类别域。对任何一个显式的intent来说,Android都会测试这三个域。只要有一个测试失败,Android系统就不会为这个Intent分配组件。

2、设置Intent过滤器

(1)在清单文件中设置Intent过滤器

Intent过滤器可直接通过Androidmanifest.xml文件来设置。例如,在Androidmanifest.xml文件中,通过android:name属性指定动作过滤:

<intent-filter …… >

    <action android:name="com.example.project.SHOW_CURRENT" />

    <action android:name="com.example.project.SHOW_RECENT" />

    <action android:name="com.example.project.SHOW_PENDING" />

    ……

</intent-filter>

注意,一个intent对象名可能仅仅是一个单独的动作,也可能是一个不能为null的过滤器列表(多个动作)。

类别过滤也是通过android:name属性来指定的,例如:

<intent-filter . . . >

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

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

    . . .

</intent-filter>

数据过滤和动作、类别相似。子元素可以多次出现,也可以不出现。例如:

<intent-filter . . . >

    <data android:mimeType="video/mpeg" android:scheme="http" . . . />

    <data android:mimeType="audio/mpeg" android:scheme="http" . . . />

    . . .

</intent-filter>

每个<data> 元素可以指定一个URI和一个数据类型(MIME类型)。构成URI的每个部分是通过各种属性(scheme、host、port、path)来组合的:

scheme://host:port/path

例如:

content://com.example.project:200/folder/subfolder/etc

这个URI方案是content,主机是com.example.project,端口是200,路径是folder/subfolder/etc。主机和端口构成URI的authority;如果没有指定主机则忽略端口。

<data>元素的type 属性指定数据的MIME类型。一般在过滤器里的情况比在URI里多。Itnent对象和过滤器可以使用*来表示子域——例如:text/*或audio/*——表示匹配任何子域。

<data>元素可以告诉Android系统组件从内容提供者里获取哪些类型的数据,例如获取图片数据并且显示出来:

<data android:mimeType="image/*" />

另一个通常的配置项是有方案和数据类型的过滤器。例如:用<data>告诉Android系统组件可以从网络上获取视频数据并且显示:

<data android:scheme="http" android:type="video/*" />

(2)在C#代码中指定过滤器

在C#代码中,可通过特性声明来指定过滤器,这比直接在清单文件中去指定Intent过滤器要方便得多。

对于显式Intent,通过属性指定过滤器即可,例如:

    var imageIntent = new Intent ();

    imageIntent.SetType ("image/*");

    imageIntent.SetAction (Intent.ActionGetContent);

对于隐式Intent,可通过特性声明来指定过滤器,例如:

[Activity(Label = "…")]

[IntentFilter(new string[] { "...", "..." }, DataMimeType = "image/*")]

public class MyActivity : Activity

{

    //……

}

其中,IntentFilter方法的第1个参数用于指定一个或多个Action。

不过,在C#编程中一般没必要去专门声明它,这是因为编译器自己会根据情况处理,并自动将过滤器声明写入到清单文件中,这要比你自己去声明智能多了。

三、如何用Intent激活组件

用Intent激活各种类型的组件有下面的几种方式:

  • 通过Context.StartActivity()或者Activity.StartActivityForResult()启动一个Activity, 后者适用于希望让activity返回一个结果的情况。
  • 通过Context.StartService()启动一个服务,或者通过Context.BindService()和后台服务进行交互。
  • 通过广播将Intent发给broadcast receivers。例如调用SendBroadcast()方法、 SendOrderedBroadcast()方法,或者SendStickyBroadcast()方法。
  • 通过调用ContentResolver的query()方法让content provider执行一个查询操作。

更多关于intent的信息,可查阅 Intent和Intent Filters 文档。

1、Activity

对于activity来说,intent对象的作用就是定义将要执行的动作(action),并指定动作执行需要的数据URL(启动组件所需的其它数据)。例如,通过intent发送一个请求,让一个activity去显示一张图片等。

下面的代码演示了如何调用StartActivity()方法传递intent对象,并用它调用拨号组件(传递的动作是Intent.ActionCall,动作执行需要的数据URL就是要拨出的电话号码):

    var call = new Intent(Intent.ActionCall); //初始化一个电话呼叫

    call.SetData(Android.Net.Uri.Parse("tel:" + translatedNumber));

    StartActivity(call);

Intent的动作是通过获取或设置Intent类的静态Action属性来激活的。

还记得吧,在第2章“初识Android App“的例子中,已经演示过这段代码的完整用法了。

在另一些情况下,你还可以通过调用StartActivity()方法启动一个activity并通过intent来接收这个activity返回的结果(后面再通过获取联系人程序说明它的具体用法)。

2、Service

对于service(服务)来说,Intent的动作也是通过获取或设置Intent类的静态Action属性来激活的。此时intent对象的作用就是在调用组件和目标服务之间建立一个联系。如果一个service没有运行,它会自动启动它。或者说,intent对象的作用就是定义将要执行的服务(例如,查看或发送什么),并指定服务执行需要的数据URL(启动组件所需的其它数据)。例如,通过intent发送一个请求,让一个activity去打开一个网页等。

在后面的章节中,我们再单独学习Service。

3、Broadcast Receiver

对于broadcast receiver来说,intent只是简单地定义了需要广播的内容。例如,一个指明设备电池电量低(ActionBatteryLow)的广播可以仅包含一个字符串:“电量低”。

intent可以传递给所有感兴趣的广播接收者。很多广播源都保存在系统内核里了,你可以调用下面的方法发送广播消息:Context.SendBroadcast()、Context.SendOrderedBroadcast()、 Context.SendStickyBroadcast()。

例如,调用StartActivity()传递Intent对象实现广播功能,那么,Android系统接收广播时会自动寻找合适的Activity或者Service或设置广播接收器来响应intent,并在需要的时候实例化它们。但是,StartActivity()仅仅把这个intent分派给广播接收器,而不会直接分派给某个Activity或Service。

在后面的章节中,我们再单独学习Broadcast Receiver。

4、Content Provider

另外一个组件类型是Content Provider,注意它不是用intent来激活,而是通过Activity对象的ContentResolver属性来激活的。

content resolver掌握了所有content provider的直接事务(事务就是要么全做,要么都不做,但绝不可能只做其中的一部分),所以用这个provider来执行事务的组件不需要直接执行而是通过ContentResolver属性来控制。为了安全,它在content provider和这个组件请求信息之间放置了一个抽象层。

在后面的章节中,我们再单独学习Content Provider。

OK,Intent的基本概念也就是这些了。

posted @ 2016-02-23 07:05  rainmj  阅读(1531)  评论(0编辑  收藏  举报