Android 应用基础知识(5)

时间:2021-12-11 19:15:13

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"。布局界面如下。

Android 应用基础知识(5)


修改MainActivity,为3个Button添加响应事件:

[java]  view plain  copy
  1. @Override  
  2.     public void onClick(View view) {  
  3.         Intent intent = new Intent();  
  4.         switch (view.getId()){  
  5.             case R.id.BtnStartAty1SS:  
  6.                 intent.setComponent(new ComponentName("com.example.tahlia.intent_exercise_01""com.example.tahlia.intent_exercise_01.Aty1"));  
  7.                 startActivity(intent);  
  8.                 break;  
  9.             case R.id.BtnStartAty1CS:  
  10.                 intent.setComponent(new ComponentName(this"com.example.tahlia.intent_exercise_01.Aty1"));  
  11.                 startActivity(intent);  
  12.                 break;  
  13.             case R.id.BtnStartAty1CC:  
  14.                 intent.setComponent(new ComponentName(this, Aty1.class));  
  15.                 startActivity(intent);  
  16.                 break;  
  17.         }  
  18.     }  

运行后,三个按钮都能顺利启动Aty1。

和使用Component这个写法等同效果的还有:

[java]  view plain  copy
  1. Intent intent = new Intent(MainActivity.this, Aty1.class);  
  2. startActivity(intent);  

[java]  view plain  copy
  1. intent.setClass(this, Aty1.class);  
  2. startActivity(intent);  



1.2 启动不同应用中的Activity

在同一个工程之中建立一个新的模块Module,在这个module下新建两个Activity,分别是Aty2和Aty3,其中Aty2是这个模块应用的主活动。布局如下:

Android 应用基础知识(5)


这两个Activity是在一个我命名为TheOtherApp里面的活动,而一开始的MainActivity和Aty1是在另一个app中的活动。也就是说这四个活动是在两个不同的app里面的。

修改一下Intent_exercise_01中的布局,添加两个按钮用于启动Aty2和Aty3:

Android 应用基础知识(5)


修改Intent_exercise_01中MainActivity的代码,添加代码,点击button从Intent_exercise_01中启动theOtherApp的Aty2和Aty3:

[java]  view plain  copy
  1. case R.id.BtnStartAty2:  
  2.     intent.setComponent(new ComponentName("com.example.tahlia.theotherapp""com.example.tahlia.theotherapp.Aty2"));  
  3.     startActivity(intent);  
  4.     break;  
  5. case R.id.BtnStartAty3:  
  6.     intent.setComponent(new ComponentName("com.example.tahlia.theotherapp""com.example.tahlia.theotherapp.Aty3"));  
  7.     startActivity(intent);  
  8.     break;  

完成后运行一下,点击BtnStartAty2或者BtnStartAty3,发现程序崩溃了。

Android 应用基础知识(5)


查看日志,发现报这个错误:

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。效果分别如下:

Android 应用基础知识(5)


可以看到,启动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文件注册语句添加一句允许导出:

[html]  view plain  copy
  1. <activity android:name=".Aty3"  
  2.             android:exported="true">  
  3.         </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。

Android 应用基础知识(5)


由于通过action使用隐式启动,因此先给Aty1添加action属性值。action实际上是一个用户自定义的区分大小写的字符串,因此使用任何字符串都可以。但为了更好地区分不同的类,约定俗成的写法是“包名+intent.action.类名”。有时候这个写法会让action的值较长,为了减少差错,也会在被启动的类中声明一个静态常量用于存放action的值。

[html]  view plain  copy
  1. <span style="white-space:pre;"> </span><activity android:name=".Aty1">  
  2.             <intent-filter>  
  3.                 <action android:name="abcdefghijklmnopqrstuvwxyz"/>  
  4.                 <action android:name="com.example.tahlia.intent_exercise_02.intent.action.Aty1"/>  
  5.                 <category android:name="android.intent.category.DEFAULT"/>  
  6.             </intent-filter>  
  7.         </activity>  

在Aty1类中定义静态常量:

[java]  view plain  copy
  1. public static final String ACTION_START = "com.example.tahlia.intent_exercise_02.intent.action.Aty1";  

