Intent详解及其用法
阅读原文时间:2021年04月20日阅读:1

Intent

概述

翻译过来为“意图”,它是一种运行时绑定(run-time binding)机制,可以应用于两个应用间的通讯交互,也能够应用于在同一个应用下不同组件的交互(activity、service、broadcast receiver)

看下面的图,虽然Intent不属于四大组件,但是Intent却承担了三大组件的“中间人”的重任。试想一下,如果没有这个中间人,每一对组件间想要通信,就都需要重新开辟一条通道,随着组件数量和通讯需求的增加,这无疑会使得组件间的关系错综复杂,最后高度耦合,而加入这样一个中间人的角色,所有组件只需要联系这个中间人,由这个中间人去为组件进行匹配、传递等相关通信操作,这就使得组件间的依赖关系被极大的降低了。

(题外:Content Provide本身就是涉及进程通信的机制,所以用不到Intent)

对于向这三种组件发送intent有不同的机制:

  • 使用Context.startActivity()或Activity.startActivityForResult(),传入一个intent来启动一个activity。
  • 使用Activity.setResult(),传入一个intent来从activity中返回结果。
  • 将intent对象传给Context.startService()来启动一个service或者传消息给一个运行的service。
  • 将intent对象传给Context.bindService()来绑定一个service。
  • 将intent对象传给Context.sendBroadcast(),Context.sendOrderedBroadcast(),或者Contex.sendStickyBroadcast()方法,则它们被传给 broadcast receiver。

然后上一段API对Intent的介绍

Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:

  • 启动 Activity

    Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity(),您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。 如果您希望在 Activity 完成后收到结果,请调用 startActivityForResult()。在 Activity 的onActivityResult() 回调中,您的 Activity 将结果作为单独的 Intent 对象接收。

  • 启动服务

    Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。 如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。

  • 传递广播

    广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。

组成部分

Intent由以下各个组成部分:

  1. component(组件) :目的组件
  2. Action (动作) :用来表现意图的行动
  3. category (类别) :用来表现动作的类别
  4. data (数据) :表示与动作要操纵的数据
  5. type (数据类型) :对于data范例的描写
  6. extras (扩展信息) :扩展信息
  7. Flags (标志位) :期望这个意图的运行模式

更多细节可以参考API文档,传送门地址:

https://developer.android.google.cn/guide/components/intents-filters.html#Building

下面简单的对每个部分进行一定的介绍,具体肯定是?的更具体。


component

即明确指定Intent的目标组件,这部描述再下面的显式Intent,不重复描述。


Action&Category

Action用来表现意图的行动当日常生活中,描述一个意愿或愿望的时候,总是有一个动词在其中。

Category则为动作声明一个类别(给action增加额外的信息),常与action(难度不是必须吗?大雾)配套使用。

句子结构常常有主谓宾,Action即“谓语”,Category相当于“状语”

  • 当你指明了一个Action,执行者就会依照这个动作的指示,接受相关输入,表现对应行为,产生符合的输出。
  • 在Intent类中,定义了一批量的动作,比如 ACTION_VIEW, ACTION_PICK等, 基本涵盖了常用动作。
  • Action是一个用户定义的字符串,用于描述一个Android应用程序组件,一个Intent Filter可以包含多个Action。
  • 在 AndroidManifest.ml的Activity定义时可以在其节点指定一个Action列表用于标识Activity所能接受的“动作”。
  • 在Intent对象中add的Category属性,在Intent Filter中必须出现,否则会直接报错!!

相关Demo统一放下方的Demo一块,可以点击右侧目录进行跳转。

同时系统提供很多默认的Action常量,可以参考API文档

https://developer.android.google.cn/reference/android/content/Intent.html#standard-activity-actions

同样的,也提供了许多category的常量,可以参考如下:

https://developer.android.google.cn/reference/android/content/Intent.html#standard-categories


Data&Type

Data即数据,表示要操纵的数据,声明方式类似Action&Category

Type即对data范例的描写

句子结构常常有主谓宾,Action即“宾语”,而Type可以理解为Application和activity的关系,有data那data的优先级就更高,没有就按type来。

  • data常配合action一起使用,来描述一个意图(想想宾语也是搭配谓语的),Data用一个uri对象(数据的地址,一种标识符)来表示。
  • 如果Intent对象中既包含Uri又包含Type,那么,在sintent-filter>中也必须二者都包含才能通过测试。
  • Data属性的声明中要指定访问数据的Uri和MIME类型,Type属性则用于明确指定Data属性的数据类型或MIME类型。
  • 通常来说,当Intent不指定Data属性时Type属性才会起作用,否则Android系统将会根据Data属性值来分析数据的类型,所以无需指定Type属性。
  • 如果Intent对象包含Uri但是没有包含类型,并且类型不能从Uri中自动识别,那么cintent-filter>列表中也只能包含Uri
  • 如果Intent对象只包含类型,没有包含Uri。那么,在sintent-filter>中也只能包含类型,不能包含Uri。

相关Demo统一放下方的Demo一块,可以点击目录进行跳转。


Extras

