显式Intent和隐式Intent解析
Android中的Intent分为两种类型:
-
显式 Intent
:按名称(完全限定类名)指定要启动的组件。 通常,您会在自己的应用中使用显式 Intent 来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,启动新 Activity 以响应用户操作,或者启动服务以在后台下载文件。 -
隐式 Intent
:不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。 例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用在地图上显示指定的位置。
显示Intent启动当前应用组件
显式Intent一般是在当前应用中调用,用来启动当前应用的指定组件。下面展示了几种常见的显式Intent启动实例:
// 显式Intent调用——构造方法传入Component
Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);
// 显式Intent调用——setComponent
ComponentName componentName = new ComponentName(this, TestActivity.class);
Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);
// 显式Intent调用——setClass
Intent intent = new Intent();
intent.setClass(this, TestActivity.class);
startActivity(intent);
// 显式Intent调用——setClassName(packageContext, className)
Intent intent = new Intent();
//context, String
intent.setClassName(this, "com.tiny.demo.firstlinecode.test.view.TestActivity");
startActivity(intent);
// 显式Intent调用——setClassName(packageName, className)
Intent intent = new Intent();
//String, String
intent.setClassName("com.tiny.demo.firstlinecode", "com.tiny.demo.firstlinecode.test.view.TestActivity");
startActivity(intent);
显示Intent启动其他应用组件
先看下错误示范: 目标Activity配置:不做任何额外配置。
<activity
android:name=".TestExplicitIntentActivity"
android:label="TestExplicitIntentActivity" />
// 启动其他应用的Activity,目标Activity不做任何配置,会报SecurityException错误
Intent intent = new Intent();
//String, String
intent.setClassName("com.tinytongtong.dividerviewdemo", "com.tinytongtong.dividerviewdemo.TestExplicitIntentActivity");
startActivity(intent);
具体错误如下:
2019-08-06 10:02:23.355 7230-7230/com.tiny.demo.firstlinecode E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.tiny.demo.firstlinecode, PID: 7230
java.lang.SecurityException: Permission Denial: starting Intent { cmp=com.tinytongtong.dividerviewdemo/.TestExplicitIntentActivity } from ProcessRecord{2fe990c 7230:com.tiny.demo.firstlinecode/u0a397} (pid=7230, uid=10397) not exported from uid 10398
...
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.am.ActivityStackSupervisor.checkStartAnyActivityPermission(Landroid/content/Intent;Landroid/content/pm/ActivityInfo;Ljava/lang/String;IIILjava/lang/String;ZZLcom/android/server/am/ProcessRecord;Lcom/android/server/am/ActivityRecord;Lcom/android/server/am/ActivityStack;)Z(libmapleservices.so:4243605)
...
这个SecurityException异常是完全可以避免的,我们给目标Activity设置android:exported="true"
属性。
<activity
android:name=".TestExplicitIntentActivity"
android:exported="true"
android:label="TestExplicitIntentActivity" />
然后再运行,就成功打开目标Activity了。
当然了,我们还有另一种方式打开其他应用的Activity,我们需要给目标Activity设置一个不相关的
<activity
android:name=".TestExplicitIntent1Activity"
android:label="TestExplicitIntent1Activity">
<intent-filter>
<action android:name="com.tinytongtong.dividerviewdemo.action.TestExplicitIntent1Activity" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.tinytongtong.dividerviewdemo.category.TestExplicitIntent1Activity" />
<data
android:host="www.tiny.com"
android:mimeType="text/plain"
android:port="8080"
android:scheme="http" />
</intent-filter>
</activity>
启动代码:
// 启动其他应用的Activity,目标Activity需要设置一个不相关的Intent-Filter
Intent intent = new Intent();
//String, String
intent.setClassName("com.tinytongtong.dividerviewdemo", "com.tinytongtong.dividerviewdemo.TestExplicitIntent1Activity");
startActivity(intent);
说了这么多,其实就是为了证明显式Intent是可以启动其他应用的Activity的。
官方是不推荐使用显式Intent启动其他应用的Activity的,我们一般也不会这么写。因为我们启动使用的Intent#setClassName方法的两个参数均是String类型,目标应用的包名和目标应用的全路径都是以String类型体现的,这就是我们应该尽力避免的硬编码了。一旦目标Activity修改了类名、修改了包名或者移动了位置,那么我们之前写的启动代码都会失败,这明显不符合我们的代码规范。
Intent#setClassName源码:
public @NonNull Intent setClassName(@NonNull String packageName, @NonNull String className) {
mComponent = new ComponentName(packageName, className);
return this;
}
所以说,启动其他应用的组件时,应该使用隐式Intent,具体来说就是使用Intent-Filter进行匹配。
隐式Intent启动实例
隐式Intent不会指定特定的组件,而是声明要执行的常规操作,系统会根据Intent的内容去匹配对应的Activity并启动。
官网上是这么介绍的:
创建隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent-Filter 进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent-Filter 匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
所以说隐式Intent既可以启动当前应用的组件,也可以启动其他应用的组件。下面会给出两个最简单的隐式Intent启动Activity实例。
1、启动当前应用组件的示例如下: 目标activity配置:
<activity android:name=".kfysts.chapter01.intent.implicit.ImplicitIntentTestAActivity">
<intent-filter>
<action android:name="com.tiny.demo.firstlinecode.kfysts.chapter01.intent.implicit.action.a" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Intent代码:
// 启动当前应用的Activity
Intent intent = new Intent();
//action
intent.setAction("com.tiny.demo.firstlinecode.kfysts.chapter01.intent.implicit.action.a");
//Category可以不设置,因为一般在AndroidManifest.xml会设置Default,startActivity方法中也会默认添加Default。
if (intent.resolveActivity(getPackageManager()) != null) {
LogUtils.e("match success");
startActivity(intent);
} else {
LogUtils.e("match failure");
}
2、启动其他应用组件的示例如下: 目标activity配置(其他应用):
<activity
android:name=".TestImplicitIntentActivity"
android:label="TestImplicitIntentActivity">
<intent-filter>
<action android:name="com.tinytongtong.dividerviewdemo.action.a" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Intent代码:
// 启动其他应用的Activity
Intent intent = new Intent();
//action
intent.setAction("com.tinytongtong.dividerviewdemo.action.a");
//Category可以不设置,因为一般在AndroidManifest.xml会设置Default,startActivity方法中也会默认添加Default。
if (intent.resolveActivity(getPackageManager()) != null) {
LogUtils.e("match success");
startActivity(intent);
} else {
LogUtils.e("match failure");
}
IntentFilter匹配规则
隐式Intent调用分为两部分,一部分是AndroidManifest中组件的
只有当我们构建的Intent对象符合目标组件的
那么如何才能匹配上