Skip to content

Android - Intent/过滤器

在 Android 中,Intent 是一种消息传递对象,可用于从另一个应用组件请求操作。Intent 以多种方式促进组件间通信,例如启动 Activity、启动 Service 或发送广播。

Intent 本身是一种被动数据结构,包含要执行的操作或(对于广播而言)已发生的事件的抽象描述。

例如,如果您的 Activity 需要发送电子邮件,它会创建一个带有 ACTION_SENDTO action 和 mailto: URI 的 Intent。然后它将此 Intent 传递给 startActivity()。Android 系统的 Intent 解析器会找到一个合适的电子邮件应用来处理此请求,如果多个应用可以处理,则会呈现一个选择器。

// 示例:发送电子邮件
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto:")); // 只有电子邮件应用应该处理此 Intent
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"recipient@example.com"});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email Subject");
emailIntent.putExtra(Intent.EXTRA_TEXT, "Email body content.");
if (emailIntent.resolveActivity(getPackageManager()) != null) {
startActivity(Intent.createChooser(emailIntent, "Choose an email client:"));
} else {
// 处理没有电子邮件客户端可用的情况
Toast.makeText(this, "No email client installed.", Toast.LENGTH_SHORT).show();
}

类似地,要打开网页,您可以使用 ACTION_VIEW 和 HTTP/HTTPS URI:

// 示例:打开网页
Uri webpage = Uri.parse("https://www.example.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
if (webIntent.resolveActivity(getPackageManager()) != null) {
startActivity(webIntent);
} else {
// 处理没有浏览器可用的情况
Toast.makeText(this, "No web browser found.", Toast.LENGTH_SHORT).show();
}

Intent 通过特定方法传递给不同的组件类型:

序号方法及描述
1Context.startActivity(Intent intent)
启动新的 Activity 或让现有 Activity 执行新操作。Intent 描述要启动的 Activity 并携带任何必要的数据。
2Context.startService(Intent intent) / Context.bindService(Intent intent, …)
startService() 启动 Service 或向正在运行的 Service 发送新指令。bindService() 建立与 Service 的连接以便直接交互。
3Context.sendBroadcast(Intent intent)
将 Intent 作为广播消息发送给所有相关的 BroadcastReceiver 组件。

Intent 对象携带 Android 系统用于确定要启动哪个组件的信息,以及接收组件用于执行操作的信息。Intent 对象的主要组成部分包括:

要启动的组件的名称(例如 com.example.app.MyActivity)。如果设置了此项,则 Intent 将直接传递给此组件实例。这称为显式 Intent。如果未设置,Android 将使用 Intent 的其他信息来查找合适的目标(隐式 Intent)。通过 setComponent()、setClass() 或 setClassName() 设置。

指定要执行的通用操作的字符串(例如 Intent.ACTION_VIEW、Intent.ACTION_SEND)。对于广播 Intent,这是已发生并正在报告的操作(例如 Intent.ACTION_BOOT_COMPLETED)。Intent 类定义了许多 ACTION_* 常量。这是隐式 Intent 的关键部分。通过 setAction() 设置。

一个 Uri(统一资源标识符),引用要对其执行操作的数据和/或该数据的 MIME 类型。例如,如果 action 是 ACTION_VIEW,则 data 字段可能包含联系人数据库中某人的 URI 或网页的 HTTP URI。通过 setData()、setType() 或 setDataAndType() 设置。

Action/Data 对示例:

序号Action/Data 对及描述
1ACTION_VIEW, content://contacts/people/1
显示 ID 为 “1” 的个人的信息。
2ACTION_DIAL, tel:1234567890
显示电话拨号器,并填充给定的号码。
3ACTION_VIEW, geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California
显示地图,显示指定位置。
4ACTION_EDIT, content://contacts/people/1
编辑 ID 为 “1” 的个人的信息。
5ACTION_SENDTO, mailto:user@example.com
打开电子邮件编辑器,收件人地址为 user@example.com。
6ACTION_SEND (with EXTRA_TEXT and setType("text/plain"))
分享纯文本内容。

包含有关应处理 Intent 的组件类型的附加信息的可选字符串。常见的 Category 包括 Intent.CATEGORY_LAUNCHER(将 Activity 标记为应用的初始入口点)和 Intent.CATEGORY_DEFAULT(如果某个 Activity 是默认选项,则允许隐式 Intent 启动它)。使用 addCategory() 添加。

携带完成请求操作所需的附加信息的键值对。存储在 Bundle 对象中。例如,ACTION_SEND Intent 可能包含 Intent.EXTRA_EMAIL(电子邮件地址的字符串数组)和 Intent.EXTRA_SUBJECT(主题字符串)。使用 putExtra() 方法添加。

修改 Intent 行为的可选整数,例如应如何启动 Activity(例如 FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP)或系统在启动后应如何处理它。使用 addFlags() 添加。

序号Flag 及描述
1FLAG_ACTIVITY_NEW_TASK
如果设置,此 Activity 将成为此历史堆栈上新任务的开始。通常用于从现有 Activity Context 之外启动 Activity 时(例如,从 Service 或 BroadcastReceiver)。
2FLAG_ACTIVITY_CLEAR_TOP
如果正在启动的 Activity 已在当前任务中运行,则不会启动新的实例,而是关闭其之上的所有其他 Activity,并通过 onNewIntent() 将此 Intent 作为新 Intent 发送给(现在位于顶部的)旧 Activity。
3FLAG_ACTIVITY_SINGLE_TOP
如果正在启动的 Activity 已位于当前任务的顶部,则不会启动新的实例,而是现有实例将在其 onNewIntent() 方法中接收 Intent。

Intent 主要有两种类型:

通过其完全限定类名指定要启动的组件。您通常使用显式 Intent 来启动自己应用内的组件,因为您知道要启动的 Activity 或 Service 的类名。

// 显式 Intent:从 FirstActivity 启动 SecondActivity(都在 com.example.app 中)
Intent explicitIntent = new Intent(FirstActivity.this, SecondActivity.class);
// 可选:向 Intent 添加数据
explicitIntent.putExtra("MESSAGE_KEY", "Hello from FirstActivity!");
startActivity(explicitIntent);

在 SecondActivity 中,您可以检索附加数据:

// 在 SecondActivity.java 的 onCreate() 或 onNewIntent() 中
Bundle extras = getIntent().getExtras();
if (extras != null) {
String message = extras.getString("MESSAGE_KEY");
// 使用消息
}

不指定特定的组件。相反,它们声明要执行的通用操作,这允许来自另一个应用的组件处理它。例如,如果您想在地图上向用户显示某个位置,可以使用隐式 Intent 请求另一个能够处理此操作的应用显示指定位置。

// 隐式 Intent:查看联系人
Intent implicitIntent = new Intent(Intent.ACTION_VIEW);
implicitIntent.setData(ContactsContract.Contacts.CONTENT_URI);
if (implicitIntent.resolveActivity(getPackageManager()) != null) {
startActivity(implicitIntent);
} else {
Toast.makeText(this, "No app can handle this action.", Toast.LENGTH_SHORT).show();
}

当您使用隐式 Intent 时,Android 系统会通过将 Intent 的内容与设备上其他应用的 Manifest 文件中声明的 Intent 过滤器进行比较,来查找合适的组件启动。如果多个组件匹配,系统可能会显示一个对话框供用户选择要使用的应用(一个“应用选择器”)。

此示例展示了如何使用隐式 Intent 启动网页浏览器和电话拨号器。

  1. 创建一个新的 Android Studio 项目(例如 com.example.intentdemo)。

  2. 修改 res/layout/activity_main.xml 以包含两个按钮:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/button_start_browser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open Web Browser" />
<Button
android:id="@+id/button_start_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Open Phone Dialer" />
</LinearLayout>
  1. 修改 MainActivity.java(或 .kt)以处理按钮点击(使用 View Binding 进行现代实践):
// MainActivity.java
package com.example.intentdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import com.example.intentdemo.databinding.ActivityMainBinding; // 生成的 ViewBinding 类
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.buttonStartBrowser.setOnClickListener(v -> {
Uri webpage = Uri.parse("https://developer.android.com");
Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(MainActivity.this, "No browser found", Toast.LENGTH_SHORT).show();
}
});
binding.buttonStartPhone.setOnClickListener(v -> {
Uri phoneNumber = Uri.parse("tel:1234567890"); // 示例号码
Intent intent = new Intent(Intent.ACTION_DIAL, phoneNumber);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(MainActivity.this, "No phone app found", Toast.LENGTH_SHORT).show();
}
});
}
}
  1. 确保在您的模块的 build.gradle 或 build.gradle.kts 文件中启用 View Binding: android { buildFeatures { viewBinding = true } }

  2. 运行应用。点击“打开网页浏览器”将启动浏览器并导航到指定 URL。点击“打开电话拨号器”将打开拨号器应用并预填充号码。resolveActivity() 检查很重要,可以防止在没有应用可以处理 Intent 时发生崩溃。

