前述
Android中的三大核心组件:Activity,Service,Broacast Receive他们各自之间是通过Intent来进行彼此联系、触发的,Intent是种被动的数据结构,它启动三大组件的机制是:
Activity:Context.startActivity()和Activity.startActivityForResult(),(Activity.setResult()可以通过Intent把信息传递给调用了startActivityForResult()方法的activity)。
Service:Context.startService()可以通过Intent初始化一个service或者传递指令给一个已经工作的service。相似的,Context.bindService()可以通过Intent在发起呼叫的组件和目标service之间建立连接。Intent可以任意的初始化还没有运行的service
Broacast Receive:Context.sendBroadcast(),Context.sendOrderedBroadcast()或Context.sendStickyBroadcast(),可以通过Intent把消息传递给Broadcast Receiver。很多广播事件发起于系统级别的代码。
startActivity()只是把Intent传递给activity,而不是service或是broadcast receiver,以此类推。
Intent对象的组成
组件名---Component name
组件名字中所包含的包名的部分不需要必须与manifest文件中的包名相匹配,如:当package="info.kagoy.contentprovider",组件名可以是:x.xx.xxx.XActivity。
组件名字可以通过setComponent(),setClass()或setClassName()来设置,通过getComponent()来读取。
如下:
Intent intent = new Intent();
ComponentName com = new ComponentName(
"Your package name(Set it in the AdroidManifest file)",
"Your Activity name(x.xx.xx.XActivity)");
intent.setComponent(com);
startActivity(intent);
eg:
ComponentName cpName = new ComponentName(
"info.kagoy.contentprovider",
"info.kagoy.contentprovider.OtherActivity");
Intent intent = new Intent();
intent.setComponent(cpName);
startActivity(intent);
Log.d("huang", "MainActivity.this.getComponentName()="
+ MainActivity.this.getComponentName()
+ "||getIntent().getComponent()" + getIntent().getComponent());
打出的值是一样的。
动作---Action
一个命名了将要被执行的动作的字符串,或在广播intents事件中,已经发生并被报告的动作。Intent类定义了许多动作常量,可以通过查看
Intent类的定义查看一系列的代表一般行为动作的常量。通常我们可以自己定义动作的常量串,一个格式如:x.xx.xxx.XXX_XXX。我们用
setAction()来设置Intent中的action,并用
getAction()来读取。
如:
Intent intent = new Intent(MainActivity.this,
OtherActivity.class);
intent.setAction("info.kagoy.intent.OtherActivity");
startActivity(intent);
在Intent的构造方法中没有指定参数,或者没有通过设置组件名方式指定的话,会出现 android.content.ActivityNotFoundException: No Activity found to handle Intent { act=haha cat=[android.intent.category.DEFAULT] }错误。出现此错误是,这是一种隐式的Intent格式,也就是说,你正在使用的Intent的隐式的,而非显示的,对于显示的Intent设不设置过滤器都一样,一样通过。你在使用过滤器的过程中在<intent-filter>中没有设置:<category android:name="android.intent.category.DEFAULT" />,因为在,startActivity(intent);//默认调用intent.addCategory("android.intent.category.DEFAULT"),所以在清单文件中必须加上。(与<intent-filter><action android:name="xxx" /></intent-filter>关系???当之是设置这句话时,使用getAction得到的是null。因为这里不是设置Action,而是一个过滤器中的一条门而已,需要在Java代码中设置一把钥匙,也就是setAction方法,这两个值必须一样,才会匹配。以上描述,其实是隐式的Intent,假如使用的是显示的Intent,无论在过滤器中设置了什么,一切都是浮云,一样执行。)
数据---Data
数据运行的URI和其MIME类型,当为某个组件匹配一个可以处理数据的Intent的时候,通常除了要了解Data的URI以外,重要的是要知道Data的类型(MIME type),例如,一个可以展示图片的组件不应该被调用来播放音频。Data的类型可以从URI中推测出来,特别是URI所展示的内容:指出了Data被用在什么位置及被哪种content provider控制(参考separate discussion on content providers)。但是Data的类型也可以在Intent中明确的设定。setData()方法设置Data的URI,setType()设置Data的类型(MIME type),setDataAndType()两者一起设置,getData()读取URI,getType()读取类型。
分类---Category
Category是这样一个String:他包含了需要处理Intent的组件的种类的信息,很多Category的描述能够放在Intent里,就像Action那样,Intent也定义了一些Category常量,Intent可以查看全部Category的列表。addCategory()方法可以把一个Category放入到Intent中,removeCategory()可以删除之前加入的Category,getCategories()可以得到目前在Intent中所有的Category。
Extras
Extras是传递给目标组件的键值对信息,Intent有一系列的put..()方法用以向Extras中插入各种类型的值,并且也有一系列的get..()方法来取出数值。可以采用,intent.putExtra和intent.getXXXExtra分别插入和读取;还有将数据用Bundle对象封装,如:
插入数据,
Bundle data = new Bundle();
data.putXXX(key, value);
intent.putExtras(data);
Bundle data = intent.getExtras();
data.getXXX(key);
标志位---Flags
通知Android System如何运行一个activity(例如某个activity应该属于哪个任务)和运行以后如何处理(例如,flag是否属于当前活动activity)。所有这些flag都是在Intent中定义的。
Intent解析
Intent的两种形式
显示形式:
指定一个目标组件通过其name( Component name field), 由于组件名称通常不会被其它应用程序的开发者知道。所以,显示意图通常用在应用程序内部消息。如:一个Activity 启动一个从属的service或者启动另一个activity。
隐式形式:
不指定目标组件名称(component name 是空的)隐式意图通常用于去激活其它应用程序的组件。
Android 传递了一个显示意图给一个被指定的目标类的实例,被传递的 intent object 只是定义了component name ,它决定了将会有那个组件去处理这个intent。
针对隐式意图需要不同的策略。在缺乏一个被指定的target的情况下,android系统必须找到最适合的组件去处理这个intent ,一个单一的activity 或者 service 去执行一个请求动作或者一组broadcase receiver 去响应广播通知。
它通过将intent 对象中的内容 和 意图过滤器(intent filters)进行比较,android系统根据intent filter打开可以接收intent的组件,如果一个组件没有intent filter, 那么它只能接受显式intent,如果有, 则能同时接受二者。
当一个intent和intent过滤器进行比较时只会考虑以下三方面:
action
data (both URI and data type)
category
Intent过滤器---Intent filters
显性的intent总是被传递给指定的目标,无论过滤器里设置了什么条件。每个过滤器描述了组件的一个能力也限定一些组件可以接收的intent.实际上过滤器是筛进组件想要的intent,而不是筛出不需要的intent--仅仅是不需要的隐含的intent。
一个Intent过滤器就是IntentFilter类的一个实例。然而,由于Android System必须在加载组件之前知道这个组件的功能,所以Intent过滤器通常不在java代码里设置,而是在AndroidManifest.xml利用<intent-filter>来设置。(一个例外可能就是broadcast receivers的过滤器,他们是通过Context.registerRecerver()动态注册的,他们作为InterFilter对象被直接创建)。
一个Intent过滤器的安全性不可靠。当它打开一个组件准备接收确定类型的隐含intents时,它并不能阻止目标组件中的显性intents。即使一个过滤器阻止了一个组件被要求处理的确定动作和数据源,别人依然可以使用不同的动作和数据源绑定到一个显示的intent上,并将其命名为与目标组件相同的名字。
在Intent中filter与action,data,category是平行关系。一个implicit intent要想通过intent filter必须要在action,data,category三个方面通过校验。如果其中一个没有通过,Android System就不会把intent传递给组件。然而,由于一个组件拥有很多的intent过滤器,所以只要通过其中一个就可以了。
显式意图我们前面已经提到,形如:
Intent intent = new Intent();
intent.setClass(this,Other.class);//此句表示显式意图,因为明确设置激活对象为Other类,也即设置好了setComponent(),setClass()或setClassName()
startActivity(intent);
在使用隐式的Intent,每个过滤器必须要在加上<category android:name="android.intent.category.DEFAULT" />。1、Action
<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-filter>
<action android:name="haha" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
当一个Intent对象只命名了一个动作,过滤器可能会列举多个,列表不能为空;一个过滤器至少需要包含一个<action>元素,否则它不会匹配任何intents。
为了通过这个测试,Intent对象中所指定的动作必须与过滤器中所列举的动作之一匹配。如果这个对象或过滤器没有指定一个动作,其结果如下:如果过滤器没有列举任何动作,那么就没有动作能够用来与intent相匹配,所以,所有的intents都不能通过测试,没有intents可以通过过滤器。
2、category
<intent-filter . . . >
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
. . .
</intent-filter>
因此,从理论上来说,不考虑过滤器中的值,一个没有分类的intent对象应该始终能通过这个测试。大部分情况下是对的。然而,有一个例外,Android把所有传递
给startActivity()的隐性intents都看作是至少有一个分类:"android.intent.category.DEFAULT"(即CATEGORY_DEFAULT常量)。所以,想要接收隐性
intent对象的活动必须在intent过滤器中包含"android.intent.category.DEFAULT"(过滤器中含有"android.intent.action.MAIN"和
"android.intent.category.LAUNCHER"设定的为异常。它们标记了活动开始新任何,并显示在启动屏幕上。它们可以在分类列表中包括
"android.intent.category.DEFAULT",但不需要这么做。)
3、Data
如同动作与分类,intent过滤器中的数据指定也包含在了一个子元素当中。并且,在这种情况下,子元素可以多次出现,或不出现。例如:
<intent-filter . . . >
<data android:mimeType="video/mpeg" android:scheme="http" . . . />
<data android:mimeType="audio/mpeg" android:scheme="http" . . . />
. . .
</intent-filter>每一个<data>元素可以指定一个URI和一个数据类型(MIME媒体类型)。有多个属性——scheme,host,port和port——组成了URI的每一个部分:
scheme://host:port/path。例如,在以下的URI中,content://com.example.project:200/folder/subfolder/etc
其中,scheme为"content",host为"com.example.project",port是"200",path为"folder/subfolder/etc"。host与port一起,组成了URI权限,
如果没有指定host,那么port也将被无视。
这些属性都是可选的,但它们之间并不相互独立:为了有意义的授权,scheme必须被指定。为了使路径(path)有意义,scheme和权限(authority)都必须被指定。
当Intent对象中的URI与过虑器中指定的URI对比时,只对比在过滤器中提及的部分。例如,如果过滤器只指定了数据类型,所有拥有这个scheme的URIs都能匹配。
如果过滤器指定了一个scheme和权限(authority),但没有路径(path),所有拥有相同scheme和权限(authority)的URIs将匹配,不考虑其路径(path)。
如果过滤器指定了scheme,权限(authority)和路径(path),那么,只有在URIs相同的这三个属性时才匹配。然而,过滤器中的路径指定可以包含通用符,
来要求路径的部分匹配。
<data>元素中的type属性指定了数据的MIME类型。在过滤器中,它比URI更常见。Intent对象和过滤器都可以使用"*"通用符作为子类型域——如,"text/*"或
"audio/*"——指定任意一个匹配的子类型。
数据测试同时对比Intent对象中和过滤器中所指定的URI和数据类型。规则如下:
a.一个既不包含URI又没有指定数据类型的Intent对象只有在过滤器同样什么都没指定的情况下才能通过测试。
b.任意一个只包含了URI但没有数据类型(并且无法从URI中推断出其类型)只有在这个URI与过滤器中的一个URI相匹配,并且同样没有指定数据类型的情况下通过测
试。当URI为mailto:和tel:时即为这种情况,并不能确切的知道数据类开。
c.Intent对象中包含了一个数据类型但没有URI时,只有在过滤器列举了相同的数据类型,并且类似有没有指定URI时通过测试。
d.Intent对象同时包含了一个URI和一个数据类型(如数据类型可以由URI推断出)只有在该类型与过滤器中所列类型匹配时才能通过测试。它将通过URI部分的测试,
要么URI与过滤器的中某个匹配,要么其包含了一个content:或file:URI并且过滤器没有指定一个URI。换句话说,如果过滤器中听指定了数据类型,那么一个组件
默认的支持span style="color:green">content:</span>和file:数据。
如果一个intent可以通过多个活动和服务的过滤器,那么用户将需要选择激活哪个组件。如果找不到目标,刚会抛出一个异常。
eg 1:
- <data
- android:host="www.kagoy.com"
- android:scheme="kagoy"/>
- intent.setData(Uri.parse("kagoy://www.kagoy.com/xia"));
eg 2:
android:host="www.kagoy.com"
android:scheme="kagoy" android:mimeType="text/*"/>intent.setDataAndType(Uri.parse("kagoy://www.kagoy.com/xia"), "text/plain"); //匹配了text/*
4、一般案例
在上一个Data test提到的最后一条(d),表达出一种期望:组件可以从文件或content provider中获得数据。因此,他们的过滤器只需列出数据的类型而不需要给 content: schemes和 file: schemes明确命名。下面是一个经典的例子。一个 <data>元素,告诉Android,组件可以从content provider获得图片数据并且显示:<data android:mimeType="image/*" />因为大多数可用的数据是由content provider配与的,过滤器只指定数据类型而不指定URI可能成为最常见的。
另一种通用的配置是过滤器指定scheme和数据类型。例如,一个<data>元素,告诉Android,组件可以从网络获得视频数据并显示:
<data android:scheme="http" android:type="video/*" />
5、intent匹配
Intents与intent filters匹配,不仅仅是为了找到激活的目标组件,而且也为了找到关于设备上的组件的相关信息。例如,Android系统填充应用程序启动器,顶层屏幕显示了用户可以启动的应用程序,通过寻找所有在intent filters中指定了"android.intent.action.MAIN"动作和"android.intent.category.LAUNCHER"分类(如前文中所展示的)的活动。然后将这些活动的图标和标签显示在启动器中。相似地,通过寻找filter中的"android.intent.category.HOME"来发现主屏幕上的应用。