下面是API上的描述:

携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。

您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将Bundle 插入 Intent 中。

例如,使用 ACTION_SEND 创建用于发送电子邮件的 Intent 时,可以使用 EXTRA_EMAIL 键指定“目标”收件人,并使用 EXTRA_SUBJECT 键指定“主题”。

Intent 类将为标准化的数据类型指定多个 EXTRA_* 常量。如需声明自己的 extra 键(对于应用接收的 Intent),请确保将应用的软件包名称作为前缀。 例如:

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

Extras是其他所有附加信息的集合,可以为组件提供扩展信息。(传值的时候,就是使用它来传递简单对象、数据等等)

关于Demo可以参考之前的文章:

https://blog.csdn.net/nishigesb123/article/details/88900360

下面不再提供单独的演示


Flags

同样上一段API的描述

在Intent 类中定义的、充当 Intent 元数据的标志。 标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。

更多信息可以参考:

https://developer.android.google.cn/reference/android/content/Intent.html#setFlags(int)

实际上指的是Intent的运行模式,一个程序启动后系统会为这个程序分配一个task。

一个task里面可以拥有不同应用程序的activity,一个程序也可以对应多个task。

可以在AndroidManifest.xml中activity标签的属性launchMode中设置它们的对应关系(Activity的加载模式),一共有以下四种模式:

  1. standard:标准模式,以这种模式加载必定会构造一个新的Activity实例放到目标task中的activity栈顶,不管当前 task的栈顶是什么情况。
  2. singleTop: 与standard模式类似,区别在于加载activity会多个判断步骤。判断需要加载的新activity与当前 task栈顶的activity是不是同一个,相同的话就不再构造新的activity,并调用这个activity的newlnstance ()方法,不相同就还是会构造新的activity放到栈顶。
  3. singleTask:该种模式下,会创建一个新的task来加iactivity,并且这个task中只允许存在一个Activity的一个实例(以后可以加载其他activity的实例)
  4. SingleInstance:这种模式下,会创建一个新的task并且这个task中只能存在一个需要加载的这个Activity实例,即除了这个activity之外,不允许其他activity。

关于 Activity 应该如何与任务关联 可以参考API地址如下:

https://developer.android.google.cn/guide/components/tasks-and-back-stack.html#ManifestForTasks

这部分内容比较多,所以单独放在另一篇文章(可能4.1号左右才可见):

https://blog.csdn.net/nishigesb123/article/details/88919008

下面不再提供单独的Demo演示


Intent大致可以分为两种, 显式和隐式,接下来我们逐个介绍。

显式Intent

显式 Intent 是指用于启动某个特定应用组件(例如,应用中的某个特定 Activity 或服务)的 Intent。

要创建显式 Intent,请为 Intent 对象定义组件名称 — Intent 的所有其他属性均为可选属性。

下面是一个简单的代码片段: 

    Intent intent = new Intent(MainActivity.this,YourActivity.class);
    startActivity(intent);

可以看到,构建了一个Intent,传入了 MainActivity.this(上下文),YourActivity.class(目标活动,即component),即在MainActivity.this之上启动目标活动,如此一来,Intent非常明确,即称之为显示Intent。

目标活动可以是Class对,也可以是包名加类名的字符串。

隐式Intent

显示intent需要指定“接收者”和“发送者”,这不利于解耦,显然是和我们intent的初衷有一定矛盾之处的。

隐式就不一样了,Intent的发送者在构造Intent对象时,并不需要指定“接收者”,而是通过一定的设置,由系统进行筛选,这有助于解耦,所以官方也是推荐这种方式。

说到筛选,不难想到Filter(过滤器),确实,隐式Intent需要借助Intent Filter来实现“筛选”这一过程,并且仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。

下面是Intent Filter的匹配过程:

图片挂了…有机会补

DEMO


首先测试显式Intent备注:除了该方式,接下来其余测试都是隐式。

测试之前准备一个Main2Activity,用于跳转,内容可以什么都不写。

再准备一个Button,并添加点击事件

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="componentClick"
        android:text="通过组件名称直接查找组件"
        app:layout_constraintTop_toTopOf="parent"
        />

书写点击事件方法

    //显式(指定组件名称)
    public void componentClick(View view){
        Intent intent = new Intent();
        ComponentName componentName = new ComponentName(this,Main2Activity.class);
        intent.setComponent(componentName);
        //等价于 Intent intent = new Intent(this,Main2Activity.class);
        startActivity(intent);
    }

点击后成功跳转到Main2Activity


接着测试Action&Category

测试之前准备一个Main3Activity,用于跳转,内容可以什么都不写,为了标识,加了个TextView。

同样准备一个Button,并添加点击事件

    <Button
        android:id="@+id/button_action"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="actionClick"
        android:text="通过Action查找组件"
        app:layout_constraintTop_toBottomOf="@id/button"
        />

