Android学习

[Android] Intent详解

2017-04-28  本文已影响1640人  seven_Android

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>

这里值得注意的几个点:

<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");
匹配规则

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

data的构成

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

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

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

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

上一篇下一篇

猜你喜欢

热点阅读