隐式Intent启动Activity
前言
我们在开发一个app时,最常用到的是通过Intent设置组件类名,然后启动相应的Activity。这种通过为Intent指定组件名称启动Activity的方式,我们称之为显示Intent启动Activity。举个例子:
Intent explicitIntent = new Intent();
//通过类启动(一般用于同一个应用)
explicitIntent.setClass(context, MainActivity.class);
//通过ComponentName类启动(一般用于同一组织开发的同一个或不同应用)
explicitIntent.setComponent(new ComponentName(("com.thoughtworks.demo","com.thoughtworks.demo.MainActivity"));
//通过包名+完整类名启动(ComponentName类启动的一种简写调用)
explicitIntent.setClassName("com.thoughtworks.demo","com.thoughtworks.demo.MainActivity");
startActivity(explicitIntent);
因为显示Intent启动Activity需要知道具体的类名包名,所以常用于同一个应用内,或者同一个组织开发的不同应用间。
正文
在了解了显示Intent启动后,我们来说下今天的主题,隐式Intent启动Activity。
什么是隐式Intent启动Activity?
通过设置Intent的action、category和data来启动Actvity的方式就是隐式Intent启动。举个例子:
Intent implicitIntent = new Intent();
implicitIntent.setAction("com.thoughtworks.demo.itent.action.EDIT_IMAGE");
implicitIntent.setData(Uri.parse(" content://com.thoughtworks.provider.userprovider/user/1?image=avatar"));
startActivity(implicitIntent);
隐式Intent启动的是哪个Activity?
具体此Intent能启动哪个Activity,需要看Activity在AndroidManifest.xml中的配置。下面给出一个配置,来具体分析隐式Intent启动Activity。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:host="com.thoughtworks.provider.userprovider" />
<data android:pathPattern="/user/.*" />
</intent-filter>
</activity>
要隐式Itent启动MainActivity必须满足如下条件:
-
Intent-filter内必须至少包含一个category,android:name为android.intent.category.DEFAULT。
-
Intent-filter内必须至少包含一个action。
intent内是否需要setAction根据setData中Uri的scheme决定(如果Uri的scheme为http, https, content等,这时候不指定intent的action,系统或者其他应用会尝试启动自己可以匹配的组件),为了能准确启动需要的组件,请在intent中调用setAction。
所以,只要一个Activity的Manifest配置满足上面2个条件,那么这个Activity一定是可以被隐式启动的。至于怎么启动,这就需要结合Intent内设置的action、category、data和AndroidManifest文件中Activity的intent-filter的匹配规则决定了。
是不是感觉好麻烦?要不你也不会看到这篇文章,下来我就说下intent-filter的匹配规则,其实很简单。
intent-filter的作用和特点
看名字就知道intent-filter的作用当然就是用来过滤intent的了,本质目的就是为了确定哪个intent可以和当前的组件匹配,如果匹配就启动它。所以intent-filter的作用就是为了隐式启动组件。
而intent-filter的特点有3个,如下:
-
一个组件内可以有多组intent-filter标签。
-
只要intent内的信息和任意一组intent-filter匹配,那么就可以启动这个组件。
-
一组intent-filter内可以多个action、category、data。
一般情况不会将多个action、category、data同时写在一组intent-filter标签内,而是会根据具体action,拆分为多组intent-filter。
intent-filter的匹配
还是举个例子:
intent信息:
Intent implicitIntent = new Intent();
implicitIntent.setAction("com.thoughtworks.demo.itent.action.EDIT_IMAGE");
//implicitIntent.setAction("com.thoughtworks.demo.itent.action.SHOW_IMAGE");
startActivity(implicitIntent);
intent-filter信息:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.SHOW_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
implicitIntent只能有一个action(看名字就是知道setAction嘛),这个action不管是SHOW_IMAGE还是EDIT_IMAGE都会启动MainActivity。
下面我们修改下intent-filter信息:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.SHOW_IMAGE" />
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
效果和之前的intent-filter完全一样。如果MainActivity只能负责show和edit用户的图片。我们要怎么办?我们再修改下intent-filter信息:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.SHOW_IMAGE" />
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:host="com.thoughtworks.provider.userprovider" />
<data android:pathPattern="/user/.*" />
</intent-filter>
</activity>
所以要启动MainActivity,intent信息如下:
Intent implicitIntent = new Intent();
implicitIntent.setAction("com.thoughtworks.demo.itent.action.EDIT_IMAGE");
//implicitIntent.setAction("com.thoughtworks.demo.itent.action.SHOW_IMAGE");
implicitIntent.setData(Uri.parse(" content://com.thoughtworks.provider.userprovider/user/1?image=avatar"));
startActivity(implicitIntent);
在MainActivity中通过getData获取contentProvider的Uri地址查询user信息,在通过Uri中的query获取avatar图片完成show或edit操作。
如果intent中的data不能和intent-filter中data的规则匹配,是无法启动组件的。
如果需求在复杂一点,MainActivity不仅仅可以对ContentProvider中user的图片做处理,还可以对指定域名下的网络图片做处理。我们要怎么办?我们继续修改intent-filter信息:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.SHOW_IMAGE" />
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:host="com.thoughtworks.provider.userprovider" />
<data android:pathPattern="/user/.*" />
<data android:scheme="http" />
<data android:host="lovemumu.cn" />
<data android:pathPattern="/assets/images/hero/.*\\.jpg" />
</intent-filter>
</activity>
intent信息为:
Intent implicitIntent = new Intent();
implicitIntent.setAction("com.thoughtworks.demo.itent.action.EDIT_IMAGE");
implicitIntent.setData(Uri.parse("http://lovemumu.cn/assets/images/hero/Geirangerfjord-Norway.jpg"));
startActivity(implicitIntent);
上面的intent一样可以启动MainActivity。But!!有一个问题,如果我们把intent中的data设置为:
implicitIntent.setData(Uri.parse("content://lovemumu.cn/user/1?image=avatar"));
会发生什么?我操,既然也打开了MainActivity。这显然不是我们想要的。所以此时我们应该拆分intent-filter,代码如下:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.SHOW_IMAGE" />
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:host="lovemumu.cn" />
<data android:pathPattern="/assets/images/hero/.*\\.jpg" />
</intent-filter>
<intent-filter>
<action android:name="com.thoughtworks.demo.intent.action.SHOW_IMAGE" />
<action android:name="com.thoughtworks.demo.intent.action.EDIT_IMAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:host="lovemumu.cn" />
<data android:pathPattern="/assets/images/hero/.*\\.jpg" />
</intent-filter>
</activity>
说了这么多,大家有木有发现,我们仅仅只提到过一次category,即:Intent-filter内必须至少包含一个category,android:name为android.intent.category.DEFAULT。
呢到底category重要吗?看下官网的解释:
A string containing additional information about the kind of component that should handle the intent. Any number of category descriptions can be placed in an intent,but most intents do not require a category.
大多数情况是不需要catagory的。好吧,看来用的时候不多。但是我们还是要简单说说它。
category在intent-filter内可以配置多个。如果intent内有category,那么这些categories都必须包含在intent-filter内,否则无法隐式启动组件。如果intent内没有任何category,只要其他规则匹配(action,data),就可以启动组件。
intent-filter匹配规则总结
以下规则中intent-filter都必须包含<category android:name="android.intent.category.DEFAULT" />
。
- 如果intent-filter内只有action(单个或多个),只要intent内的action和intent-filter内任意一个action匹配,即可启动组件。
- 如果intent-filter内有action和category(单个或多个),满足规则1的条件下,只要intent内的categories都属于intent-filter,即可启动组件。
- 如果intent-filter内有action、category、data(单个或多个),满足规则1,2的条件下,只要intent内的data和intent-filter内的规则匹配,即可启动组件。
关于action、category、data的理解
action:对于intent,action表示你期望完成什么操作。而对于组件,intent-filter内的action表示你的组件具有什么样的能力。例如:如果你的activity的intent-filter配置了android.intent.action.SEND的action,表示你的activity具有处理SEND的能力。SEND的是什么,可以根据data的mineType指定。而Inent的setAction设置了android.intent.action.SEND,表示你希望去SEND一个消息,具体是发送什么消息(文本,图片,音视频),通过intent的setType指定。而谁会去真的帮你SEND,那就要看哪些应用的组件配置了对应的action和data。其实android.intent.action.SEND是系统的action。很多第三方应用想要共享自己应用的能力,他们可以通过公开文档的形式告诉你action,但是大多数是直接使用系统的action。举个例子:
intent信息为:
Intent implicitIntent = new Intent();
implicitIntent.setAction("android.intent.action.SEND");
implicitIntent.putExtra(Intent.EXTRA_TEXT, "I am Twer~");
implicitIntent.setType("text/plain");
startActivity(implicitIntent);
intent-filter信息:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<data android:mimeType="text/plain" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
此时系统会弹出应用或组件选择器,因为很多应用的组件都匹配到了你intent。例如微信会帮你SEND信息到好友或者群聊在或者朋友圈,邮件会帮你SEND信息到发送页面,百度会帮你SEND到搜索结果页面,等等。
category:虽然说他不是很重要,但是它也有自己的应用场景。最常用的2个系统category如下:
<category android:name="android.intent.category.LAUNCHER" />
和
<category android:name="android.intent.category.BROWSABLE"/>
LAUNCHER:这个不用多说,我们的项目的入口Activity都必须有的配置。它表示该 Activity 是任务的初始 Activity,在系统的桌面列出图标。
BROWSABLE:表示该 Activity 允许自身通过网络浏览器启动。举个例子:
intent-filter信息:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="app" />
<data android:host="thoughtworks"/>
<data android:pathPrefix="/behring"/>
</intent-filter>
</activity>
此时你在手机浏览器访问连接app://thoughtworks/behring 就可以拉起App的MainActivity。
android.intent.category.BROWSABLE
必须配合android.intent.action.VIEW
还有data
一起使用。
data:主要是用来传递Uri类型的数据,可以通过?key=value&key=value的形式传递参数。包含的属性有scheme
、host
、port
、path
、mimeType
、pathPrefix
、pathPattern
。
对应内容如下图:
intent-filter-data.png详细内容请参考。
需要注意的是,若要同时给intent设置 URI 和 MIME 类型,请勿调用
setData()
和setType()
,因为它们会互相抵消彼此的值。请始终使用setDataAndType()
同时设置 URI 和 MIME 类型。
总结
了解了上面的知识后,我有2个问题。
-
什么时候使用隐式Intent启动组件?
当你需要调用其他应用的能力,包括系统能力和第三方应用能力。例如:调用拨号盘,调用微信分享,调用美图秀秀处理图片等等。
-
什么时候配置自己的组件支持隐式启动?
需要对外暴露自己组件的能力。例如:京东和淘宝提供”拍立得“接口,可以直接调用他们的该组件完成图片搜索购物。