Understanding Intent and IntentFilter--理解Intent和IntentFilter
Intent(意图)在Android中是一个十分重要的组件,它是连接不同应用的桥梁和纽带,也是让组件级复用(Activity和 Service)成为可能的一个重要原因。Intent的使用分为二个方面一个是发出Intent,另一个则是接收Intent用官方的说法就是Intent Resolving。本主将对Intent和IntentFilter进行一些介绍。Intent和IntentFilter是Android和一种消息通信机制,可以让系统的组件之间进行通信。信息的载体就是Intent,它可以是一个要完成的动作请求,也可以一般性的消息广播,它可以由任意一个组件发出。消息,也就是Intent,最终也是要被组件来进行处理和消化。消息由组件发出,通常在消息的里面也会有会标记有目标组件的相关信息,另外目标组件也需要告诉系统,哪些消息是它所感兴趣的,它需要设置一些过滤器,以过滤掉无关的消息。
其实这里就好比学校里的广播,广播有时会播放通知,但有时也会播放要执行的动作,比如打扫卫生。消息中通常都会说明消息的目标对象,可能是计算机学院,可能是老师,也可能是英语系的人才需要关注。而每个人,或是学院组织,也只关心与他们有关的消息,当然这里就要他们自己去判断哪些是与他们有关的消息了。在Android当中消息就是Intent,过滤器就是IntentFilter。发出消息的组件可以在消息中设置目标组件的相关信息,目标组件也可以设置过滤器,然后系统会进行过滤,只把组件所感兴趣的消息,传递给组件。这里假设读者已经了解Intent和IntentFilter的基本使用方法,且并不会进行全面的介绍,如果不了解,可以先读读官方文档,这里重点讲讲IntentFilter在使用时的一些注意事项。
Intent消息机制通常有二种,一个是显式Intent(Explicit Intent),另一个是隐式Intent(Implicit Intent)。
- 显式Intent--需要在Intent中明确指定目标组件,也就是在Intent中明确写明目标组件的名称(Component name),需要指定完整的包名和类名。因为对于本程序以外的其他应用程序,你很难知道它的组件名字,所以显式的Intent通常用于应用程序内部通信,更确切的说,显示Intent是用于应用程序内部启动组件,通常又是Activity或Service。还没有见用显式Intent来给BroadcastReceiver发送广播的。
- 隐式Intent--也就是不在Intent中指定目标组件,在Intent中不能含有目标的名字。系统是根据其他的信息,比如Data,Type和Category去寻找目标组件。
显式Intent(Explicit Intent)
显示Intent使用起来比较简单,只需要在Intent中指定目标组件的名字即可,也就是通过Intent的方法设置Component属性。如前所述,显式Intent通常用于应用程序内部启动Activity或Service。事实上,并不完全局限在应用程序内部,对于外部应用的Activity和Service,也可以用显式Intent来启动,但你必须知道它们的名字。设置组件的名字的方法有:
- publicIntentsetComponent(ComponentNamecomponent);
- publicIntentsetClass(ContextpackageContext,Class<?>cls);
- publicIntentsetClassName(ContextpackageContext,StringclassName);
- publicIntentsetClassName(StringpackageName,StringclassName);
ComponentName Intent.getComponent();
对于应用程序内部启动Activity通常是这样子设置Intent:
- Intenti=newIntent();
- //Selectoneofthem
- i.setComponent(newComponentName(getApplication(),ViewStubDemoActivity.class));
- i.setComponent(newComponentName(getApplication(),ViewStubDemoActivity.class.getName()));
- i.setComponent(newComponentName(getApplication().getPackageName(),ViewStubDemoActivity.class.getName()));
- i.setClass(getApplication(),ViewStubDemoActivity.class);
- i.setClassName(getApplication(),ViewStubDemoActivity.class.getName());
- i.setClassName(getApplication().getPackageName(),ViewStubDemoActivity.class.getName());
- startActivity(i);
但是对于外部应用程序的Activity,通常只能通过以下方法:
- Intenti=newIntent();
- //selectoneofthem
- i.setComponent(newComponentName("com.hilton.networks","com.hilton.networks.WifiManagerActivity"));
- i.setClassName("com.hilton.networks","com.hilton.networks.WifiManagerActivity");
- startActivity(i);
对于Service组件,也是一样,Intent的写法与Activity组件一致,但是对于BroadcastReceiver组件通常都用显式Intent。
隐式Intent的消息过滤器--IntentFilter
IntentFilter是用来解析隐式Intent(Implicit Intent)的,也就是说告诉系统你的组件(Activity, Service, BroadcastReceiver)能够处理哪些隐式的Intent。在使用的时候我们通常是这样子的:- <manifest...>
- <receiver...>
- <intent-filter>
- <actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE"/>
- <actionandroid:name="android.appwidget.action.APPWIDGET_ENABLED"/>
- <actionandroid:name="android.appwidget.action.APPWIDGET_DISABLED"/>
- <actionandroid:name="android.appwidget.action.APPWIDGET_DELETED"/>
- </intent-filter>
- <intent-filter>
- <actionandroid:name="android.intent.action.MEDIA_MOUNTED"/>
- <actionandroid:name="android.intent.action.MEDIA_UNMOUNTED"/>
- <actionandroid:name="android.intent.action.MEDIA_SHARED"/>
- <actionandroid:name="android.intent.action.MEDIA_REMOVED"/>
- <actionandroid:name="android.intent.action.MEDIA_EJECT"/>
- <dataandroid:scheme="file"/>
- </intent-filter>
- <intent-filter>
- <actionandroid:name="android.intent.action.PACKAGE_ADDED"/>
- <actionandroid:name="android.intent.action.PACKAGE_REMOVED"/>
- <actionandroid:name="android.intent.action.PACKAGE_DATA_CLEARED"/>
- <dataandroid:scheme="package"/>
- </intent-filter>
- </receiver>
- </manifest>
1. 千万注意拼写错误
这里有一个需要十分小心和注意的地方那就是对于IntentFilter里面的Action和Data字串常量不要写错,因为这个在编译时是不会被检查,在运行时又不会抛出异常,如果你拼写错了,比如大小写拼错了,在编译时和运行时都不会有错误,但是你的程序却不能正常工作,你的程序无法收到相应的Intent。曾有一个同事在IntentFilter中写了一些Action,但把其中一个的大小写拼错了,结果花了他一个下午的时间来调试,最后还是另外一个同事到他那聊天才发现了是大小写的拼写错误。
这里也可以发现Android在Manifest文件中的IntentFilter这块的封装性很差。如果,仅仅是如果,这些Action常量也可以通过引用的方式来写,就可以在编译时做些检查和匹配,可以大大的减少出错的机率,同时也加强了封装和信息隐藏。比如,对于上面的可以写成这样:
- <manifest...>
- <receiver...>
- <intent-filter>
- <actionandroid:name="@android:action/AppWidgetManager.APPWIDGET_UPDATE"/>
- <actionandroid:name="@android:action/AppWidgetManager.APPWIDGET_ENABLED"/>
- <actionandroid:name="@android:action/AppWidgetManager.APPWIDGET_DISABLED"/>
- <actionandroid:name="@android:action/AppWidgetManager.APPWIDGET_DELETED"/>
- </intent-filter>
- </receiver>
- </manifest>
- <manifest...>
- <receiver...>
- <intent-filter>
- <actionandroid:name="android.intent.action.MEDIA_MOUNTED"/>
- <actionandroid:name="android.intent.action.MEDIA_UNMOUNTED"/>
- <actionandroid:name="android.intent.action.MEDIA_SHARED"/>
- <actionandroid:name="android.intent.action.MEDIA_REMOVED"/>
- <actionandroid:name="android.intent.action.MEDIA_EJECT"/>
- <dataandroid:scheme="file"/>
- </intent-filter>
- <intent-filter>
- <actionandroid:name="android.intent.action.PACKAGE_ADDED"/>
- <actionandroid:name="android.intent.action.PACKAGE_REMOVED"/>
- <actionandroid:name="android.intent.action.PACKAGE_DATA_CLEARED"/>
- <dataandroid:scheme="package"/>
- </intent-filter>
- </receiver>
- </manifest>
Intent,在Intent的文档中并没有说明如何使用,如果没有参考事例,相信需要一定的时间才能够找出为什么接收不到Intent。
除了DataScheme还有一个是MimeType,这个对于系统公共接口是必须加上的,比如Email要处理Intent.ACTION_SENTTO,就需要这样声明:
- <manifest...>
- <activityandroid:name="EmailComposer">
- <intent-filter>
- <actionandroid:name="android.intent.action.SEND"/>
- <dataandroid:mimeType="image/*"/>
- </intent-filter>
- </activity>
- </manifest>
如果没有对IntentFilter写正确的Category字段,也是收不到Intent。比如:
- <manifest...>
- <receiver...>
- <intent-filter>
- <actionandroid:name="com.hilton.controlpanel.action.BUTTON_ACTION"/>
- <categoryandroid:name="com.hilton.controlpanel.category.SELF_MAINTAIN"/>
- </intent-filter>
- </receiver>
- </manifest>
总之,对于Intent,要保证发出和接收完全一致,否则系统就无法找到相应的匹配,程序也就无法接收Intent。
有关于 DEFAULT category,也要注意,如果是针对Activity的Implicit Intent隐式Intent,如果在没有其他Category的情况下,一定要加上DEFAULT Category。因为系统会在Context.startActivity(Intent)和Context.startActivityForResult(
Intent)时给Intent加上DEFAULT category。而对于Context.sendBroadcast(Intent),Context.sendOrderedBroadcast(Intent),Contxt.sendStickyBroadcast(Intent)和Context.bindService(Intent)Context.startService(Intent)就不会加DEFAULT Category。
另外要注意,尽量把Action进行合并写进一个IntentFilter中。因为对于每个IntentFilter标签都会创建一个IntentFilter对象,所以如果写几个就会有几个对象在那,不但耗费资源而且在匹配的时候也会耗费更多的时间,因为在查询匹配的时候是要一个IntentFilter对象接着一个IntentFilter对象进行检查的。直到找到最佳匹配或是到所有的IntentFilter都检查完为止。
IntentFilter的匹配规则
1. 通过Action字段来匹配这个是Intent中比较基本的一个字段,也比较简单,就是一个字串,如果相等就匹配成功,否则证明还没找到目标。但要注意,如果IntentFilter没有指定Action,那么它不会匹配到任何的隐式Intent,它只能被显式的Intent匹配上。反过来,如果Intent自己没有指定Action,那么它能匹配上含有任何Action的IntentFilter,但不能匹配上没有指定Action的IntentFilter。对于Action,平时要注意拼写错误,因为在AndroidManifest文件中声明Action都是字串,并且在编译时不会做检查,运行时,如果Action拼错了导致匹配不上,要么是程序不能正常工作,要么会有异常抛出。
2. 通过Category字段来匹配对于Activity来讲,如果想处理隐式Intent,并且除了Intent.ACTION_MAIN以外,必须指定Category为DEFAULT,否则不会被匹配到。因为Context.startActivity()和Context.startActivityForResult()会自动加上DEFAULT Category。其他情况,Service和BroadcastReceiver则不会,对于Service和BroadcastReceiver,如果Intent中没有指定Category,那么在其IntentFilter中也不必指定。
3. 通过Data字段来匹配这个相对来讲比较复杂,通常Data包含uri, scheme(content, file, http)和type(mimeType)对于Intent来讲有二个方法:
- Intent.setData(Uri);//一个Uri,Scheme包含在其中
- Intent.setType(String);//指定MimeType,比如'image/jpeg','audio/mpeg'等
- Intent.setDataAndType(Uri,String);//上面二个方法的简便调用方式,一起搞进去
- <intent-filter>
- <actionandroid:name="android.intent.action.SEND"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- <dataandroid:scheme="content"android:mimeType="image/*"/>
- </intent-filter>
a.如果Intent没有指定Data相关的字段,只能匹配上没有指定Data的IntentFilter。也就是说如果一个Intent没有指定任何的Data(Uri和Type),它只能匹配到没有指定任何Data(Scheme和Type)的IntentFilter。
b.如果一个Intent只指定了Uri但是没有Type(并且Type也不能够从Uri中分析出)只能匹配到仅指定了相应Scheme且没有指定Type的IntentFilter。实际的例子有如果一个Intent是想要发邮件,或是打电话,它们的Intent是类似这样的:"mailto:someone@sb.c<wbr>om</wbr>"和"tel:1234567"。换句话说,这些Uri本身就是数据,而不再是一个指向数据的地址。比如:Phone中的Dialer就有如下的IntentFilter:
- <intent-filter>
- <actionandroid:name="android.intent.action.CALL"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- <dataandroid:scheme="tel"/>
- </intent-filter>
再如,要处理SD状态变化的IntentFilter:
- <intent-filter>
- <actionandroid:name="android.intent.action.MEDIA_MOUNTED"/>
- <actionandroid:name="android.intent.action.MEDIA_UNMOUNTED"/>
- <actionandroid:name="android.intent.action.MEDIA_SHARED"/>
- <actionandroid:name="android.intent.action.MEDIA_REMOVED"/>
- <actionandroid:name="android.intent.action.MEDIA_EJECT"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- <dataandroid:scheme="file"/>
- </intent-filter>
再如,要处理Package状态变化的IntentFilter:
- <intent-filter>
- <actionandroid:name="android.intent.action.PACKAGE_ADDED"/>
- <actionandroid:name="android.intent.action.PACKAGE_REMOVED"/>
- <actionandroid:name="android.intent.action.PACKAGE_DATA_CLEARED"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- <dataandroid:scheme="package"/>
- <intent-filter>
但是注意,对于想对数据进行操作的Intent,最好不要只指定Uri,而不指定类型。因为如果这样做通常会匹配到一大堆
c. 如果一个Intent只指定了Type,但是没有指定Uri,它只能匹配到只指定了相应Type且没有指定Scheme的IntentFitler
d. 如果一个Intent即有Uri又有Type,那么它会匹配上:1).Uri和Type都匹配的IntentFilter;2).首先Type要匹配,另外如果Intent的Uri是content:或file:,且IntentFilter没有指定Scheme的IntentFilter。因为对于Android来讲content和file这二种Scheme是系统最常见也是用的最多的,所以就当成缺省值来对待。
另外需要注意,Type,因为是MimeType,所以是允许使用通配符的,比如'image/*',能匹配上所有以'image'为开头的类型,也说是说能匹配上所有的图像。
根据Data匹配的例子
假如系统中有四个Activity,A的IntentFilter是这样子的:- <activity...>
- <intent-filter>
- <actionandroid:name="android.intent.action.SEND"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- <dataandroid:scheme="content"android:mimeType="image/*"/>
- </intent-filter>
- </activity>
而另外一个Activity B是这样子声明的:
- <activity...>
- <intent-filter>
- <actionandroid:name="android.intent.action.SEND"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- <dataandroid:scheme="file"android:mimeType="image/*"/>
- </intent-filter>
- </activity>
还有一个C是这样子声明的:
- <activity...>
- <intent-filter>
- <actionandroid:name="android.intent.action.SEND"/>
- <categoryandroid:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- lt;/activity>