Intent

1、什么是Intent

Intent,中文可翻译为“意图”,可用于Android同个应用程序中各个组件之间的交互,或者不同应用程序之间的交互。可以用来表明当前组件的思想和意图,比如想执行某个动作,想发送某些数据等等。每个组件都有不同的启动方法:

  • Activity:可以调用startActivity() 或 startActivityForResult() 传递 Intent来打开新的Activity;
  • Service:可以调用startService()传递 Intent 来启动服务,也可通过 bindService() 传递 Intent 来绑定到该服务;
  • Broadcast,可以调用sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 等方法传递 Intent 来发起广播;

Intent分为显式Intent和隐式Intent,我们以打开新的Activity为例进行讲解。

1.1 显示Intent

显式指定意图,系统就会根据我们指定的意图,打开我们想打开的Activity。Intent有如下构造函数:Intent(Context packageContext, Class<?> cls)。其中第二个参数就是指定想启动的Activity。

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

通过上面两行代码,我们可以显式指定打开SecondActivity.class。

1.2 隐式Intent

不指定特定的意图,而是通过清单文件里,每个Activity节点下事先配置好的(也就是“Intent过滤器”),由系统根据我们设定的Intent,从系统所有Activity中选出最符合的我们要求的Activity。先看看例子:

// 清单文件中 XuruiActivity 提前声明好如下:
    <activity android:name=".XuruiActivity">
        <intent-filter>
            <action android:name="com.example.android.xuruitest"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="com.example.android.xuruicategory"/>
        </intent-filter>
    </activity>
// 代码调用
    Intent intent = new Intent();
    intent.setAction(com.example.android.xuruitest);
    intent.addCategory(com.example.android.xuruicategory);
    startActivity(intent); //1

执行 [注释1]的代码后,系统会发现XuruiActivity的所设定的内容,和当前Intent所设定的内容最匹配,系统就会打开XuruiActivity,但这个过程中,我们并没有显式的指出打开XuruiActivity,而是通过设置了一些特定条件进行匹配,如“action”,“category”等,从而隐式地打开了XuruiActivity。

1.3 Intent的组成部分

“action”,“category”都是Intent的组成部分。为了更好的理解隐式Intent,需要了解一个Intent由几部分组成:

  • componentName:目的组件
  • action(动作):用来表现意图的行动
  • category(类别):用来表现动作的类别
  • data(数据):表示与动作要操纵的数据
  • type(数据类型):对于data范例的描写
  • extras(扩展信息):扩展信息
  • Flags(标志位):期望这个意图的运行模式

1.3.1 componentName

指定Intent目标组件的类名。可以这么记得,如果直接指定了componentName,那就是显式Intent。我们可以通过setComponent()、setClass()、setClassName()等方法指定:

// 1、Intent构造
Intent intent = new Intent(this, SecondActivity.class);  
startActivity(intent); 

// 2、setComponent()方法
ComponentName componentName = new ComponentName(this, SecondActivity.class);  //2
ComponentName componentName = new ComponentName(this, "com.example.android.SecondActivity");  //3 
ComponentName componentName = new ComponentName(this.getPackageName(), "com.example.android.SecondActivity");  //4 
  
Intent intent = new Intent();  
intent.setComponent(componentName);  
startActivity(intent); 

// 3、setClass/setClassName方法
Intent intent = new Intent();  
  
intent.setClass(this, SecondActivity.class);  
intent.setClassName(this, "com.example.android.SecondActivity");  
intent.setClassName(this.getPackageName(), "com.example.android.SecondActivity");  
      
startActivity(intent);  

我们看看[注释2-4]这三种方式:

  • [注释2]:this是当前Activity,直接写类名,则该Activity必须在同个包名内;
  • [注释3]:this是当前Activity,类名包括包名,则可以在当前包跳转到其他包的Activity;
  • [注释4]:this是当前Activity所在的应用程序,类名包括包名,则可以在当前包跳转到其他包的Activity;

在可以使用应用的Context代替Activity的Context时,都推荐使用应用的Context,且Activity名称前面加上包名可以打破同个包内的限制,所以推荐使用[注释4]的方式。