在使用Intent进行隐式启动时,可以通过setAction的方法设置action,也可以通过Intent构造函数传入action。两者写法效果是一样的。给MainActivity的button注册监听事件:

[java]  view plain  copy
  1. @Override  
  2.     public void onClick(View view) {  
  3.         Intent intent;  
  4.         switch (view.getId()){  
  5.             case R.id.BtnStartAty1_01:  
  6.                 intent = new Intent();  
  7.                 intent.setAction("abcdefghijklmnopqrstuvwxyz");  
  8.                 startActivity(intent);  
  9.                 break;  
  10.             case R.id.BtnStartAty1_02:  
  11.                 intent = new Intent("com.example.tahlia.intent_exercise_02.intent.action.Aty1");  
  12.                 startActivity(intent);  
  13.                 break;  
  14.             case R.id.BtnStartAty1_03:  
  15.                 intent = new Intent(Aty1.ACTION_START);  
  16.                 startActivity(intent);  
  17.                 break;  
  18.             case R.id.BtnStartAty1_04:  
  19.                 intent = new Intent("test");  
  20.                 startActivity(intent);  
  21.                 break;  
  22.         }  
  23.     }  

运行程序,看看效果:

Android 应用基础知识(5)


发现通过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值)。如下:

Android 应用基础知识(5)


另外,当我们配置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设置:

[java]  view plain  copy
  1. intent.addCategory("category1");  
  2. intnet.addCategory("category2");  
那么能够被启动的Activity的category中就 必须包含“category1”“category2”和“android.intent.category.DEFAULT”这三个category属性值。

※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如下:

[html]  view plain  copy
  1. <span style="white-space:pre;"> </span><activity android:name=".Aty1" >  
  2.             <intent-filter>  
  3.                 <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>  
  4.                 <category android:name="android.intent.category.DEFAULT"/>  
  5.             </intent-filter>  
  6.         </activity>  
  7.         <activity android:name=".Aty2" >  
  8.             <intent-filter>  
  9.                 <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>  
  10.                 <category android:name="myCategory"/>  
  11.                 <category android:name="android.intent.category.DEFAULT"/>  
  12.             </intent-filter>  
  13.         </activity>  
  14.         <activity android:name=".Aty3" >  
  15.             <intent-filter>  
  16.                 <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>  
  17.                 <category android:name="myCategory"/>  
  18.                 <category android:name="otherCategory"/>  
  19.                 <category android:name="android.intent.category.DEFAULT"/>  
  20.             </intent-filter>  
  21.         </activity>  
  22.         <activity android:name=".Aty4">  
  23.             <intent-filter>  
  24.                 <action android:name="com.example.tahlia.intent_exercise_03.intent.action.Aty"/>  
  25.                 <category android:name="myCategory"/>  
  26.                 <category android:name="otherCategory"/>  
  27.                 <category android:name="ourCategory"/>  
  28.                 <category android:name="android.intent.category.DEFAULT"/>  
  29.             </intent-filter>  
  30.         </activity>  

在MainActivity的布局文件中添加布局如下:

Android 应用基础知识(5)


对应地注册监听事件:

[java]  view plain  copy
  1. @Override  
  2.     public void onClick(View view) {  
  3.         Intent intent = new Intent("com.example.tahlia.intent_exercise_03.intent.action.Aty");  
  4.         switch(view.getId()){  
  5.             case R.id.BtnStartAction1:  
  6.                 startActivity(intent);  
  7.                 break;  
  8.             case R.id.BtnStartAction2:  
  9.                 intent.addCategory("myCategory");  
  10.                 startActivity(intent);  
  11.                 break;  
  12.             case R.id.BtnStartAction3:  
  13.                 intent.addCategory("myCategory");  
  14.                 intent.addCategory("otherCategory");  
  15.                 startActivity(intent);  
  16.                 break;  
  17.             case R.id.BtnStartAction4:  
  18.                 intent.addCategory("myCategory");  
  19.                 intent.addCategory("otherCategory");  
  20.                 intent.addCategory("ourCategory");  
  21.                 startActivity(intent);  
  22.                 break;  
  23.         }  
  24.     }  

现在运行一下,查看效果:

Android 应用基础知识(5)

从这个结果能看到,点击第一个按钮时,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

设置该组件可以使用浏览器启动。