在前 4 篇文章中,咱们介绍了 Android 四大组件的基础知识,四大组件是构成咱们 App 的基础,也是 Android 系统设计的最佳体现。各个组件之间彻底是解耦的,若是想访问其余组件或者启动其余组件可使用 Intent
来操做。在四种组件类型中,有三种(Activity、Service 和 Broadcast)都可以经过异步消息 Intent 进行启动。Intent 会在运行时对各个组件进行互相绑定。因此咱们能够把 Intent
看成是各个组件之间的信使(不管该组件是本身 App 的仍是其余 App)。javascript
每一个组件都有不一样的启动方法:css
如要启动 Activity,能够用 startActivity() 或 startActivityForResult() 传递 Intent(须要 Activity 返回结果时)html
如要启动 Service,能够经过 startService()传递 Intent 来启动服务,也可经过 bindService() 传递 Intent 来绑定到该服务java
如要发送 Broadcast,能够经过 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 等方法传递 Intent 来发起广播android
与 Activity、Service 和 Broadcast,ContentProvider 并不是由 Intent 启动。相反,它们会在成为 ContentResolver 的请求目标时启动。想要访问 ContentProvider 的内容,能够经过 ContentResolver 调用 query()、insert()、update()、delete()等方法。浏览器
Intent
是一个消息传递对象,用来向其余组件请求操做,不管该组件是当前应用仍是其余应用。具体来讲包含三大用处: 启动一个 Activity、启动或者绑定 Service、传递广播。安全
Intent 分为两种类型:app
假设有一个 Activity A 须要启动一个 Activity B。若是经过显示 Intent 启动时,系统会当即启动该组件。使用隐式 Intent 时,Android 系统经过将 Intent 的内容与在设备上其余应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。若是 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。若是多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。大体流程以下图:异步
特别注意:ide
为了确保应用的安全性,启动 Service 时,必须使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,由于没法肯定哪些服务将响应 Intent,且用户没法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,若是使用隐式 Intent 调用 bindService(),系统会抛出异常。
一个 Intent 对象会携带一些信息,Android 系统会根据这些信息来肯定要启动哪一个组件。具体来讲会携带如下七种信息: ComponentName、Action、Category、Data 、Type、Extra、Flags
。咱们能够把这七种信息称之为 Intent 的七大属性
。下面咱们具体的来分析每个属性的含义和使用方法:
ComponentName:要启动的组件名称
指定了ComponentName 属性的 Intent 已经明确了它将要启动哪一个具体组件,所以这种 Intent 被称为显式 Intent,没有指定 ComponentName 属性的 Intent 被称为隐式 Intent。隐式 Intent 没有明确要启动哪一个组件,应用会根据 Intent 指定的其余信息去启动符合条件的组件。代码示例://声明一个显示意图
Intent intent = new Intent();
ComponentName componentName = new ComponentName(MainActivity.this,SecondActivity.class);
intent.setComponent(componentName);
startActivity(intent);
复制代码
除了经过 setComponent
为 intent 指定要启动的组件名称外,还可使用 Intent 的构造函数:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
复制代码
使用 Intent 构造函数设置组件名称的方式,代码更加的简洁。
Action:操做
当咱们要声明一个隐式意图的时候,就要用到 Action,它指定了被启动的组件要完成的具体的操做,好比我想启动一个能够浏览照片的 Activity,能够设置 Action 为 ACTION_VIEW
,或者启动一个能够发送邮件的 Activity,能够设置 Action 为 ACTION_SEND
。Action 一般是和 Category 结合起来使用。Action 自己是一个字符串,除了系统定义好的,咱们也能够本身定义,下面经过代码来演示经过定义一个隐式 Intent 来启动 Activity:step1:在启动方定义一个隐式 Intent
Intent intent = new Intent();
//自定义了一个action:com.cyy.jump
intent.setAction("com.cyy.jump");
//必须指定一个category
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
复制代码
step2:为被启动方配置 <intent-filter>
。具体作法是在 AndroidManifest.xml
中,为被启动的 Activity 设置上 :
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.cyy.jump" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
复制代码
在 <intent-filter>
中,咱们设置了两个属性:<action>
和 <category>
。的值必须与启动方的 setAction 的值同样:com.cyy.jump。
下面咱们运行一下项目,看看效果:
跳转成功。
假如咱们为多个 Activity 配置了一样的 会有什么效果呢?
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.cyy.jump" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="com.cyy.jump" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
复制代码
咱们运行一下,看看效果:
能够看到,在同一个应用内,若是有多个 Activity 设置了一样的 ,当在调用 startActivity() 后,系统会弹出一个选择框,让用户本身选择须要跳转的 Activity。
这是启动同一个应用内的页面,假设其余应用的某个 Activity 也设置了一样的 呢?咱们能够来试验一下,新建一个项目,名叫 IntentDemo2,在 IntentDemo2 中新建一个页面叫作 HomeActivity:
<activity android:name=".HomeActivity">
<intent-filter>
<action android:name="com.cyy.jump" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
复制代码
再次运行项目,看看效果:
能够看到,在对话框中显示出了 IntentDemo2 的选项,点击便可跳转 IntentDemo2 的 HomeActivity 页面。
以上是自定义的 Action,下面咱们来演示一下使用系统定义的 Action,以 ACTION_SEARCH
为例:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEARCH);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
复制代码
运行项目,查看效果:
此时,系统也会弹出全部知足 ACTION_SEARCH
的应用列表提供给用户选择。
系统定义的一些 Action:
Action | 含义 |
---|---|
ACTION_MAIN | Android 的程序入口 |
ACTION_VIEW | 显示指定数据 |
ACTION_EDIT | 编辑指定数据 |
ACTION_DIAL | 显示拨号面板 |
ACTION_CALL | 直接呼叫 Data 中所带的号码 |
ACTION_ANSWER | 接听来电 |
ACTION_SEND | 向其余人发送数据(例如:彩信/email) |
以上只是简单列举了一部分,更多的能够参考官方文档:Intent
Category:类别
Category 属性为 Action 增长额外的附加类别信息。经常使用的如 CATEGORY_DEFAULT
,表示 Android 系统中默认的执行方式,按照普通的 Activity 的执行方式执行。在好比 CATEGORY_LAUNCHER
,设置该组件为当前应用程序启动器中优先级最高的,一般与程序入口动做 ACTION_MAIN
配合使用:<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
复制代码
Data & Type:数据&类型
Data 属性一般是为 Action 属性提供要操做的数据,例如拨打指定电话、发送短信时指定电话号码和短信内容等数据。Data 属性的值是一个 Uri
对象,格式以下:schema://host:port/path
复制代码
schema
协议host
主机port
端口path
路径如:http://www.baidu.com:8080/a.jpg
系统内置的几个 Data 属性常量
协议 | 含义 |
---|---|
tel: | 号码数据格式,后跟电话号码 |
mailto: | 邮件数据格式,后跟邮件收件人地址 |
smsto: | 短信数据格式,后跟短信接收号码 |
file:/// | 文件数据格式,后跟文件路径。注意必须是三根斜杠 /// |
content:// | 内容数据格式,后跟须要读取的内容。ContentProvider 特有的格式 |
举几个例子给你们展现一下:
例1:打开一个网页
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
复制代码
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("tel:18755555555"));
startActivity(intent);
复制代码
Type
属性用于指定 Data 所制定的 Uri 对应的
MIME
类型,一般应用于调用系统 App,好比实现查看文件(文本、图片、音频或者视频等),经过指定文件的 MIME 类型,可让系统知道用什么程序打开该文件。
设置 Data 时,调用 setData()
,设置 Type 时,调用 setType
,注意,这两个方法不能同时设置,会被覆盖掉。若是想要同时设置 Data 和 Type,请调用 setDataAndType
。咱们能够查看一下源码:
/** * Set the data this intent is operating on. This method automatically * clears any type that was previously set by {@link #setType} or * {@link #setTypeAndNormalize}. * * <p><em>Note: scheme matching in the Android framework is * case-sensitive, unlike the formal RFC. As a result, * you should always write your Uri with a lower case scheme, * or use {@link Uri#normalizeScheme} or * {@link #setDataAndNormalize} * to ensure that the scheme is converted to lower case.</em> * * @param data The Uri of the data this intent is now targeting. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #getData * @see #setDataAndNormalize * @see android.net.Uri#normalizeScheme() */
public @NonNull Intent setData(@Nullable Uri data) {
mData = data;
mType = null;
return this;
}
......
/** * Set an explicit MIME data type. * * <p>This is used to create intents that only specify a type and not data, * for example to indicate the type of data to return. * * <p>This method automatically clears any data that was * previously set (for example by {@link #setData}). * * <p><em>Note: MIME type matching in the Android framework is * case-sensitive, unlike formal RFC MIME types. As a result, * you should always write your MIME types with lower case letters, * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize} * to ensure that it is converted to lower case.</em> * * @param type The MIME type of the data being handled by this intent. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #getType * @see #setTypeAndNormalize * @see #setDataAndType * @see #normalizeMimeType */
public @NonNull Intent setType(@Nullable String type) {
mData = null;
mType = type;
return this;
}
......
/** * (Usually optional) Set the data for the intent along with an explicit * MIME data type. This method should very rarely be used -- it allows you * to override the MIME type that would ordinarily be inferred from the * data with your own type given here. * * <p><em>Note: MIME type and Uri scheme matching in the * Android framework is case-sensitive, unlike the formal RFC definitions. * As a result, you should always write these elements with lower case letters, * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalizeScheme} or * {@link #setDataAndTypeAndNormalize} * to ensure that they are converted to lower case.</em> * * @param data The Uri of the data this intent is now targeting. * @param type The MIME type of the data being handled by this intent. * * @return Returns the same Intent object, for chaining multiple calls * into a single statement. * * @see #setType * @see #setData * @see #normalizeMimeType * @see android.net.Uri#normalizeScheme * @see #setDataAndTypeAndNormalize */
public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
mData = data;
mType = type;
return this;
}
复制代码
下面咱们经过一个例子来讲明一下 Data 和 Type 的用法:
查看手机里的一张图片,地址为:storage/emulated/0/DCIM/IMG_201910297_162012_328.jpg
File file = new File("storage/emulated/0/DCIM/IMG_201910297_162012_328.jpg");
Intent intent = new Intent();
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "image/jpeg");
startActivity(intent);
复制代码
运行查看效果:
经常使用的 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 |
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 |
Extra:额外
属性用于添加一些附加信息,它的属性值是一个 Bundle 对象,经过键值对的形式存储数据。在隐式 Intent 中使用较少,主要用于显示 Intent 传递数据。下面简单演示一下://MainActivity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("name", "我叫 Android");
startActivity(intent);
复制代码
//SecondActivity
Intent intent = getIntent();
if (intent != null) {
String name = intent.getStringExtra("name");
textView.setText(name);
}
复制代码
Flags:标记
一般用来动态配置 Activity 的启动模式。大部分状况下,咱们都不须要设置 Flags,因此,对于 Flags 你们可以理解就行。下面,介绍几个经常使用的:
FLAG_ACTIVITY_NEW_TASK:设置这个标记位的话,是为 Activity 指定 “singleTask” 启动模式,它的做用和在清单文件中指定该启动模式的效果同样。
FLAG_ACTIVITY_SINGLE_TOP:设置这个标记位的话,是为 Activity 指定 “singleTop” 启动模式,它的做用和在清单文件中指定该启动模式的效果同样。
FLAG_ACTIVITY_CLEAR_TOP:具备此标记位的 Activity ,在它启动时,在同一个任务栈中全部位于它上面的 Activity 都要出栈。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具备这个标记的 Activity 不会出如今历史 Activity 的列表中。它等同于在清单文件中指定 Activity 的属性 android:excludeFromRecents="true"
。
在上一节的内容中,咱们介绍了 Intent 的七大属性,也给你们演示了如何定义一个显示 Intent和隐式 Intent,显示 Intent 的使用更加的简单,所以咱们就不过多介绍了,下面再给你们分析一下隐式 Intent。
要声明组件能够接收哪些隐式 Intent,须要在清单文件中使用 <intent-filter>
元素为组件声明一个或多个
Intent 过滤器。每一个 <intent-filter>
中主要设置的属性包括:<action>
、<data>
、<category>
。当隐式 Intent 能够匹配上其中一个<intent-filter>
时,系统就会将该 Intent 传递给应用组件(显式 Intent 始终会传递给其目标组件,不管目标组件声明的 <intent-filter>
是什么)。
应用组件应该为自身可执行的每一个独特做业声明单独的 <intent-filter>
。例如,图像库应用中的一个 Activity 可能会有两个 <intent-filter>
,分别用于查看图像和编辑图像。当 Activity 启动时,将检查 Intent 并根据 Intent 中的信息决定具体的行为(例如,是否显示编辑图片控件)。
特别注意:在 <intent-filter>
中,必须设置一个默认的 <category>
:<category android:name="android.intent.category.DEFAULT" />
,否者组件不会接收隐式 Intent。
假如咱们不但愿其余应用启动咱们的组件,只但愿在本应用中使用组件,那么咱们就不要在清单中声明 <intent-filter>
,而且将该组件的 exported
属性设置为 false
。
下面咱们经过一个具体的例子来讲明一下 <intent-filter>
的用法:
<activity android:name="MainActivity">
<!-- This activity is the main entry, should appear in app launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="ShareActivity">
<!-- This activity handles "SEND" actions with text data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
<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>
复制代码
第一个 Activity MainActivity 是应用的主要入口:
ACTION_MAIN
操做指示这是主要入口,且不要求输入任何 Intent 数据。CATEGORY_LAUNCHER
类别指示此 Activity 的图标应放入系统的应用启动器。若是 元素未使用 icon 指定图标,则系统将使用 元素中的图标。这两个元素必须配对使用,Activity 才会显示在应用启动器中。
第二个 Activity ShareActivity 旨在便于共享文本和媒体内容。尽管能够经过从 MainActivity 进入此 Activity,但也能够从发出隐式 Intent(与两个 Intent 过滤器之一匹配)的另外一应用中直接进入 ShareActivity。
MIME 类型 application/vnd.google.panorama360+jpg 是一个指定全景照片的特殊数据类型
注意1:当没有任何应用可以响应咱们调用 startActivity() 传递的隐式 Intent 时如何处理?
咱们自定义一个 Action,可是不让任何 Activity 接收该 Intent:
Intent intent = new Intent();
intent.setAction("com.cyy.send");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
复制代码
运行后,应用奔溃,Error 信息以下:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.cyy.send cat=[android.intent.category.DEFAULT] }
为了不这种状况的出现,咱们在调用 startActivity() 前,须要调用 resolveActivity()
验证是否有 Activity 能够接收 Intent,具体作法以下:
Intent intent = new Intent();
intent.setAction("com.cyy.send");
intent.addCategory(Intent.CATEGORY_DEFAULT);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
复制代码
若是 resolveActivity 的结果为非空,则表示至少有一个应用可以处理该 Intent,此时便可安全的调用 startActivity()。
注意2:当有多个应用能够响应咱们的隐式 Activity 时,系统会弹出一个选择框,让用户选择须要打开的应用,用户也能够选择记住要本身打开的应用,这样下次就不会再弹出选择框。那么假如我但愿每次都弹窗,不让用户记住呢?咱们可使用 createChooser()
建立 Intent。
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
String title = "请选择浏览器";
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(intent, title);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
复制代码
运行效果以下: