美文网首页Android学习
[Android] Intent详解

[Android] Intent详解

作者: seven_Android | 来源:发表于2017-04-28 22:44 被阅读1640次

    Intent 是 Android 非常常用的一个用于组件间互相通信的信息对象,常用于启动组件和传递数据,大部分的程序里都有着他的身影。Intent 的用法比较简单,看看示例代码自然而然就用了起来,相信很多人和我之前一样也是没有仔细的去了解过。本文总结了一下 Intent 的结构和用法,除了一般的用法外,像隐式 Intent 的匹配规则之类可能很多人都比较一知半解的内容也会进行深入。

    Intent 的包含信息与构造

    Intent 作为一个负责组件间传递消息的信息对象,最重要的就是其包含的信息。实际上无论是显式还是隐式,Intent 发出的时候,系统对应的行为正是由 Intent 所包含信息的组合决定。一个 Intent 所包含的信息如下图:

    Intent包含信息

    Intent 的各部分信息均为可选值,但是在使用 Intent 的时候,会根据 Intent 设定的信息组合来决定对应行为。先来看看 Intent 的构造方法和一些常用设定信息的方法

    Intent 常用构造方法:

    | 方法 | 描述 |
    |::|:-----|
    |Intent() | 构造一个空 Intent |
    | Intent(String action)| 构造一个指定 action 的 Intent |
    | Intent(String action,Uri uri)| 构造一个指定 action 和 uri(相当于同时设定了 data)的 Intent|
    | Intent(Context packageContext,Class<?> cls)| 构造一个指定目标组件的 Intent,显式 Intent 的主要构造方法 |

    常用的设定信息方法:

    | 方法 | 描述 |
    |::|:-----|
    |setAction(String action)|指定 action|
    |setClass(Context packageContext, Class<?> cls)|指定目标组件类名|
    |setData(Uri data)|设置 Data 的 uri|
    |setType(String type)|设置 Data 的 MIME 类型|
    |setDataAndType(Uri data, String type)|同时设置 Data 的 uri 与 MIME 类型|
    |addCategory(String category)|添加一项 Category,Intent 可有多个 Category|
    |addFlags(int flags)|设置 Flag,决定目标组件的启动方式|
    |putExtra(String name, 基本类型和序列化类 value)|放入附加数据,参 2 可以是各种基本类型,及序列化后的自定义类|
    |putExtras(Bundle extras)|把封装了数据信息的 Bundle 对象放入 Intent|

    *若要同时设置 URI 和 MIME 类型,请勿调用 setData() 和 setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。

    目标组件名称

    显式 Intent 的必要信息,指定后 Intent 仅传给指定的组件(也就是所谓显性 Intent),否则系统会根据其他信息筛选出可以响应 Intent 的所有组件(即隐性 Intent)。可以使用 setComponent()、setClass()、setClassName() 或 Intent 构造函数设置组件名称。
    ** 由于安全性原因,在5.0后的系统,启动 Service 时,应始终指定组件名称,否则会报错。**

    Action

    指 Intent 发向的组件的主要动作,比如:图片应用中主要动作为查看图片的组件、地图应用中主要动作为查看地址的组件。另外,对于广播(Broadcast)组件而言,Intent 的 action 则是指广播具体的值。当 Broadcast Receiver 接收到该值时代表了某事件已经发生。
    通常使用的主要是 Android 系统内置 action,这些 action 实际上是保存在 Intent 类中的静态常量,系统的默认组件(如:默认浏览器、图片浏览器、拨号页面等)都可以响应相应的 action。下面给出几个比较常见内置 action。

    ACTION_VIEW
    向用户展示某信息,比如使用浏览器打开网址,用图片应用显示图片等

    ACTION_SEND
    用于发送数据,比如电子邮件应用或者一些社交应用。

    ACTION_DIAL
    显示带拨号盘的页面,让用户可以进行拨号动作。

    有关更多定义通用操作的常量,请参阅 Intent类参考文档。

    除了 Android 内置的 action 之外,也可以自定义action, 供 Intent 在自己的应用内使用(或者供其他应用在自己的应用中调用组件)。如果定义自己的操作,请确保将应用的软件包名称作为前缀。 例如:

    static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
    

    Data

    包含了 URI 对象和 memitype 两个部分,分别是待操作数据的引用 uri,以及待操作数据的数据类型。两部分均为可选,但是要注意同时设置时应该使用 setDataAndType()方法,防止互相抵消。
    Data 内容一般由 action 决定,比如 action 为 ACTION_VIEW,那么 Data 就可以是一个网址,也可以是图片之类的数据 uri。
    同时指定 Uri 和 MIME 类型有助于 Android 系统找到接收 Intent 的最佳组件,例如可以响应 ACTION_VIEW 的组件可能有非常多,浏览器、播放器、图片应用等等。此时设置mimeType"image/jpeg"或者video/mp4,则系统可以筛选出更合适的响应组件。

    Category

    目标组件的类型信息字符串,一个 Intent 可以添加多个 Category 。以下是比较常见的 Category:

    CATEGORY_BROWSABLE
    目标 Activity 允许本身通过网络浏览器启动,以显示链接引用的数据,如图像或电子邮件。

    CATEGORY_LAUNCHER
    该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。

    需要注意的是大部分 Intent 虽然不需要设置 Category,但是在调用使用 Intent 的方法(如starActivity()等)的时候,会默认为该 Intent 添加 CATEGORY_DEFAULT,相应目标组件的Intent过滤器应该添加该类别,具体会在下文 2、Intent的用法 中详述。

    以上 4 种(组件类名、Action、Data、Category)是会影响系统对 Intent 的解析从而决定最终启动那个组件的信息,以下两种(Extra、Flag)属于附加的信息,不影响系统解析启动那个组件

    Extra

    Intent 携带附加数据,也是组件间互相传递信息比较常见的做法。使用各种 putExtra()方法添加 extra 数据,每种方法均接受两个参数:键名和值。还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras()将 Bundle 插入 Intent 中。
    具体用法在下文 3、数据传送 中详述。

    Flag

    指示 Android 系统如何启动 Activity,例如,Activity 应属于哪个任务,以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。

    Intent 构造示例:

        Intent intent = new Intent(this,TagerActivity.class);//显式 Intent 构造示例
    
        //隐式Intent构造示例
        Intent intent=new Intent(Intent.ACTION_VIEW);//设定 action 为展示内容
        intent.setData(Uri.parse("http://www.baidu.com"));//设置 data 的 uri 为一个网址
        intent.addCategory(Intent.CATEGORY_LAUNCHER);//目标组件为某个应用的首页面
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);//目标 Activity 的启动方式为 single top
        intent.putExtra("extra_my","Tom");//附加 String 数据
    

    Intent的用法

    Intent 主要分为显式 Intent 和隐式 Intent,根据显隐用法不同。

    Intent 使用流程

    常用发送 Intent 启动组件方法:

    | 方法 | 描述 |
    |::|:-----|
    |startActivity() | 启动 Activity|
    |startActivityForResult()| 启动 Activity,该 Activity 销毁后会回调到上个活动的 onActivityResult() 方法 |
    | bindService()| 启动 Service,5.0后只能接收显性 Intent 作为参数|
    |sendBroadcast()| 发送标准广播 |
    |sendOrderedBroadcast()| 发送有序广播 |

    显式

    显式 Intent 通常应用在自己的程序中,启动特定组件。用法比较简单,就是构造一个带有目标组件名的 Intent,作为参数传入上述方法即可,调用方法后会直接启动相应组件。
    值得注意的就是在5.0之后的系统, Service 只能通过显式 Intent 启动

    隐式

    隐式 Intent 允许启动其他应用中的组件,在调用发送 Intent 的方法后,该 Intent 会交由 Android 系统进行匹配,(匹配根据信息是 action、data、category 这3项,即本文第一张图片 Intent 包含信息中标蓝色部分)筛选出整个设备可响应该 Intent 的组件。下图官方文档对隐式 Intent 如何传递启动其他应用组件的图解:

    隐式 Intent 如何通过系统传递以启动其他 Activity 的图解: [1] Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。 [2] Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。 找到匹配项之后, [3] 该系统通过调用匹配 Activity(Activity B)的 onCreate() 方法并将其传递给 Intent,以此启动匹配 Activity。
    Intent过滤器

    Intent 过滤器是 manifests 里组件的子标签<intent-filter>,一个控件可以声明一个或者**多个 **Intent 过滤器,只要其中一个通过匹配,该组件就可以相应相应 Intent。先来看一个官方给出的 Intent 过滤器示例:

    <activity android:name="MainActivity">
        <!-- 应用的首页面,会显示在启动器中 -->
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    <activity android:name="ShareActivity">
        <!-- 该活动可以处理 SEND这个 aciton,且处理数据类型为无格式文本 -->
        <intent-filter>
            <action android:name="android.intent.action.SEND"/>
         <!--需要响应隐式Intent的活动必须添加 android.intent.category.DEFAULT这个分类,因为starActivity()方法会默认为Intent添加-->
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:mimeType="text/plain"/>
        </intent-filter>
        <!-- 此为同一个 Activity 的第二个过滤器  该活动可以处理 "SEND" 和 "SEND_MULTIPLE"两种 aciton  处理数据类型为多媒体数据(包括图片、视频和全景照片) -->
        <intent-filter>
            <action android:name="android.intent.action.SEND"/>
            <action android:name="android.intent.action.SEND_MULTIPLE"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:mimeType="application/vnd.google.panorama360+jpg"/>
            <data android:mimeType="image/*"/>
            <data android:mimeType="video/*"/>
        </intent-filter>
    </activity>
    

    这里值得注意的几个点:

    • 1 Intent 只能有1个 action,而 Intent 过滤器可有多个 action
    • 2 需要响应隐式 Intent 的 Activity 的 Intent 过滤器中必须添加 android.intent.category.DEFAULT这个分类
    • 3 data 在构造时传入的是 Uri 对象以及 mimeType 字符串,mimeType 的对应关系比较直观不详述,Uri 对象会被分为 4 个部分<scheme>://<host>:<port>/<path>(<协议>://<主机名>:<端口>/<路径>),在 Intent 过滤器中的写法如下:
    <intent-filter>
           <data 
               android:scheme="content" android:host="com.example.project" 
               android:port="200" android:path="/folder/subfolder/etc"/>
          </intent-filter>
    

    对应的Uri对象:

    Uri uri=Uri.parse("content://com.example.project:200/folder/subfolder/etc");
    
    • 4 path 和 mimeType 允许使用*通配符,实现部分匹配。

    • 5 对于所有 Activity,必须在清单文件中声明 Intent 过滤器。
      但是,广播接收器的过滤器可以通过调用 registerReceiver() 动态注册。 且可以使用 unregisterReceiver() 注销该接收器。这样一来,应用便可仅在应用运行时的某一指定时间段内侦听特定的广播。

    匹配规则
    • 隐式 Intent 会与所有设备所有应用组件的 Intent 过滤器进行比对。

    • 一个组件可以有多个 Intent 过滤器,某个 Intent 只需与其中任何一个匹配即可启动组件。

    • 系统会根据 Intent 与 Intent 过滤器的 action、data、category 进行三次比对(测试),要全部通过才能匹配。

    • action 的匹配规则:
      A、Intent 只能包含一项 action,而 Intent 过滤器可以包含多项 action,只要 Intent 的 action 可以在 Intent 过滤器中找到对应项即可通过测试。
      B、当 Intent 过滤器未声明任何 action 时,任何 Intent 都不能通过匹配。
      C、当 Intent 未指定 Action 时,无法通过匹配。

    • category 的匹配规则:
      A、Intent 可以包含多项 category,Intent 过滤器也可以包含多项 category。Intent 中的每项 category 必须在Intent过滤器中都有对应项,才能通过匹配。
      B、当目标组件为 Activity 时,如需相应隐式 Intent,必须添加 "android.intent.category.DEFAULT" 到 Intent 过滤器中。因为启动Activity的方法都会默认为Intent添加该 category。
      C、目标组件为广播时,Intent 和 Intent 过滤器都不设置 category,可通过匹配。

    • data的匹配规则:

    和上面两个元素不同,data 具有子元素,其构成如下图:

    data的构成

    在 Intent 中传入的 Uri 对象会被解析成<scheme>://<host>:<port>/<path>(<协议>://<主机名>:<端口>/<路径>)4 个部分进行匹配测试。4 项均为可选,但是存在线性依赖关系:
    如果未指定 scheme,则会忽略 host;
    如果未指定 host,则会忽略 port;
    如果未指定 scheme 和 host,则会忽略 path。

    data 的匹配规则主要有以下几点:

    A、将 Intent 中的 URI 与过滤器中的 URI 规范进行比较时,它仅与过滤器中包含的部分 URI 进行比较。 例如:

    • 如果过滤器仅指定 scheme,则具有该 scheme 的所有 URI 均与该过滤器匹配。
    • 如果过滤器指定 scheme 和 host,但未指定 path,则具有相同 scheme 和 host 的所有 URI 都会通过过滤器,无论其 path 如何均是如此。
    • 如果过滤器指定 scheme、host和path,则仅具有相同 scheme、host 和 path 的 URI 才会通过过滤器。
    • path 部分可以使用*通配符,仅需部分匹配路径名即可。

    B、mimeType 可以部分使用通配符,如:image/*(表示匹配所有格式图像数据),也可以全部使用*/* 表示匹配所有类型数据。

    C、当Intent同时不指定 uri 与 mimeType 时,只有同样未声明 uri 与 mimeType 的 Intent 过滤器可以通过匹配。

    D、当 Intent 只含有 uri 时,只有声明 uri 相互匹配,且未声明 mimeType 的 Intent 过滤器可以通过匹配。

    E、当 Intent 只含有 mimeType 时,只有 mimeType 相互匹配,且未声明 uri 的 Intent 过滤器可以通过匹配。

    F、当 Intent 同时含有 uri 和 mimeType 时,只有两部分均匹配的 Intent 过滤器可以通过匹配。

    G、Intent过滤器只声明mimeType时,默认支持scheme为content: 和 file: 的uri。

    H、当 Intent 传入的 uri 为 content: URI 时,表明数据位于设备中,且由 ContentProvider 控制,此时即使不设置 mimeType,mimeType 也对系统可见。

    非空判断

    当隐式 Intent 发出而找不到匹配 Activity 时,调用将会失败,且应用会崩溃。要验证是否存在会接收 Intent 的 Activity ,可以对 Intent 对象调用 resolveActivity()。如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()。 如果结果为空,则不应使用该 Intent,应停用发出该 Intent 的功能。

    Intent sendIntent = new Intent();
    sendIntent.setAction(Intent.ACTION_SEND);
    sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
    sendIntent.setType("text/plain");
    
    // 判断是否存在能够匹配该 Intent 的 Activity
    if (sendIntent.resolveActivity(getPackageManager()) != null) {
        startActivity(sendIntent);
    }
    

    强制使用选择器

    当存在多个 Activity 响应 Intent 时,系统会弹出默认选择器,供用户选择启动 Activity,且用户可以选择为该操作设定默认选项。

    默认选择器,有just once和always选择让用户决定是否设定默认选项

    根据应用场景,有一些 Intent 可能并不适合存在默认选项。例如,当应用使用 ACTION_SEND 操作执行“共享”时,用户根据目前的状况可能需要使用另一不同的应用。此时可以强制显示选择器(显式的调用选择器),选择器对话框每次都会要求用户选择用于操作的应用(用户无法为该操作选择默认应用)。
    具体使用createChooser()方法,接收两个参数,第一个是包含信息的 Intent,第二个是选择器的标题。

        Intent viewIntent=new Intent();
        viewIntent.setAction(Intent.ACTION_VIEW);
        //createChooser()方法,显式调用选择器,用户将无法设定默认选项
        Intent intent=Intent.createChooser(viewIntent,"应用选择器");
        //判断该Intent是否存在可响应Activity,用未使用createChooser()方法的原Intent进行判断
        if (viewIntent.resolveActivity(getPackageManager())==null) return;
        startActivity(intent);
    
    createChooser()方法生成的选择器

    数据传送

    Intent 作为组件间的信息对象,另一个主要作用就是数据的传送。
    Intent传送数据是以键值对的形式,主要通过putExtra()方法,该方法接收两个参数,第一个是数据的键,
    第二个是数据的值。第二个参数的取值范围包括8种基本数据类型和 String、CharSequence,以及他们的数组,另外还可以是 Parcelable(包含数组)、Serializable,以及包含数据的 Bundle 对象。

    基本类型的传出与回传

    在目标组件中取出 Intent 的方法根据数据类型有非常多,这里不一一列举,只给出一般格式。

    方法 描述 默认取值
    getXxxExtra(String name,基本数据类型 默认值) Xxx 为基本数据类型 Extra 中无对应键名时,取方法参数 2
    getXxxExtra(String name) Xxx为Srting等引用类型 Extra 中无对应键名时,取值 null
    getBundleExtra(String name) 取出Bundle对象的方法 Extra 中无对应键名时,取值 null
    getXxxArrayExtra() 取出Xxx类型对应数组方法 Extra 中无对应键名时,取值 null

    ps:方法使用驼峰法,单词间首字母大写。

        //添加 Extra 示例
        Intent intent = new Intent(this,TagerActivity.class);
        intent.putExtra("extra_int",1000);
        int[] ints={1,2,3};
        intent.putExtra("extra_int_array",ints);
        intent.putExtra("extra_string","teger Activity");
        startActivity(intent);
    
    ------------------------------------------
        //对应取出数据示例
        Intent intent=getIntent();
        int i=intent.getIntExtra("extra_int",0);
        int[] ints=intent.getIntArrayExtra("extra_int_array");
        String a=intent.getStringExtra("extra_string");
    
    • 数据回传

    把数据传回上个活动也是 Intent 比较常用的方法之一。
    实现的方式是在启动活动时使用startActivityForResult(Intent intent,int requestCode)代替startActivity(Intent intent),当被启动活动销毁时,就会携带一个 Intent 回调到调用startActivityForResult()方法的Activity的startActivityForResult() 方法。关于 Intent 添加 Extra,和数据的取出和普通的用法并没有什么区别。
    基本的步骤是:
    A、调用startActivityForResult()方法启动新 Activity,该方法有两个参数,参 1 为 Intent,参 2 为 int 类型的唯一请求码,用于判断数据来源。
    B、在新 Activity 中调用setResult()方法把携带希望回传数据的 Intent 作为参数传入。
    C、重写原 Activity 的onActivityResult() 方法。该方法携带 3 个参数,参 1 为启动 Activity 的请求码 requestCode,参 2 为表示处理结果是否成功的 resultCode,参 3 为携带数据的 Intent。
    重写的主要逻辑是:先通过 requestCode 判断数据来源(根据场景,原 Activity 可能启动不同新 Activity);然后通过 resultCode 判断处理结果是否成功;最后取出 Intent 中数据进行处理即可。

        //原 Activity 中启动新 Activity 并请求返回数据
        Intent intent = new Intent(this,TagerActivity.class);
        startActivityForResult(intent,1);
    
    ------------------------------------
    
        //新 Activity 中设定返回 Intent 并销毁,销毁后会回调到原 Activity 的 onActivityResult()方法
        Intent intent=new Intent();
        intent.putExtra("extra_boolean",true);
        setResult(RESULT_OK,intent);
        finish();
    
    ------------------------------------
    
      //原 Activity 中重写 onActivityResult() 方法
      @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode){
          case 1:
            if (resultCode==RESULT_OK){
              boolean b=data.getBooleanExtra("extra_boolean",false);
            }
            break;
          default:
        }
      }
    

    自定义类型序列化传送

    使用 Intent 来传递自定义对象主要有两种实现方式:Serializable 和 Parcelable。
    Serializable 是序列化的意思,是指将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。
    Parcelable 也可以实现相同效果,其实现原理是将一个完整的对象进行分解,而分解后的每一部分都是 Intent 所支持的数据类型,这样也就实现传递对象的功能了。
    两种方式对比下 Serializable 比 Parcelable 效率偏低,Parcelable 实现虽然相对复杂,但是在 parceler之类的第三方库支持下也变得非常简单,所以通常情况比较推荐用 Parcelable 的方式实现 Intent 传递对象。

    自定义类实现 Serializable 示例:

    public class User implements Serializable {
      private int id;
      private String name;
    
      public int getId() {
        return id;
      }
    
      public void setId(int id) {
        this.id = id;
      }
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    }
    

    非常简单,只要让自定义类实现 Serializable 接口即可。

    Parcelable 方式比 Serializable 多出几个步骤:1、重写 describeContents() 返回 0 即可;2、重写writeToParcel() 写出字段;3、提供一个 CREATOR 常量。

    自定义类实现Parcelable示例:

    public class User implements Parcelable {
      private int id;
      private String name;
    
      public int getId() {
        return id;
      }
    
      public void setId(int id) {
        this.id = id;
      }
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    
      //步骤1:重写 describeContents()  返回0即可
      @Override public int describeContents() {
        return 0;
      }
    
      //步骤2:重写 writeToParcel() 写出字段
      @Override public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(id);
      }
    
      //步骤3:提供一个 CREATOR 常量
      public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
        @Override public User createFromParcel(Parcel source) {
          User user = new User();
          user.id = source.readInt();
          user.name = source.readString();
          return user;
        }
    
        @Override public User[] newArray(int size) {
          return new User[size];
        }
      };
    }
    

    添加 Extra 与取出示例:

        User user=new User();
        user.setId(1);
        user.setName("Tom");
    
        Intent intent = new Intent(this,TagerActivity.class);
        intent.putExtra("extra_user_data",user);
        startActivity(intent);
    ------------------------------------------
        Intent intent=getIntent();
        //如果放入自定义类使用了 Serializable 方式,则改用 getSerializableExtra() 方法 ,另外需要取出数据需要转型
        User user=(User)intent.getParcelableExtra("extra_user_data");
    

    和基本类型的放入取出基本相同,只需要注意转型即可。


    本文主要参考自: https://developer.android.com/guide/components/intents-filters.html

    相关文章

      网友评论

        本文标题:[Android] Intent详解

        本文链接:https://www.haomeiwen.com/subject/ipppzttx.html