第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对象。系统知道你要启动哪个组件,不需要任何匹配过程。

我的习惯:在同一个App内部,我几乎只用显式Intent。因为明确、可控、不会出错。隐式Intent更多用于跨App调用。

显式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就会出现在选择列表中。

我曾经踩过的坑:隐式Intent启动Activity时,如果没有任何组件能匹配,会抛出ActivityNotFoundException。所以一定要用try-catch包裹,或者先调用resolveActivity()检查。

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等。

我画了一张图,帮你理清匹配流程:

隐式Intent匹配流程 隐式Intent发起 步骤1:Action匹配 步骤2:Category匹配 步骤3:Data匹配 匹配成功 → 启动组件 匹配失败 → 抛异常

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");
}
我的建议:如果传递的数据超过5个字段,用Bundle更清晰。而且Bundle可以复用,比如在多个地方传递相同的数据集。

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");
我曾经踩过的坑:用Serializable传递大对象时,序列化和反序列化耗时较长,可能导致界面卡顿。如果对象结构复杂,建议用Parcelable或者改用其他方式(比如传递ID,接收方再查询)。

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()。

我的建议:新项目直接用Activity Result API。它更安全,不会因为Activity重建导致回调丢失。而且代码更集中,不用在onActivityResult里写一堆if-else。

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