Activity显式调用和隐式调用的区别以及Intent Filter知识点总结
前言
测试代码时写的项目,基本就是正文的代码
https://github.com/wodongx123/startActivityDemo
1. 显式调用和隐式调用
Activity的启动分为两种,显示调用和隐式调用。不过在绝大部分的情况下,我们一般都是使用显示调用,只有少部分情况时会用上隐式调用。
1.1 显示调用
需要明确指定被启动对象的组件信息的调用方式,是显式调用,显式调用Activity有三种不同方式
- 常见的指明目标Activity的class类。
/** * 显式启动Activity */ private void explicitStart() { Intent intent = new Intent(this, SecondActivity.class); startActivity(intent); }
- 通过Intent的ComponentName启动Activity。
// 第一个参数是包名,第二个参数是包下的类名。包名可以直接用getPackageName()方法代替,也可以直接输入当前包的环境 ComponentName c = new ComponentName("com.wodongx123.startactivitydemo", "com.wodongx123.startactivitydemo.SecondActivity"); //ComponentName c = new ComponentName(getPackageName(), getPackageName() + ".SecondActivity"); //ComponentName c = new ComponentName(getApplicationContext(), getPackageName() + ".SecondActivity"); Intent intent1 = new Intent(); intent1.setComponent(c); startActivity(intent1);
- 直接指定类名启动。
Intent intent2 = new Intent(); // 第一个参数是包名,第二个参数是包下的类名。包名可以直接用getPackageName()方法代替,也可以直接输入当前包的环境 //intent2.setClassName("com.wodongx123.startactivitydemo", "com.wodongx123.startactivitydemo.SecondActivity"); intent2.setClassName(getApplicationContext(), "com.wodongx123.startactivitydemo.SecondActivity"); startActivity(intent2);
注:网上搜来的资料是说,第一个参数填当前类名,第二个参数填目标类名。我自己实际测试了之后发现这么填是会报错的。可能是因为我的操作方式不对也可能是因为版本更新后不支持以前的方式了。
1.2 隐式调用
概念和显示调用相对,不指明目标Activity的具体信息,Intent会根据Intent Filter所设置的过滤信息去挨个比对,如果匹配成功后就会启动对应的Activity。
Intent Filter设置的过滤信息主要有三个:action,data,category。关于过滤信息的内容下面会详细介绍,先看启动的示例。
- 先在AndroidManifest.xml中,将目标Activity设置action和category,两者的值都是字符串,所以随意设置即可。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.wodongx123.startactivitydemo"> <application ......省略无关代码 > ......省略无关代码 <activity android:name=".ThirdActivity"> <intent-filter> <action android:name="a"/> <category android:name="b"/> <!-- 无论设置了哪些内容,最后都要加上这个,否则无法启动--> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> </application> </manifest>
- 然后在Activity中启动
private void implicitStart() { Intent intent = new Intent(); intent.setAction("a"); intent.addCategory("b"); startActivity(intent); }
2. Intent Filter
- 一个Activity可以有多个Intent Filter,我们的Intent只要能匹配其中一个Intent Filter就能成功启动。
- 一个Intent Filter中可以有多个Action,category,data,只有Intent中的Action,category,data三个字段都完全按照匹配规则匹配之后,才算成功匹配这个Intent Filter。
2.1 Action的匹配规则
action是一个字符串,也就是说无论是你在Intent Filter写的内容,还是Intent中setAction中,都是填写的字符串。
- Intent中的Action必须完全和Intent Filter中的Action一样。
- Intent Filter中必须有至少一个Action。
- 如果Intent Filter中有多个Action,那么Intent只需要匹配其中一个即可。
- Action的内容区分大小写。
示例:
<activity android:name=".test2.ActionTestActivity">
<intent-filter>
<!-- 设置多个action,匹配一个即可打开-->
<action android:name="test1"/>
<action android:name="test2"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
/**
* 测试Action的匹配规则
*/
private void actionTest() {
Intent intent = new Intent();
intent.setAction("test1");
startActivity(intent);
}
2.2 Category的匹配规则
Category也是一个字符串,但是区别于Action的是,Intent中可以同时添加多个Category。
- Intent Filter中必须要添加android.intent.category.DEFAULT这一条category(除了App启动的Intent Filter外)。
- Intent中如果含有category,那么所有的category都必须符合Intent Filter中的category。
- Intent中如果没有category,那么只要Action匹配成功即可。
- 综合一下2和3,即Intent中的category要是Intent Filter中category的子集。
<activity android:name=".test2.CategoryTestActivity">
<intent-filter>
<action android:name="t" />
<category android:name="cate1" />
<category android:name="cate2" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
/**
* 测试category的匹配规则
* 取消category2的注释后运行,然后取消category3的注释后再次运行,查看结果
*/
private void categoryTest() {
Intent intent = new Intent();
intent.setAction("t");
intent.addCategory("cate1");
//intent.addCategory("cate2");
//intent.addCategory("cate3"); //运行失败
try {
startActivity(intent);
}catch (Exception e){
e.printStackTrace();
}
}
2.3 Data的匹配规则
由于Data不像Category和Action一样是一个单字符串(只有一个android:name),所以先看一下Data的结构。
<!-- String表示是字符串类型 -->
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
内容虽然看着多,但是其实就分为两个部分,从scheme到pathPrefix,都是Uri的一部分,mimeType单独是一部分。
mimeType是媒体类型,比如image/png,audio/mpeg4-generic之类不同的媒体格式。支持用*通配符,关于媒体类型列表,一般所用的类型点这里,当然由于是字符串,也可以根据具体需求自创一个类型。
Uri是用于定位各种各样的资源的字符串,包括本地的和互联网的。直接看一下URI的结构和示例就能理解:
结构:
<scheme>://<host>:<port>/[<path>/<[pathPrefix>/<pathPattern>]
示例:
https://www.baidu.com:80/search/info
最后介绍一下匹配规则,规则很简单,Intent Filter中出现哪个字段,Uri和mimeType的对应部分就要完全一样,不出现可以无视(比如说你可以只设置mimeType而不设置Uri的任何内容)。Intent Filter可以有多个data,Intent只要能匹配其中一个即可。
示例1(只设置mimeType也能匹配):
<activity android:name=".test2.DataTestActivity">
<intent-filter>
<action android:name="t1"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/png" />
</intent-filter>
</activity>
/**
* 测试Data的匹配规则(只设置mimeType)
*/
private void dataTest() {
Intent intent = new Intent();
intent.setAction("t1");
intent.setType("image/png");
//intent.setType("image/jpeg"); //运行失败
try {
startActivity(intent);
}catch (Exception e){
e.printStackTrace();
}
}
示例2:(设置mimeType和Uri)
<activity android:name=".test2.DataTestActivity">
<intent-filter>
<action android:name="t2"/>
<data android:mimeType="image/jpeg"
android:scheme="https"
android:host="www.baidu.com"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
/**
* 测试Data的匹配规则(设置mimeType和URI)
* 切换不同的URI查看结果
*/
private void dataTest1() {
Intent intent = new Intent();
intent.setAction("t2");
intent.setDataAndType(Uri.parse("https://www.baidu.com/aaa"), "image/jpeg");
//intent.setDataAndType(Uri.parse("https://www.tecent.com/aaa"), "image/jpeg");
try {
startActivity(intent);
}catch (Exception e){
e.printStackTrace();
}
}
2.3.1 URI的属性
scheme:Uri的模式,http、https、file、content等,Uri如果没有scheme其他参数无效,整个Uri也无效。
host:Uri的主机名,比如www.baidu.com,Uri如果没有host,整个Uri还是无效。
Port:Uri中的端口号,仅当Uri中指定了scheme和host时才有效。
path、pathPattern、PathPrefix:三个参数都表示路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它可以包含通配符‘*’,可以匹配0个或多个字符,需要注意的是由于正则表达式的规范,如果想表达真实的字符串,‘*’要写成‘\\*’,‘\’要写成‘\\\\’;PathPrefix表示路径的前缀信息。
2.4 Intent
我们都知道,Intent的作用不仅仅是用于Activity的启动,它是关联四大组件的重要内容,而四大组件都要在AndroidManifest.xml中注册,所以刚刚关于Intent Filter的一切内容,不仅限于Activity,在Service和BroadcastReceiver中也能同样的使用。
3. 隐式调用的其他内容
3.1 关于android.intent.category.DEFAULT
在Intent Filter的设置中,android.intent.category.DEFAULT这个变量是必须要加上的,如果不加的话,哪怕其他字段都匹配成功隐式启动时也会直接找不到对应的Activity然后报错。
那么具体是什么原因会导致这样呢?
实际上,我们在启动Activity,调用startActivity或者startActivityForResult方法时,会自动的为Intent添加一个android.intent.category.DEFAULT的category。由于category的匹配规则,所以我们每个Intent Filter都要加上android.intent.category.DEFAULT才能正常进行匹配。
3.2 系统自带的一些隐式调用的Activity
其实我们很多时候就已经会用隐式调用的方法调用Android系统内置的一些Activity了,比如打电话。
// 要在AndroidManifest.xml中先加上<uses-permission android:name="android.permission.CALL_PHONE"/>
String tel = "110";
//隐式调用,第一个参数是Action,第二个参数是data
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + tel));
startActivity(intent);
开网页:
Intent intent1 = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.baidu.com"));
startActivity(intent1);
3.3 同时出现多个Activity匹配
如果同时出现多个Activity匹配我们的隐式调用时,会怎么样?答案很简单,系统会让你选择要启动的对应Activity
以刚刚的打开网页为例,如果你的手机里面有装其他浏览器的话,会变成这样:
3.4 为什么需要隐式调用Activity,隐式调用Activity的应用场景
其实在讲完隐式启动之后,这个问题的答案已经不言而喻了,隐式启动的应用场景主要是用于应用程序间的数据互通,也就是SDK开发,当你的某个页面需要让其他应用程序调用的时候,只要对接完Intent Filter中的数据,对方的应用程序就可以直接打开你所指定的页面,不过一般情况下是己方的程序员写完这一套流程封装成aar后,对方直接导入后使用就是了(比如支付宝的支付接口)。
参考材料
Android开发艺术探索
p28 - p34.
Android 启动一个Activity的几种方式 - 相伴流年 - 博客园
https://www.cnblogs.com/cyqx/p/10927458.html
隐式启动Activity_qinxue24的博客-优快云博客
https://blog.youkuaiyun.com/qinxue24/article/details/72818510
为什么默认需加android.intent.category.DEFAULT - Holyday - 博客园
https://www.cnblogs.com/holyday/p/7204487.html
Intent的属性及Intent-filter配置——Data、Type属性与intent-filter配置 - TealerProg - 博客园
https://www.cnblogs.com/wolipengbo/p/3427574.html