1.3.2 action

用来表示意图的行动,一个Intent只能有一个Action。以下是一些比较常见的Action。所有的Action可以在Android开发手册-Intent 查看。

Action 含义
ACTION_MAIN Android 的程序入口
ACTION_VIEW 显示指定数据
ACTION_EDIT 编辑指定数据
ACTION_DIAL 显示拨号面板
ACTION_CALL 直接呼叫 Data 中所带的号码
ACTION_ANSWER 接听来电
ACTION_SEND 向其他人发送数据(例如:彩信/email)

打开手机的拨号界面的代码可以这么写:

    Intent intent=new Intent();
    intent.setAction(Intent.ACTION_DIAL);
    intent.setData(Uri.parse("tel:1234567"));
    startActivity(intent);

1.3.3 category

用来表示动作的类别,一个Intent可以有多个category,类别越多,代表动作越具体,意图越明确。

1.3.4 data

清单文件里标签具体如下:

属性 解释
scheme 协议
host 主机
port 端口
path 用来匹配完整的路径
pathPrefix 用来匹配路径的开头部分
pathPattern 用表达式来匹配整个路径
mimeType 用来匹配数据类型或MIME类型

举个例子:

<data android:scheme="http"  android:host="www.baidu.com"/>

系统内置的几个 Data 属性常量:

协议 含义
tel: 号码数据格式,后跟电话号码
mailto: 邮件数据格式,后跟邮件收件人地址
smsto: 短信数据格式,后跟短信接收号码
file:/// 文件数据格式,后跟文件路径。注意必须是三根斜杠 ///
content:// 内容数据格式,后跟需要读取的内容。ContentProvider 特有的格式

1.3.5 type

type 属性用于指定 data 所制定的 Uri 对应的 MIME 类型,通常应用于调用系统 App,比如实现查看文件(文本、图片、音频或者视频等),通过指定文件的 MIME 类型,可以让系统知道用什么程序打开该文件。

常用的 MIME 类型

文件格式 对应的MIME类型
.bmp image/bmp
.gif image/gif
.png image/png
.tif .tiff image/tiff
.jpe .jpeg .jpg image/jpeg
.txt text/plain
.xml text/xml
.html text/html
.css text/css
.js text/javascript
.mht .mhtml message/rfc822
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.rtf application/rtf
.xls application/vnd.ms-excel application/x-excel
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.ppt application/vnd.ms-powerpoint
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.pps application/vnd.ms-powerpoint
.ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow
.pdf application/pdf
.swf application/x-shockwave-flash
.dll application/x-msdownload
.exe application/octet-stream
.msi application/octet-stream
.chm application/octet-stream
.cab application/octet-stream
.ocx application/octet-stream
.rar application/octet-stream
.tar application/x-tar
.tgz application/x-compressed
.zip application/x-zip-compressed
.z application/x-compress
.wav audio/wav
.wma audio/x-ms-wma
.wmv video/x-ms-wmv
.mp3 .mp2 .mpe .mpeg .mpg audio/mpeg
.rm application/vnd.rn-realmedia
.mid .midi .rmi audio/mid

在清单文件中的里,只有没有。但在代码里就可以调用:

  • setType(): 调用后设置 mimeType,然后将 data 置为 null;
  • setData(): 调用后设置 data,然后将 mimeType 置为 null;
  • setDataAndType(): 调用后才会同时设置 data 与 mimeType。

举个打开百度的例子:

<intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:scheme="http"  android:host="www.baidu.com"/>
</intent-filter>
    intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    Uri data=Uri.parse("http://www.baidu.com");
    intent.setData(data);
    startActivity(intent);

1.3.6 extras

extras:拓展信息,通常用于多个Activity之间的数据交换,extras属性是一个Bundle对象,通过键值对,对数据进行存储。extras在日常开发中非常常见:

Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("name", "Tony");
intent.putExtra("age", 28);
startActivity(intent);

1.3.7 flags

