显式Intent和隐式Intent解析

显式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设置一个不相关的<intent-filter>。具体配置如下:

<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-filter>配置,一部分是Intent对象的构建。

只有当我们构建的Intent对象符合目标组件的<intent-filter>配置的时候,才能成功启动目标组件。

那么如何才能匹配上<intent-filter>的配置呢?这个就是我们要说的IntentFilter的匹配规则。

<intent-filter>中的过滤信息有三种,分别是action、category、data。下面是一个过滤规则的实例:

<activity
    android:name=".IActivity"
    android:label="IActivity"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name=“com.tinytongtong.dividerviewdemo.action.11" />
        <action android:name=“com.tinytongtong.dividerviewdemo.action.22" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name=“com.tinytongtong.dividerviewdemo.category.11" />
        <data
            android:host="www.tiny.com"
            android:mimeType="text/plain"
            android:port="8080"
            android:scheme="http" />
    </intent-filter>
</activity>

匹配规则

为了匹配过滤列表,需要同时匹配过滤列表中的action、category、data信息,否则匹配失败。

一个过滤列表中的action、category和data可以有多个,所有的action、category、data分别构成不同类别,同一类别的信息共同约束当前类别的匹配过程。

只有一个Intent同时匹配action、category、data才算完全匹配,只有完全匹配才能成功启动Activity。

另外一点,一个activity中可以有多个intent-filter,一个Intent只要能匹配任何一组Intent-filter即可成功启动对应的activity。

action

action是一个字符串,该字符串区分大小写。系统预定义了一些action,同时我们也可以在应用中定义自己的action。

一个<intent-filter>中可以有多个action,此时Intent中的action能够和<intent-filter>中的任何一个action相同即可匹配成功。

另外,<intent-filter>中的action和Intent中的action都是必须的,就是说<intent-filter>中至少指定一个action,同理Intent中也必须设置action,否则就没有任何意义了。

category

category也是一个字符串,也区分大小写。系统预定义了一些category,同时我们也可以在应用中定义自己的category。

我们一般说category有默认值,是由于系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category。

因此,我们的<intent-filter>配置中必须添加对应的配置,不然会匹配失败。

<intent-filter>
    ...
    <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

Intent中我们可以不设置category,因为系统默认给我们添加了“android.intent.category.DEFAULT”。如果我们要添加category的话,这个category就必须跟</intent-filter>的任意一个匹配,否则会匹配失败。

data
data语法

data语法如下所示:

<data android:scheme="string"

      android:host="string"

      android:port="string"

      android:path="string"

      android:pathPattern="string"

      android:pathPrefix="string"

      android:mimeType="string" />

data由两部分组成,mimeType和URI。
mimeType指媒体类型,比如image/jpegaudio/mpeg4-genericvideo/*等,可以表示图片、文本、视频等不同的媒体格式。

URI包含的数据比较多,结构如下所示:

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

具体示例如下所示:

content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

接下来介绍每一个数据的含义:

①android:scheme
URI的模式,比如http、file、content等。如果URI中没有指定scheme,那么整个URI的其他参数无效,这也意味着URI是无效的。

②android:host
URI的主机名,比如www.baidu.com。如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。

③Android:port
URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。

④android:path、android:pathPrefix、android:pathPattern
这三个参数表述路径信息,其中path表示完整的路径信息;
pathPrefix表示路径的前缀信息;
pathPattern也表示完整的路径信息,但是它里面可以包含通配符“*”“*”表示0个或多个任意字符,需要注意的事,由于正则表达式的规范,如果想表示真实的字符串,那么“*”要写成“\\*”“\”要写成“\\\\”

另外,data有两种特殊写法:下面两种写法是等价的。

<intent-filter . . . >
    <data android:scheme="something" android:host="project.example.com" />
    . . .
</intent-filter>
<intent-filter . . . >
    <data android:scheme="something" />
    <data android:host="project.example.com" />
    . . .
</intent-filter>
data的匹配规则

data是非必须的,可以不设置。但是如果在</intent-filter>定义了data,那么Intent中也必须设置可匹配的data。

再来看看data内部:

</intent-filter>的URI有默认值file和content,如果设置了URI,则默认值就失效。

</intent-filter>的mimeType可以不设置。

data的匹配意味着mimeType和URI同时匹配。

综合以上所有情况,这里分几种情况:

①data中只配置了mimeType:

<intent-filter>
    ...
    <data android:mimeType="image/*" />
</intent-filter>

由于这里只配置了mimeType,所以会使用默认的URI,默认的URI的scheme为file或content。

所以使用下面这两段代码可以匹配:

intent.setDataAndType(Uri.parse("content://maolegemi"), "image/jpeg");
// 下面这段在api大于24的版本上会报错FileUriExposedException,需要将file替换为content
intent.setDataAndType(Uri.parse("file://maolegemi"), "image/jpeg");

②data中只配置了URI:

<intent-filter>
    ...
    <data
        android:host="www.tiny.com"
        android:port="8080"
        android:scheme="http" />
</intent-filter>

对应匹配代码如下:

intent.setDataAndType(Uri.parse("http://www.tiny.com:8080/abcdefg"), null);

③data中同时配置了mimeType和URI:

<intent-filter>
    ...
    <data
        android:host="www.tiny.com"
        android:port="8080"
        android:mimeType="text/plain"
        android:scheme="http" />
</intent-filter>

对应的匹配代码如下:

intent.setDataAndType(Uri.parse("http://www.tiny.com:8080/abcdefg"), "text/plain");

总结

综上所述,对<intent-filter>而言,必不可少的配置是<cation>和默认的category。

对Intent而言,必不可少的是action,因为默认的category会添加。

如果<intent-filter>定义了data,不管mimeType是否设置,Intent中都必须设置uri,因为uri有默认值。

参考

Android开发艺术探索

https://developer.android.com/guide/components/intents-filters?hl=zh-cn

https://developer.android.com/guide/topics/manifest/data-element

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • Intent组件虽然不是四大组件,但却是连接四大组件的桥梁,学习好这个知识,也非常的重要。 一、什么是Intent...
    困惑困惑困惑阅读 1,503评论 0 0
  • 什么是Intent intent是一个消息传递对象,可以使用它从其他应用组件请求操作。通常我们用intent来启动...
    dony_east阅读 836评论 0 3
  • 1.Intent的用法: (1)Action跳转 1、 使用Action跳转,当程序AndroidManifest...
    彭文波_4118阅读 5,732评论 0 7
  • 今天在餐馆吃饭,有一道菜是砂锅豆腐。菜来了,从盆里往外端,竟然烫手,两个食指烫着了。赶紧用水冲冲,然后问服务员:烫...
    金明啊阅读 226评论 0 0
  • 从开始写简书以来,除了写作,日子变得散漫,毫无计划。虽然有时我也很努力,看起来很努力,每天忙忙碌碌,但没有整体规划...
    逆风飞扬你的笑阅读 2,436评论 24 110