Intent 过滤器是应用 Manifest 文件 (AndroidManifest.xml) 中的一个表达式,指定组件(Activity、Service 或 BroadcastReceiver)可以接收的 Intent 类型。当您创建隐式 Intent 时,Android 系统会搜索具有与该 Intent 匹配的 Intent 过滤器的组件。

一个 <intent-filter> 元素声明其父组件的能力。它可以包含一个或多个以下子元素:

  • <action android:name="...">:声明它接受的 Intent Action。必须至少包含一个 Action。
  • <category android:name="...">:声明它接受的 Intent Category。Intent 必须匹配过滤器中的所有 Category。
  • <data ...>:声明它接受的数据类型,由 URI 方面(scheme、host、port、path)和/或 MIME 类型指定。

示例:声明用于处理分享纯文本的 Activity (ShareActivity):

<activity android:name=".ShareActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>

现在可以通过以下隐式 Intent 启动此 ShareActivity:

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
sendIntent.setType("text/plain");
// 验证 Intent 是否能解析到 Activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(Intent.createChooser(sendIntent, "Share via"));
}

CATEGORY_DEFAULT 很重要,因为它允许在使用隐式 Intent 调用 startActivity() 时找到该 Activity。如果没有它,该 Activity 只能通过显式 Intent 启动。

Intent 解析过程: 使用隐式 Intent 时,系统会将 Intent 的 Action、Data(URI 和 MIME 类型)和 Category 与所有已安装应用的 <intent-filter> 声明进行比较。如果找到匹配项(或多个匹配项),相应的组件可以处理该 Intent。如果没有组件可以处理该 Intent,则调用 startActivity() 将导致 ActivityNotFoundException。在通过隐式 Intent 启动 Activity 之前,务必使用 resolveActivity(getPackageManager()) != null 进行检查,或者使用 try-catch 块。

Intent 和 Intent 过滤器是 Android 应用互操作性的基石,可以在单个应用内和不同应用之间实现强大而灵活的组件通信。