Intents 和 Intent Filters
(Intent译为意图,让人比较费解,实际上解释为“消息”更加合理,干脆就不翻译了)
Intent是能在app组件间传递的消息体,基本使用方式有如下三种:
- 启动activity
- startActivity:intent描述需要启动的activity和必须的数据
- startActivityForResult:intent启动的activity结束后,会触发onActivityResult回调
- 启动服务
- startService:intent描述需要启动的service和必须的数据
- bindService
- 发送广播
- 通过Intent来的
sendBroadcast()
,sendOrderedBroadcast()
, orsendStickyBroadcast()
来发送广播
- 通过Intent来的
Intent类型
- 显式intents:通过指定包名+类名来明确需要启动的组件,一般用在app内部使用。
- 隐式intents:不指定具体的组件,通过定义一些动作或者条件,由系统来匹配能够执行动作或者满足条件的组件。
- A创建intent,作为入参startActivity
- Android系统搜索所有app的intent filter用于适配A发出的intent。(如果有多个匹配上,会弹框给用户选择)
- Android系统通过onCreate启动匹配上的B,并把A的intent当做入参
Caution: 为了确保你的app是安全的,通常用显式intent的方式来启动service,因为用隐式intent启动服务是有安全风险的,隐式intent无法预知启动的service就是你想要的那个。
创建一个Intent
Intent主要包含如下属性:
组件名
需要启动的组件名,一般指包名+类名:com.example.ExampleActivity
对于显式intent是必选的,对于隐式intent,不能指定。
Action(动作?)
指定执行一般动作的字符串,比如:
/** * Activity Action: 给用户显示数据。比如:在联系人中显示具体 * 联系人的信息;在邮件发件人中,显示可用的发件人列表。 * <p>Input: {@link #getData} is URI from which to retrieve data. * <p>Output: nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_VIEW = "android.intent.action.VIEW";
你可以自定义Action,一般优先使用Intent类中默认支持的Action。
在Intent构造函数或者通过setAction指定:
public Intent setAction(String action) { mAction = action != null ? action.intern() : null; return this; }public Intent(String action) { setAction(action); }
Data(数据)
URI一般是具体MIME类型的数据,由action来执行。比如:action是ACTION_EDIT,那么数据一般是可编辑文档的URI。
为了让系统能够更精确地适配到你发出来的Intent,在指定Data的同时最好把Type也指定。当你的数据以content:开头时,系统可以判断出具体类型。
public Intent setType(String type) { mData = null; mType = type; return this; }public Intent setData(Uri data) { mData = data; mType = null; return this; }public Intent setDataAndType(Uri data, String type) { mData = data; mType = type; return this; }上述代码说明type和data如果需要同时指定,请使用setDataAndType,否则不论是setType还是setData都会将另一个设置为null。
Category(类别)
任意数量的category可以放在一个intent中,但是大多数intents不需要category。
常用的如下:
- CATEGORY_BROWSABLE:activity可以通用web浏览器来显示链接索引,比如图片或者emai信息。
- CATEGORY_LAUNCHER:activity是此应用初始启动的activity,并且会在launcher中显示。
public Intent addCategory(String category) { if (mCategories == null) { mCategories = new ArraySet<String>(); } mCategories.add(category.intern()); return this; }
系统可以通过componet name、action、data、category这四个属性来匹配具体启动的组件,然而intent还可以设置更多不影响组件匹配的属性,如下介绍的Extras和Flags:
Extras(额外信息)
public Intent putExtra(String name, 类型 value) { if (mExtras == null) { mExtras = new Bundle(); } mExtras.put类型(name, value); return this; }
其中类型可以是:boolean、byte、char,short、int、long、float、double、String、CharSequence、Parcelable +上述类型的数组(类型[ ]) + Serializable、Bundle、IBinder
public Intent putExtras(Bundle extras) { if (mExtras == null) { mExtras = new Bundle(); } mExtras.putAll(extras); return this; } public Intent putExtras(Intent src) { if (src.mExtras != null) { if (mExtras == null) { mExtras = new Bundle(src.mExtras); } else { mExtras.putAll(src.mExtras); } } return this; }比如,当创建发送email的intent,可以用
EXTRA_EMAIL
来指定收件人,用EXTRA_SUBJECT
来指定邮件主题。可以自定义Extra_*的常量。
Flags(标记)
flags可以通知Android系统怎么启动一个activity(比如,activity的所属task)和如何对待启动后的activity(比如,activity是否属于最近activities启动列表)
public Intent setFlags(int flags) { mFlags = flags; return this; }
显式intent例子
// 在Activity中执行,this表示当前Context // fileUrl是个URL字串,比如"http://www.example.com/image.png" Intent downloadIntent = new Intent(this, DownloadService.class); downloadIntent.setData(Uri.parse(fileUrl)); startService(downloadIntent);
隐式intent例子
Caution: 隐式intent通过startActivity时,有可能系统没有任何apps可以处理你发出的intent,这会导致你的app crash,为了确保系统中至少有一个app能够处理你发出的intent,请发送intent之前先用resolveActivity进行判断。
// 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); }
应用程序选择器
当有多个app可以处理你发出的隐式intent,系统会弹框给用户选择,用户可以选择一个并且将其设为默认项。
然后如果多个app可以处理你发出的隐式intent,并且用户想每次都选择不同的app来处理,有就必须强制显示选择框给用户选择。这种选择框每次都会询问用户使用哪个app,并且没法将其中一个设为默认。比较常见的例子:通过ACTION_SEND来分享数据,用户通常根据使用场景选择不同的app进行分享。
Intent intent = 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 chooser Intent chooser = Intent.createChooser(intent, title); // Verify the intent will resolve to at least one activity if (intent.resolveActivity(getPackageManager()) != null) { startActivity(chooser); }
接收隐式intent
在manifest文件中定义<intent-filter>可以指定app能够处理的隐式intent,当然显式intent的话只匹配组件名,其他的都直接忽略。
一组filter定义一个app能够支撑的能力
<action>
<data>
<category>
:对于activity,filter必须包含CATEGORY_DEFAULT,系统在startActivity和startActivityForResult时只会处理带CATEGORY_DEFAULT类型的activities。<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>
系统对
<action>
,<data>
,<category>
三个属性都会进行匹配,缺一不可。用filter并不是一种安全的方式来保护你的组件不被其他apps启动,何况开发者可用通过包名+类名来直接显式intent启动你的组件,如果你的组件只能由同一个app里面的其他组件来启动,那么请将exported属性设置为false
Caution:还是那句话,服务请显式启动。
Note:对于广播来说,可以在代码中动态注册和去注册,运行时指定时间内有效的广播可以用此方法。
过滤器例子
<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>
使用Pending Intent
需要预定好,但是有触发条件之后才能执行的intent,常用场景:
- Notification:Android中点击每个下拉栏的通知,可以跳转到其他界面,这个通知中包含的用户点击后才执行的动作就是pendingintent,在NotificationManager中执行这个intent
- App Widget:用于预先定义widget上具体需要执行的动作,比如按Music Widget上的播放按钮,来播放音乐同时刷新widget视图,滚动显示歌词,这个响应按键动作的intent就是pendingintent
- AlarmManager:指定时间之后执行的intent
创建方式:
Intent匹配测试
Action测试
可匹配场景:
- intent包含的action在filter定义的action列表中
- intent如果没有action,filter包含至少一个action
filter没有定义action,必定匹配失败
Category测试
可匹配场景:
- filter中的category至少包含所有intent中的category
- intent如果没有category
Data测试
每个<data>可以指定一个URI结构:<scheme>://<host>:<port>/<path>
结构中每个属性都是可选项,但是有具体关系:
- scheme不匹配,host直接忽略
- host不匹配,port直接忽略
- scheme和host都不匹配,path直接忽略
intent中URI和filter中URI比较时,只匹配filter中的URI
- filter只定义scheme,所有scheme的URIs都匹配
- filter定义scheme和authority,但是没有path,那么同样scheme和authority能匹配上,而不管path
- filter定义scheme、authority、path,全匹配
Note:path可用通配符*来适配
intent中URI和MIME type,与filter中URI和MIME type匹配规则:
- intent定义URI或者MIME,而filter都不定义,匹配上。
- intent定义URI,但没有定义MIME(没有定义,也不能从URI中识别出MIME),filter定义能匹配上的URI并且没有定义MIME,匹配上
- intent定义MIME,但没有定义URI,filer定义相同的MIME,并且没有指定URI
- intent定义MIME(可从URI中识别出MIME也算)和URI,filter定义的MIME必须包含intent定义的MIME,同时URI必须匹配上,有一种情况例外:intent定义的URI是以content:和file:开头的,那么filter只需要能匹配MIME,就算匹配上。
Intent匹配
intent匹配不仅仅用于启动组件,还可以有其他用途,比如Launcher通过匹配规格来查找需要在桌面显示的所有图标(activities必须包含
ACTION_MAIN
和CATEGORY_LAUNCHER
category)packageManager有一系列query…()和resolve…()方法来列出可适配对应intent的组件集合。