flags:标志位,指明当前的Intent所期望的运行模式,具体有:

  • FLAG_ACTIVITY_CLEAR_TOP:新跳转的Activity如果已经在栈中,则将其上面的所有Activity都清出栈,相当于Activity四大启动模式中的singleTask;
  • FLAG_ACTIVITY_SINGLE_TOP:返回栈没有当前需要跳转的Activity,则新建该Activity并放入栈顶。但如果该Activity已经在栈顶了,则不会再重新创建新的Activity。相当于Activity四大启动模式中的singleTop;
  • FLAG_ACTIVITY_NEW_TASK:假设当前返回栈有A B C这3个Activity。此时C通过intent跳转到D,并且这个intent添加了FLAG_ACTIVITY_NEW_TASK标记,如果D这个Activity在AndroidManifest.xml中的声明中添加了Task affinity,系统首先会查找有没有和D的Task affinity相同的task栈存在,如果有存在,将D压入那个栈,如果不存在则会新建一个D的affinity的栈将其压入。但如果D的Task affinity默认没有设置,则会把其压入原来的返回栈,变成:A B C D,这样就和不加FLAG_ACTIVITY_NEW_TASK标记效果是一样的了。注意如果试图从非activity的非正常途径启动一个activity,比如从一个service中启动一个activity,则intent比如要添加FLAG_ACTIVITY_NEW_TASK标记。

1.4 隐式Intent匹配规则总结

隐式Intent的匹配规则,就是根据当前Intent所设置的action、category、data和清单文件中使用 元素为组件声明一个或多个 Intent 过滤器的、、几个属性做匹配,找到最合适的。总结如下:

  • 清单文件中:category如要使用默认值,则设为android.intent.category.DEFAULT,否者组件不会接收隐式 Intent;
  • 如果不希望自己的组件,如自定义的XXXActivity被其他应用程序调用,则清单文件中,XXXActivity下面不要声明 ,并且将该组件的 exported 属性设置为 false;
  • 代码中:如果没有指定category,系统会默认设置为android.intent.category.DEFAULT;
  • 一个Intent只有一个action,但可以有多个category,只有当action和所有category和某个都同时匹配,才算匹配成功。
  • 一个可以设置一个或多个,当Intent能和的任意一个action匹配就算匹配成功;
  • 一个可以设置一个或多个,只有当Intent能和的所有category匹配才算匹配成功;
  • 如果有多个组件被匹配成功,就会以对话框列表的方式让用户进行选择。

1.5 显示Intent与隐式Intent的区别

对明确指出了目标组件名称的Intent,我们称之为“显式Intent”。 对于没有明确指出目标组件名称的Intent,则称之为“隐式 Intent”。

2、拓展

2.1 Intent携带数据的大小

Intent传递数据大小的限制大概在1M左右,超过这个限制就会静默崩溃。所以,如果是同个应用程序之间的数据传输,可以使用文件缓存或者磁盘缓存,如果是应用程序之间,则推荐使用ContentProvider进行数据传输。

2.2 PendingIntent

PendingIntent就是延迟或者挂起的Intent,平时也会使用到,在此做个拓展。如接收到消息需要在桌面做通知时,可以这么写:

pendingIntent = PendingIntent.getActivities(
            applicationContext, 0, intents,
            PendingIntent.FLAG_UPDATE_CURRENT
        )

 val builder = NotificationCompat.Builder(applicationContext, FORCE_UPDATE_NOTIFY_CHANNEL_ID)
            builder.setWhen(System.currentTimeMillis())
            .setContentTitle(showTitle)
            .setContentText(showContent)
            .setContentIntent(pendingIntent) //5
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setCategory(NotificationCompat.CATEGORY_MESSAGE)
            .setVisibility(VISIBILITY_PUBLIC)
            .setAutoCancel(true)
            .setSmallIcon(R.drawable.ic_launcher)

其中,[注释5]的pendingIntent就是PendingIntent。PendingIntent的具体用法和实现原理,推荐看看这篇文章:Android 面试题:说一下 PendingIntent 和 Intent 的区别