Activity与Intent:Android组件的灵魂
说实话,Activity和Intent是Android开发里最基础也最核心的概念。我刚开始学Android时,总觉得Activity就是个“页面”,Intent就是个“跳转工具”。但做了几个项目后才发现——嗯,理解得还是太浅了。
今天我们就来彻底搞懂这几个东西。我会结合自己踩过的坑,帮你把知识点吃透。
Activity生命周期:你的页面会经历什么?
每个Activity从创建到销毁,都会经历一系列回调。我习惯把这七个方法记成“一生七步”:
| 回调方法 | 触发时机 | 典型用途 |
|---|---|---|
| onCreate() | Activity首次创建 | 初始化布局、绑定数据 |
| onStart() | Activity可见但不可交互 | 注册广播、开始动画 |
| onResume() | Activity可见且可交互 | 开始摄像头、恢复音乐播放 |
| onPause() | Activity失去焦点但部分可见 | 保存草稿、暂停动画 |
| onStop() | Activity完全不可见 | 释放资源、取消网络请求 |
| onRestart() | Activity从停止状态重新启动 | 重新加载数据 |
| onDestroy() | Activity被销毁 | 清理内存、解绑服务 |
核心要点:onCreate()和onDestroy()只调用一次,onPause()和onResume()可能被频繁调用。别在onPause()里做耗时操作——我曾经在这里写了个数据库保存,结果用户切应用时卡了半秒,被测试骂惨了。
我的习惯:在onCreate()里只做轻量初始化,比如findViewById。网络请求、数据库读取这些放到onResume()里,配合一个“是否首次加载”的布尔值控制,避免重复请求。
Intent:显式与隐式调用
Intent说白了就是“消息传递员”。它分两种:显式Intent和隐式Intent。
显式Intent:指名道姓
直接指定要启动的Activity类名。就像你打电话前先拨号——目标明确。
// 显式调用:直接指定目标Activity
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
这种写法简单粗暴,适合应用内部跳转。我在项目里90%的场景都用显式Intent——因为可读性强,别人一看就知道你要跳到哪里。
隐式Intent:广撒网
不指定具体类名,而是通过action、category、data等条件让系统帮你匹配。就像你喊一声“谁帮我拿杯水”——系统会找到能处理这个请求的Activity。
// 隐式调用:打开浏览器
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
startActivity(intent);
隐式Intent需要在AndroidManifest.xml里配置intent-filter:
<activity android:name=".WebActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
</intent-filter>
</activity>
避坑指南:我曾经在隐式Intent里忘了加category,结果系统死活找不到目标Activity。记住:隐式Intent至少需要一个action和一个category(通常用DEFAULT)。另外,如果系统找不到匹配的Activity,会抛出ActivityNotFoundException——记得用try-catch包一下。
Activity之间的数据传递
数据传递是Activity通信的核心。我总结了三种常用方式:
1. 使用putExtra()
最基础的方式,通过Intent携带数据:
// 发送方
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", -1);
String userName = intent.getStringExtra("user_name");
2. 使用Bundle
当数据较多时,我习惯用Bundle打包:
// 发送方
Bundle bundle = new Bundle();
bundle.putInt("user_id", 1001);
bundle.putString("user_name", "张三");
bundle.putSerializable("user_info", userObject);
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtras(bundle);
startActivity(intent);
// 接收方
Bundle extras = getIntent().getExtras();
if (extras != null) {
int userId = extras.getInt("user_id");
String userName = extras.getString("user_name");
UserInfo info = (UserInfo) extras.getSerializable("user_info");
}
3. 获取返回结果(startActivityForResult)
有时候你需要从目标Activity拿回数据。比如选照片后返回图片路径:
// 发送方
Intent intent = new Intent(this, PickerActivity.class);
startActivityForResult(intent, REQUEST_CODE_PICKER);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICKER && resultCode == RESULT_OK) {
String result = data.getStringExtra("selected_item");
// 处理返回的数据
}
}
// 接收方(PickerActivity中)
Intent resultIntent = new Intent();
resultIntent.putExtra("selected_item", "item_123");
setResult(RESULT_OK, resultIntent);
finish();
我的建议:现在官方推荐用Activity Result API替代startActivityForResult,但老项目里startActivityForResult依然常见。如果你维护的是新项目,建议用registerForActivityResult——代码更简洁,而且避免了onActivityResult里一堆if-else。
启动模式:控制Activity的“存在方式”
启动模式决定了Activity在任务栈里的行为。Android提供了四种模式,我一个个说。
Standard(标准模式)
每次启动都创建新实例,不管栈里有没有。就像每次点外卖都重新做一份——哪怕你刚吃完同样的菜。
<activity android:name=".StandardActivity" android:launchMode="standard" />
这种模式最常用,但要注意:如果频繁启动同一个Activity,栈里会堆积大量实例。我在一个新闻App里就踩过这个坑——用户连续点开10篇文章,按返回键要按10次才能退出。
SingleTop(栈顶复用)
如果目标Activity已经在栈顶,就不创建新实例,而是回调onNewIntent()。否则创建新实例。
<activity android:name=".TopActivity" android:launchMode="singleTop" />
适合通知栏点击跳转的场景。比如用户正在看消息列表,又来了一条新消息通知——点击通知后,复用栈顶的列表页,刷新数据即可。
SingleTask(栈内复用)
如果目标Activity在栈里存在,就把它上面的所有Activity出栈,然后复用这个实例。如果不存在,就创建新实例并放在栈顶。
<activity android:name=".MainActivity" android:launchMode="singleTask" />
这个模式我常用在主界面。比如用户从首页跳到详情页,再跳到设置页,然后点击返回首页——这时候应该把首页上面的页面全部清掉,直接显示首页。SingleTask完美解决这个问题。
SingleInstance(单实例模式)
这个模式最特殊。目标Activity会单独占用一个任务栈,而且整个系统里只有一个实例。
<activity android:name=".PlayerActivity" android:launchMode="singleInstance" />
适合播放器、拨号器这类全局唯一的页面。我记得有个音乐App项目,播放页面用了SingleInstance——用户从任何地方打开播放器,都回到同一个播放界面,体验很统一。
启动模式对比表:
| 模式 | 是否创建新实例 | 是否复用已有实例 | 典型场景 |
|---|---|---|---|
| Standard | 是 | 否 | 普通页面跳转 |
| SingleTop | 栈顶有时不创建 | 栈顶时复用 | 通知栏跳转 |
| SingleTask | 栈内有时不创建 | 栈内时复用 | 主界面、首页 |
| SingleInstance | 全局唯一 | 全局复用 | 播放器、拨号器 |
避坑指南:我曾经在SingleTask模式下忘了处理onNewIntent(),结果页面复用后数据没刷新。记住:只要启动模式不是Standard,都要在onNewIntent()里处理新传入的Intent数据。另外,SingleInstance模式下不同应用共享同一个实例——如果处理不好,会出现数据混乱。
知识体系总览
下面这张图总结了本章的核心知识点。我建议你把它保存下来,复习时对照着看:
Activity和Intent是Android开发的基石。生命周期决定了你的页面何时创建、何时销毁;Intent决定了页面之间如何通信;启动模式则控制了页面的“存在方式”。这三者配合好了,你的App才能流畅、稳定、用户体验好。
我个人建议:刚开始学的时候,先把Standard模式和显式Intent用熟。等项目做多了,再慢慢尝试SingleTask和隐式Intent。别一口气吃成胖子——我见过太多新手一上来就搞SingleInstance,结果把自己绕晕了。
好了,这一章的内容就到这里。记住:多写代码,多调试,多踩坑——踩过的坑才是真正学到的东西。
公众号:蓝海资料掘金营,微信deep3321