第9章 Intent与IntentFilter:组件间的通信桥梁
说实话,刚学Android那会儿,我最困惑的就是Activity之间怎么传数据。明明两个界面看起来是独立的,怎么让它们互相说话?后来我才明白,这一切都靠Intent——Android里最核心的通信机制。
这一章,我会把Intent的显式与隐式用法、匹配规则、数据传递以及Activity间返回结果,一次性讲清楚。嗯,都是我在项目里踩过坑、总结出来的经验。
9.1 什么是Intent?
Intent,直译是“意图”。说白了,就是你告诉系统:“我想做某件事”。比如“我想打开另一个界面”、“我想拨打电话”、“我想分享一张图片”。
Intent分为两种:显式Intent和隐式Intent。它们的区别,我习惯用一个比喻来解释:
- 显式Intent:你直接叫一个人的名字,让他做事。比如“张三,帮我把灯打开”。
- 隐式Intent:你只说“谁能把灯打开?”,系统会找到能开灯的人来响应。
显式Intent指定了目标组件的类名,隐式Intent则通过Action、Category、Data等条件让系统去匹配。
9.2 显式Intent
显式Intent最常见的使用场景,就是在同一个App内跳转Activity。代码很简单:
// 从MainActivity跳转到SecondActivity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
这里直接传入了目标Activity的Class对象。系统知道你要启动哪个组件,不需要任何匹配过程。
显式Intent也可以用于启动Service或发送Broadcast,用法类似:
// 启动Service
Intent serviceIntent = new Intent(this, MyService.class);
startService(serviceIntent);
// 发送广播
Intent broadcastIntent = new Intent(this, MyReceiver.class);
sendBroadcast(broadcastIntent);
9.3 隐式Intent与IntentFilter匹配规则
隐式Intent不指定具体组件,而是声明一个“动作”。系统会去查找所有注册了匹配IntentFilter的组件。
举个例子,你想打开一个网页:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
startActivity(intent);
系统会找到所有能处理“查看网页”这个动作的App,比如浏览器。如果有多个,会弹出选择器让用户选。
9.3.1 IntentFilter的三大要素
一个IntentFilter可以声明三种匹配条件:
| 要素 | 说明 | 示例 |
|---|---|---|
| Action | 要执行的动作 | ACTION_VIEW, ACTION_SEND |
| Category | 附加的类别信息 | CATEGORY_DEFAULT, CATEGORY_BROWSABLE |
| Data | 操作的数据(URI和MIME类型) | https://, content://, image/* |
在AndroidManifest.xml中注册Activity时,可以这样声明IntentFilter:
<activity android:name=".ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
这样,当其他App发送文本分享时,你的ShareActivity就会出现在选择列表中。
9.3.2 匹配规则详解
Action匹配:Intent中的Action必须与IntentFilter中至少一个Action匹配。如果IntentFilter没有声明任何Action,则所有Intent都无法匹配(除非Intent的Action为null)。
Category匹配:Intent中所有的Category都必须与IntentFilter中的Category匹配。但有一个例外:如果Intent没有设置Category,系统会自动添加CATEGORY_DEFAULT。所以,想让Activity能被隐式Intent启动,必须在IntentFilter中声明CATEGORY_DEFAULT。
Data匹配:包括URI和MIME类型。如果IntentFilter声明了Data,那么Intent的Data必须与之匹配。URI的匹配规则比较复杂,涉及scheme、host、port、path等。
我画了一张图,帮你理清匹配流程:
9.4 传递数据:Bundle与Extra
Intent传递数据,主要通过两种方式:putExtra()和Bundle。其实底层都是Bundle,只是写法不同。
9.4.1 使用putExtra()
这是最直接的方式:
// 发送方
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("user_id", 1001);
intent.putExtra("user_name", "张三");
startActivity(intent);
// 接收方
Intent intent = getIntent();
int userId = intent.getIntExtra("user_id", 0);
String userName = intent.getStringExtra("user_name");
支持的数据类型很丰富:int、String、boolean、float、Serializable、Parcelable等。
9.4.2 使用Bundle
当数据比较多时,我习惯用Bundle统一管理:
// 发送方
Intent intent = new Intent(this, DetailActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("user_id", 1001);
bundle.putString("user_name", "张三");
bundle.putString("email", "zhangsan@example.com");
intent.putExtras(bundle);
startActivity(intent);
// 接收方
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
int userId = bundle.getInt("user_id");
String userName = bundle.getString("user_name");
}
9.4.3 传递复杂对象
传递自定义对象,需要实现Serializable或Parcelable接口。我个人更推荐Parcelable,因为性能更好:
// 定义Parcelable对象
public class User implements Parcelable {
private int id;
private String name;
// 构造方法、getter/setter省略
protected User(Parcel in) {
id = in.readInt();
name = in.readString();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
}
// 发送方
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("user", new User(1001, "张三"));
startActivity(intent);
// 接收方
User user = getIntent().getParcelableExtra("user");
9.5 Activity间返回结果:startActivityForResult
有时候,你启动一个Activity,希望它返回一些数据。比如从相册选一张图片,或者填写一个表单后把结果带回来。这时候就需要startActivityForResult。
不过,从Android 11开始,Google推荐用Activity Result API替代旧的方式。但很多老项目还在用startActivityForResult,所以两种我都讲一下。
9.5.1 传统方式:startActivityForResult
// 发送方(MainActivity)
Intent intent = new Intent(this, EditActivity.class);
startActivityForResult(intent, REQUEST_CODE_EDIT);
// 接收方(EditActivity)返回数据
Intent resultIntent = new Intent();
resultIntent.putExtra("result", "编辑完成");
setResult(RESULT_OK, resultIntent);
finish();
// 发送方接收结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_EDIT) {
if (resultCode == RESULT_OK) {
String result = data.getStringExtra("result");
// 处理返回的数据
} else if (resultCode == RESULT_CANCELED) {
// 用户取消了操作
}
}
}
这里有几个要点:
- requestCode:区分不同的请求来源。比如从相册选图和从编辑页面返回,用不同的requestCode区分。
- resultCode:返回结果的状态。RESULT_OK表示成功,RESULT_CANCELED表示取消。
- setResult():必须在finish()之前调用,否则数据传不回去。
9.5.2 新方式:Activity Result API
新API解决了onActivityResult耦合度高、容易出错的问题:
// 注册结果回调
private ActivityResultLauncher<Intent> editLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
Intent data = result.getData();
if (data != null) {
String resultStr = data.getStringExtra("result");
// 处理结果
}
}
});
// 启动Activity
Intent intent = new Intent(this, EditActivity.class);
editLauncher.launch(intent);
接收方返回数据的方式和传统方式一样,还是用setResult()。
9.6 避坑指南
最后,分享几个我在项目中遇到过的典型问题:
- Intent传递数据大小限制:Intent的Bundle数据大小建议控制在1MB以内。超过这个限制,会触发TransactionTooLargeException。我曾经传一张大图就踩过这个坑。解决方案是传URI或者文件路径,而不是传Bitmap本身。
- 隐式Intent的安全性:用隐式Intent启动Activity时,如果用户手机上没有任何App能处理这个Intent,会直接崩溃。记得用PackageManager.queryIntentActivities()检查一下。
- setResult()的时机:如果Activity被系统销毁重建,setResult()可能不会生效。所以尽量在用户主动操作(比如点击按钮)时调用setResult(),而不是在onDestroy()里。
- Parcelable vs Serializable:在Android中,Parcelable的性能比Serializable好很多。我做过测试,同样一个对象,Parcelable的序列化时间只有Serializable的十分之一。
好了,这一章的内容就到这里。Intent是Android开发的基石,理解透彻了,后面的Service、BroadcastReceiver学起来会轻松很多。嗯,多写多练,慢慢就熟练了。
公众号:蓝海资料掘金营,微信deep3321