Intent用法—Componet、Action、Category的属性
Intent在英语中的意思是意图,android中的日常使用就是通过intent语句去表达不同的操作意图,让系统知道我们的意图之后完成一系列的操作,例如启动Activity、Service、广播等等。
Intent中包含7大属性,分别是ComponentName、Action、Category、Data、Type、Extra以及Flag,每个属性都有其不同的作用。按照分类可以归为以下三种:
用于启动:ComponetName(显式)、Action(隐式)、Category(隐式)。
用于传递数据:Data(隐式),Type(隐式),Extra(隐式、显式)。
用于规定启动模式:Flag。
一、Intent启动:
在Intent中,我们最常使用也是最基础的操作就是启动一个Activity。下面就以启动Activity作为例子分析一下ComponetName、Action和Category的用法。
1.显式启动
通过Intent的ComponentName可以显式启动一个Activity。ComponentName的实例需要传入参数,有:
(String pkg, String cls)、
(Context pkg, String cls)、
(Context pkg, Class<?> cls)、
(Parcel in)
四种选择。ComponentName的第一个参数是要被启动的Activity所属的包的名字或者Context,第二个参数则是被启动的Activity的全称(包名加类名)或者class文件。这几种用法令Intent显式启动就可以启动同一个app中另一个Actvivity,也可以启动不同app中的Activity。下面来看看用法示例:
1.1 启动同一个应用的Activity
当前这个应用我命名为Intent_exercise_01。在这个应用中我们新建两个Activity,分别是MainActivity和Aty1,两个Activity所在的包名为package="com.example.tahlia.intent_exercise_01"。布局界面如下。
修改MainActivity,为3个Button添加响应事件:
- @Override
- public void onClick(View view) {
- Intent intent = new Intent();
- switch (view.getId()){
- case R.id.BtnStartAty1SS:
- intent.setComponent(new ComponentName("com.example.tahlia.intent_exercise_01", "com.example.tahlia.intent_exercise_01.Aty1"));
- startActivity(intent);
- break;
- case R.id.BtnStartAty1CS:
- intent.setComponent(new ComponentName(this, "com.example.tahlia.intent_exercise_01.Aty1"));
- startActivity(intent);
- break;
- case R.id.BtnStartAty1CC:
- intent.setComponent(new ComponentName(this, Aty1.class));
- startActivity(intent);
- break;
- }
- }
运行后,三个按钮都能顺利启动Aty1。
和使用Component这个写法等同效果的还有:
- Intent intent = new Intent(MainActivity.this, Aty1.class);
- startActivity(intent);
- intent.setClass(this, Aty1.class);
- startActivity(intent);
1.2 启动不同应用中的Activity
在同一个工程之中建立一个新的模块Module,在这个module下新建两个Activity,分别是Aty2和Aty3,其中Aty2是这个模块应用的主活动。布局如下:
这两个Activity是在一个我命名为TheOtherApp里面的活动,而一开始的MainActivity和Aty1是在另一个app中的活动。也就是说这四个活动是在两个不同的app里面的。
修改一下Intent_exercise_01中的布局,添加两个按钮用于启动Aty2和Aty3:
修改Intent_exercise_01中MainActivity的代码,添加代码,点击button从Intent_exercise_01中启动theOtherApp的Aty2和Aty3:
- case R.id.BtnStartAty2:
- intent.setComponent(new ComponentName("com.example.tahlia.theotherapp", "com.example.tahlia.theotherapp.Aty2"));
- startActivity(intent);
- break;
- case R.id.BtnStartAty3:
- intent.setComponent(new ComponentName("com.example.tahlia.theotherapp", "com.example.tahlia.theotherapp.Aty3"));
- startActivity(intent);
- break;
完成后运行一下,点击BtnStartAty2或者BtnStartAty3,发现程序崩溃了。
查看日志,发现报这个错误:
FATAL EXCEPTION: main
Process: com.example.tahlia.intent_exercise_01, PID: 5644
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.tahlia.theotherapp/com.example.tahlia.theotherapp.Aty2}; have you declared this activity in your AndroidManifest.xml?
也就是说,无法找到对应Activity的class文件。这个错误很好解决,因为我们新建了一个theOtherApp的应用之后,并没有在模拟器上安装,因此模拟器中目前还没有这个Activity的存在(因为这个应用不存在)。因此先在模拟器上运行一次theOtherApp,确保模拟器中已经安装了要被启动的Activity所在的应用后,我们再次运行Intent_exercise_01,点击启动Aty2和Aty3。效果分别如下:
可以看到,启动Aty2时一切正常,而启动Aty3时程序就会崩溃。报错如下:
FATAL EXCEPTION: main
Process: com.example.tahlia.intent_exercise_01, PID: 14015
java.lang.SecurityException: Permission Denial: starting Intent { cmp=com.example.tahlia.theotherapp/.Aty3 } from ProcessRecord{fddb6eb 14015:com.example.tahlia.intent_exercise_01/u0a77} (pid=14015, uid=10077) not exported from uid 10078
报错提示:安全性错误。
解决方法:将Aty3的manifest文件注册语句添加一句允许导出:
- <activity android:name=".Aty3"
- android:exported="true">
- </activity>
这个属性的意思是允许这个活动被其他程序访问。如果没有这一句,exported属性默认为false,即不可被外部程序访问。
而Aty2由于作为一个应用的主Activity,在manifest文件<activity>标签中包含了过滤器<intent-filter>,对于包含了过滤器的组件,系统默认exported为true,可以被外部程序访问。这就是Aty2能被成功启动而Aty3会出错的原因了。
2.隐式启动
Intent的隐式启动方法是指不通过目标组件名字,而是通过intent-filter过滤器的设置,进行组件的匹配和解析后,由Android决定把这个Intent交给谁去处理。在<intent-filter>中,我们一般通过action和category的值进行设置和匹配。
2.1 action的匹配规则
action是一个用户定义的字符串,用来描述当前定义action的组件,我把它理解为是Activity的一个名字。action的匹配规则有两个:
1.对于设置了action的Activity来说,如果使用隐式启动这个Activity,就必须匹配到他的action。
2.一个Activity允许设置多个action,在匹配的时候只需要匹配到其中一个就可以了。
这个规则可以这么理解:有个人的名字是王五,大家都叫他老王,也有叫王胖子的。如果想要和他打招呼,叫“王五”、“老王”、“王胖子”都可以得到他的回应。但是如果喊他老张,他就不会给任何的回应,因为他不是老张。
使用一段代码来加深了解action的匹配规则。新建工程后新建一个准备被启动的Activity,命名为Aty1。MainActivity和Aty布局如下,通过点击MainActivity中的button隐式启动Aty。
由于通过action使用隐式启动,因此先给Aty1添加action属性值。action实际上是一个用户自定义的区分大小写的字符串,因此使用任何字符串都可以。但为了更好地区分不同的类,约定俗成的写法是“包名+intent.action.类名”。有时候这个写法会让action的值较长,为了减少差错,也会在被启动的类中声明一个静态常量用于存放action的值。
- <span style="white-space:pre;"> </span><activity android:name=".Aty1">
- <intent-filter>
- <action android:name="abcdefghijklmnopqrstuvwxyz"/>
- <action android:name="com.example.tahlia.intent_exercise_02.intent.action.Aty1"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
在Aty1类中定义静态常量:
- public static final String ACTION_START = "com.example.tahlia.intent_exercise_02.intent.action.Aty1";
在使用Intent进行隐式启动时,可以通过setAction的方法设置action,也可以通过Intent构造函数传入action。两者写法效果是一样的。给MainActivity的button注册监听事件:
- @Override
- public void onClick(View view) {
- Intent intent;
- switch (view.getId()){
- case R.id.BtnStartAty1_01:
- intent = new Intent();
- intent.setAction("abcdefghijklmnopqrstuvwxyz");
- startActivity(intent);
- break;
- case R.id.BtnStartAty1_02:
- intent = new Intent("com.example.tahlia.intent_exercise_02.intent.action.Aty1");
- startActivity(intent);
- break;
- case R.id.BtnStartAty1_03:
- intent = new Intent(Aty1.ACTION_START);
- startActivity(intent);
- break;
- case R.id.BtnStartAty1_04:
- intent = new Intent("test");
- startActivity(intent);
- break;
- }
- }
运行程序,看看效果:
发现通过setAction传入“abcdefghijklmnopqrstuvwxyz”、通过Intent构造方法传入“com.example.tahlia.intent_exercise_02.intent.action.Aty1”、通过Intent构造方法传入Aty1类的静态常量ACTION_START均能成功启动Aty1。传入“test”后程序崩溃并报错:
FATAL EXCEPTION: main
Process: com.example.tahlia.intent_exercise_02, PID: 17013
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=test }
由于Aty1的action并没有“test”的值,因此在Android解析中找不到匹配的类去启动,因此也就崩溃了。
※要注意的是,一旦使用了<intent-filter>,标签内一定要定义<category android:name="android.intent.category.DEFAULT"/>,否则程序将会报错。由于在通过Intent调用Activity时,我们不主动使用intent.addCategory添加category,Android就会默认自动添加“android.intent.category.DEFAULT”,因此在<intent-filter>配置中category_DEFAULT是不可缺少的。
不同的Activity业可以配置同一个action值。使用Intent通过action去启动Activity时,Android通过过滤器对action进行匹配筛选,就会得到多个Activity(他们同时都拥有一个action值)。如下:
另外,当我们配置action的时候,系统也会提供常用的属性值提示,以下举例部分:
ACTION_MAIN |
Android Application的入口,每个APP必须且只能有一个Activity包含一个此类型的Action声明。 |
ACTION_DIAL |
打开系统默认的拨号程序,如果Data中设置了电话号码,则自动在拨号程序中输入此号码。 |
ACTION_CALL |
直接拨打Data设置好的电话号码 |
ACTION_VIEW |
根据不同的Data类型,显示不同的数据 |
ACTION_SEND |
由用户指定发送方式进行数据发送操作 |
2.2 Category的匹配规则
category也是一个用户自定义的字符串。但和action不一样的是,它可以在intent中通过intent.addCategory设置多个category的值。在匹配时,通过过滤器寻找到的Activity必须包含intent设置的所有category的值。
比如通过intent设置:
- intent.addCategory("category1");
- intnet.addCategory("category2");
※category_DEFAULT是隐式启动一个Activity必不可少的一个属性值。使用隐式启动Activity,Android会默认在隐式启动的intent中加入intent.addCategory(“android.intent.category.DEFAULT”);这一句,表示Android会把它当成Activity进行处理执行,因此对于Activity,我们都应该主动加上category_DEFAULT这一句。(app的入口活动除外,当然也可以手动添加进去,不添加也不会出错)
下面用例子来加深一下category的匹配印象。新建一个工程,在MainActivity以外新建4个Activity,分别命名为Aty1,Aty2,Aty3,Aty4。在manifest文件中对他们4个activity都设置同一个action和不同的category如下:
- <span style="white-space:pre;"> </span><activity android:name=".Aty1" >
- <intent-filter>
- <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
- <activity android:name=".Aty2" >
- <intent-filter>
- <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>
- <category android:name="myCategory"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
- <activity android:name=".Aty3" >
- <intent-filter>
- <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>
- <category android:name="myCategory"/>
- <category android:name="otherCategory"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
- <activity android:name=".Aty4">
- <intent-filter>
- <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>
- <category android:name="myCategory"/>
- <category android:name="otherCategory"/>
- <category android:name="ourCategory"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
在MainActivity的布局文件中添加布局如下:
对应地注册监听事件:
- @Override
- public void onClick(View view) {
- Intent intent = new Intent("com.example.tahlia.intent_exercise_03.intent.action.Aty");
- switch(view.getId()){
- case R.id.BtnStartAction1:
- startActivity(intent);
- break;
- case R.id.BtnStartAction2:
- intent.addCategory("myCategory");
- startActivity(intent);
- break;
- case R.id.BtnStartAction3:
- intent.addCategory("myCategory");
- intent.addCategory("otherCategory");
- startActivity(intent);
- break;
- case R.id.BtnStartAction4:
- intent.addCategory("myCategory");
- intent.addCategory("otherCategory");
- intent.addCategory("ourCategory");
- startActivity(intent);
- break;
- }
- }
现在运行一下,查看效果:
从这个结果能看到,点击第一个按钮时,category匹配条件只要求含有category_DEFAULT,对于四个activity来说都含有这个category的值,因此都能匹配上。第二个按钮多添加了一个myCategory的匹配要求,因此除了Aty1以外都含有myCategory的值,Aty2、Aty3、Aty4能匹配上。以此类推,就能很好理解category的匹配要求了。
另外,当我们配置category的时候,系统也会提供常用的属性值提示,以下举例部分:
CATEGORY_DEFAULT |
Android系统中默认的执行方式,按照普通Activity的执行方式执行 |
CATEGORY_HOME |
设置该组件为Home Activity |
CATEGORY_LAUNCHER |
设置该组件为在当前应用程序启动器中优先级最高的Activity,通常为入口ACTION_MAIN配合使用。 |
CATEGORY_BROWSABLE |
设置该组件可以使用浏览器启动。 |