书写点击事件方法

    //隐式(通过action)
    public void actionClick(View view){
        Intent intent = new Intent();
        //参数为字符串,理论上可以随便写,规范点加上包名(大雾)
        intent.setAction("com.example.a3_30intent.action.MY_ACTION");
        //同样可以合并成一行 Intent intent = new Intent("com.example.a3_30intent.action.MY_ACTION");
        //指定category
        //intent.addCategory("com.example.a3_30intent.action.MY_Category");
        startActivity(intent);
    }

进入配置清单文件(AndroidManifest.xml)

在Main3Activity部分加入intent-filter

 定义子项action,为其name赋值com.example.a3_30intent.action.MY_ACTION(setAction的那个参数)

需要注意的:

  • 一个Activity是可以指定多个action属性的
  • 一个Activity也可以指定多个category属性
  • 一个Intent只能指定一个action
  • 一个intent可以指定多个category

最后还要加上一句配置category的语句(可以暂时理解成默认格式….)

注意:如果当前组件是Activity,并且没有指定的category,必须加上category并使用默认的DEFAULT!!!如下:

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

        <activity android:name=".Main3Activity">
            <intent-filter>
                <action android:name="com.example.a3_30intent.action.MY_ACTION"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
            </intent-filter>
        </activity>

点击后成功跳转到Main3Activity

可以试着在之前的Main2Activity的配置文件也加入

<action android:name="com.example.a3_30intent.action.MY_ACTION"></action>
<category android:name="android.intent.category.DEFAULT"></category>

即,有两个符合的Activity,

可以看到系统会弹出选择框,示意你从两个中选一个…(感觉还是给Main3Activity设置一下label比较好…不然名字显示是application的label…)

可以通过android:priority来设置优先级(本身是数字越大优先级越高,但是在此处,除非有一个数字为负数,比如一个为1,一个为2,优先级将视相同,即依旧会弹出提示框让用户选择)

 <activity
            android:name=".Main3Activity">
            <intent-filter android:priority="-1">
                <action android:name="com.example.a3_30intent.action.MY_ACTION"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
            </intent-filter>
        </activity>
        <activity
            android:name=".Main2Activity"
            android:label="@string/title_activity_main2"
            android:theme="@style/AppTheme.NoActionBar" >
            <intent-filter android:priority="2">
                <action android:name="com.example.a3_30intent.action.MY_ACTION"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
            </intent-filter>
        </activity>

接下来关于data&type

再准备一个Main4Activity,用于跳转,内容可以什么都不写。

依旧需要一个Button,并添加点击事件

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="componentClick"
        android:text="通过组件名称直接查找组件"
        app:layout_constraintTop_toTopOf="parent"
        />

书写点击事件方法

由于setdata参数是Uri,所以需要定义一个Uri。

    //隐式(通过data)一般配合action
    public void dataClick(View view){
        Intent intent = new Intent();
        intent.setAction("com.example.a3_30intent.action.MY_ACTION");
        //Uri data = Uri.fromFile();
        Uri data = Uri.parse("http://www.baidu.com");
        //参数为一个uri
        intent.setData(data);
        startActivity(intent);
    }

然后是配置文件

需要配置如下属性

android:scheme(协议,如http)

android:host(具体的地址,如本例中的www.baidu.com)
android:path(具体的路径,如果是www.baidu.com/abc/abc...则可以再补path,实际上好像path可以代替host?)

        <activity
            android:name=".Main4Activity"
            android:label="@string/title_activity_main4"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter android:priority="1">
                <action android:name="com.example.a3_30intent.action.MY_ACTION" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="http" android:host="www.baidu.com"></data>
            </intent-filter>
        </activity>

点击后成功跳转到Main4Activity

如果不使用自定义的action,而是使用默认的如intent.ACTION_VIEW

并在配置文件清单加入

<action android:name="android.intent.action.VIEW" />

可能还需要设置以下语句,否则飘红线…

<category android:name="android.intent.category.BROWSABLE"/>

可以看到会打开浏览器(也可能是出来一个选择,选用浏览器还是直接在Main4Activity打开)

访问百度…(← ←甭管地址栏,虽然访问的是www.baidu.com但是它自己重定向了)

下面再设置type

首先我们观察一下setData方法和setType方法在系统中的定义

    public @NonNull Intent setData(@Nullable Uri data) {
        mData = data;
        mType = null;
        return this;
    }


    public @NonNull Intent setType(@Nullable String type) {
        mData = null;
        mType = type;
        return this;
    }

可以发现,setData方法中默认Type被设置为null,setType方法中Data被设置为null

显然他俩是相互矛盾的,所以想两个方法该怎么办呢?

系统提供了一个

intent.setDataAndType()方法

    public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
        mData = data;
        mType = type;
        return this;
    }

我们使用它就可以同时对type和data进行设置了

测试代码如下:

        intent.setDataAndType(data,"text/html");

配置文件:

        <activity
            android:name=".Main4Activity"
            android:label="@string/title_activity_main4"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter android:priority="1">
                <action android:name="com.example.a3_30intent.action.MY_ACTION" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE"/>
                <data android:scheme="http" android:host="www.baidu.com" android:mimeType="text/html"></data>
            </intent-filter>
        </activity>

测试效果和原来没有差别,所以实际上type并不常用….效果不算明显