Android Activity 页面导航基础:Manifest 声明、Intent 显式/隐式跳转与数据传递
本文面向所有 Android 开发者,围绕 Activity 的入门到进阶必备知识展开:Activity 的职责与定位、Manifest 声明、Intent 跳转方式、页面间数据传递与结果回传,并补充常见坑与最佳实践。
目录
- [1. Activity 是什么](#1. Activity 是什么)
- [2. 创建与声明 Activity](#2. 创建与声明 Activity)
- [2.1 创建 Activity 类](#2.1 创建 Activity 类)
- [2.2 在 Manifest 中声明](#2.2 在 Manifest 中声明)
- [2.3 关联布局 XML](#2.3 关联布局 XML)
- [2.4 项目代码示例:MainActivity 与 SecondActivity](#2.4 项目代码示例:MainActivity 与 SecondActivity)
- [3. Activity 跳转:Intent 的两种用法](#3. Activity 跳转:Intent 的两种用法)
- [3.1 显式 Intent](#3.1 显式 Intent)
- [3.2 隐式 Intent](#3.2 隐式 Intent)
- [3.3 常见坑:exported、intent-filter 与匹配规则](#3.3 常见坑:exported、intent-filter 与匹配规则)
- [4. Activity 数据传递与结果回传](#4. Activity 数据传递与结果回传)
- [4.1 向目标页面传值](#4.1 向目标页面传值)
- [4.2 目标页面接收值](#4.2 目标页面接收值)
- [4.3 回传结果给上个页面](#4.3 回传结果给上个页面)
- [4.3.1 startActivityForResult(旧方式,已过时)](#4.3.1 startActivityForResult(旧方式,已过时))
- [4.3.2 ActivityResultLauncher(新方式,推荐)](#4.3.2 ActivityResultLauncher(新方式,推荐))
- [4.3.3 目标页面 setResult 回传(两种方式通用)](#4.3.3 目标页面 setResult 回传(两种方式通用))
- [4.4 Serializable 与 Parcelable:如何选择](#4.4 Serializable 与 Parcelable:如何选择)
- [4.4.1 示例类代码:Serializable 与 Parcelable 长什么样](#4.4.1 示例类代码:Serializable 与 Parcelable 长什么样)
- [4.4.2 注意事项:顺序一致、判空与体积限制](#4.4.2 注意事项:顺序一致、判空与体积限制)
- [5. 常见坑清单](#5. 常见坑清单)
1. Activity 是什么
Activity 是 Android 四大组件之一,通常对应一个可交互的 UI 屏幕(可理解为"页面")。它的核心价值不在于"像页面",而在于:
- 由系统托管生命周期:何时创建、显示、进入前台、进入后台、销毁,都由系统调度。
- 承载 UI 与交互入口:布局加载、事件监听、导航跳转、权限申请等最常发生在 Activity 中。
- 参与任务栈管理:返回键行为、多任务切换体验与启动模式密切相关(本文聚焦跳转与传参,任务栈与启动模式可在后续专题展开)。
2. 创建与声明 Activity
2.1 创建 Activity 类
最基础的 Activity 写法如下:在 onCreate() 中加载布局。
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
实践建议:
onCreate()适合做一次性初始化:setContentView()、findViewById()、绑定监听、初始化 ViewModel 等。- UI 初始化与业务加载尽量分离,避免
onCreate()过重导致启动卡顿。
2.2 在 Manifest 中声明
Activity 必须在 AndroidManifest.xml 中声明,否则无法被系统启动。入口页通常需要声明 MAIN + LAUNCHER。
xml
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
2.3 关联布局 XML
下面是一个典型的 ConstraintLayout 根布局示例:
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".dialog.TestActivity">
<!-- 在这个地方可以定义布局内的视图元素 -->
</androidx.constraintlayout.widget.ConstraintLayout>
说明:
tools:context仅用于预览,不影响运行时逻辑。- 布局文件名通常与
setContentView(R.layout.xxx)对应。
2.4 项目代码示例:MainActivity 与 SecondActivity
以下代码展示了:显式/隐式跳转、传参、以及两种结果回传方式(旧/新)。
MainActivity(项目代码)
java
/**
* 工程主入口
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private EditText etData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnStartSecond = findViewById(R.id.btn_start_second);
btnStartSecond.setOnClickListener(this);
Button btnStartSecond2 = findViewById(R.id.btn_start_second2);
btnStartSecond2.setOnClickListener(this);
findViewById(R.id.btn_start_second3).setOnClickListener(this);
findViewById(R.id.btn_start_second4).setOnClickListener(this);
findViewById(R.id.btn_start_second5).setOnClickListener(this);
etData = findViewById(R.id.et_data);
Log.i(TAG, "onCreate: btnStartSecond id = " + btnStartSecond.getId());
Log.i(TAG, "onCreate: btnStartSecond2 id = " + btnStartSecond2.getId());
}
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: v id = " + v.getId());
int id = v.getId();
if (id == R.id.btn_start_second) {
//跳转到Second页面
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
} else if (id == R.id.btn_start_second2) {
//隐式意图跳转到Second
Intent intent = new Intent();
//添加隐式意图过滤器 指定事件和意图分类
intent.setAction("com.ls.acbjp.activity.SecondActivity");
intent.addCategory(Intent.CATEGORY_DEFAULT);
// intent.addCategory("android.intent.category.DEFAULT");
startActivity(intent);
} else if (id == R.id.btn_start_second3) {
//传递数据到Second页面
Intent intent = new Intent(this, SecondActivity.class);
//获取输入框里的数据
String string = etData.getText().toString();
if (string != null && string.length() > 0) {
//传递给Second
intent.putExtra("key_data", string);
intent.putExtra("key_int", 666);
}
//传递一个实现了Serializable序列化接口的自定义类
User user = new User("老孙", "31", 123456);
intent.putExtra("key_user", user);
//传递Parcelable序列化接口的自定义类
Student student = new Student("老王,", "35", 6664);
intent.putExtra("key_stu",student);
startActivity(intent);
} else if (id == R.id.btn_start_second4) {
//跳转到second页面,并且等待second回传数据
//这个方式在api30后被标记为过时
startActivityForResult(new Intent(this, SecondActivity.class), 9);
} else if (id == R.id.btn_start_second5) {
//跳转到second页面,并且等待second回传数据
activityResultLauncher.launch(new Intent(this, SecondActivity.class));
}
}
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult o) {
int resultCode = o.getResultCode();
Intent data = o.getData();
if (resultCode == 8) {
//通过key接收second页面返回的参数
int keyCmd = data.getIntExtra("key_cmd", 0);
String hello = data.getStringExtra("key_hello");
Log.i(TAG, "onActivityResult: keyCmd = " + keyCmd);
Log.i(TAG, "onActivityResult: hello = " + hello);
etData.setText("接受到second的数据: " + keyCmd + hello);
}
}
}
);
/**
* 接受其他Activity页面返回的结果
*
* @param requestCode 跳转页面的时候指定的请求码
* @param resultCode 其他页面回传的结果码
* @param data 返回的意图(数据)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//判断是哪里跳回到当前Activity的
if (requestCode == 9 && resultCode == 8) {
//通过key接收second页面返回的参数
int keyCmd = data.getIntExtra("key_cmd", 0);
String hello = data.getStringExtra("key_hello");
Log.i(TAG, "onActivityResult: keyCmd = " + keyCmd);
Log.i(TAG, "onActivityResult: hello = " + hello);
etData.setText("接受到second的数据: " + keyCmd + hello);
}
}
}
SecondActivity(项目代码)
java
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//获取一个intent
Intent intent = getIntent();
//通过key接收main页面传递来的参数
String string = intent.getStringExtra("key_data");
Log.i(TAG, "onCreate: string = " + string);
//接收Serializable序列化接口的自定义类
User user = (User) intent.getSerializableExtra("key_user");
String name = user.getName();
String age = user.getAge();
int mobile = user.getMobile();
Log.i(TAG, "onCreate: user name = " + name);
Log.i(TAG, "onCreate: user age = " + age);
Log.i(TAG, "onCreate: user mobile = " + mobile);
//接收实现了Parcelable序列化接口的自定义类
Student student = intent.getParcelableExtra("key_stu");
Log.i(TAG, "onCreate: student name = " + student.getName());
Log.i(TAG, "onCreate: student age = " + student.getAge());
Log.i(TAG, "onCreate: student mobile = " + student.getMobile());
TextView textView = findViewById(R.id.text_view);
if (string != null && string.length() > 0) {
textView.setText(string);
}
//通过key_int接收main页面传递来的参数,如果没接收到,就让keyInt默认为0
int keyInt = intent.getIntExtra("key_int", 0);
Log.i(TAG, "onCreate: keyInt = " + keyInt);
findViewById(R.id.btn_back).setOnClickListener(view -> {
int cmd = 777;
String hello = "hello world!";
//设置了一个当前页面的处理结果,并且设置了一些回传的参数
Intent backIntent = new Intent();
backIntent.putExtra("key_cmd", cmd);
backIntent.putExtra("key_hello", hello);
setResult(8, backIntent);
finish();//关闭当前Activity
});
findViewById(R.id.btn_back_1).setOnClickListener(view -> {
int cmd = 777;
String hello = "hello world!";
//设置了一个当前页面的处理结果,并且设置了一些回传的参数
Intent backIntent = new Intent();
backIntent.putExtra("key_cmd", cmd);
backIntent.putExtra("key_hello", hello);
setResult(8, backIntent);
});
}
}
3. Activity 跳转:Intent 的两种用法
Intent 是 Android 组件间通信与页面导航的核心机制。跳转 Activity 时主要使用:
- 显式 Intent:直接指定目标 Activity 类(推荐,最清晰稳定)。
- 隐式 Intent :不写目标类名,通过
intent-filter匹配系统中可响应的组件。
3.1 显式 Intent
java
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
3.2 隐式 Intent
java
// 隐式意图跳转到 Second
Intent intent = new Intent();
// 添加隐式意图过滤器:指定事件和意图分类
intent.setAction("com.ls.acbjp.activity.SecondActivity");
intent.addCategory(Intent.CATEGORY_DEFAULT);
// intent.addCategory("android.intent.category.DEFAULT");
startActivity(intent);
目标 Activity 需要配置 intent-filter:
xml
<!--
如果是在安卓 12 以上的设备运行:需要 exported="true" 才能被外部/隐式方式访问。
这里保留你原文的提醒,但注意 exported=true/false 要结合你的入口方式与安全策略设置。
-->
<activity
android:name="com.ls.acbjp.activity.SecondActivity"
android:exported="false">
<intent-filter>
<action android:na me="com.ls.acbjp.activity.SecondActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
3.3 常见坑:exported、intent-filter 与匹配规则
- Android 12(API 31)起,只要组件声明了
intent-filter,就必须显式声明android:exported。 exported表示是否允许其他应用启动该组件:- 仅应用内使用:通常
false。 - 需要被外部或系统/其他 App 触发:设为
true,并做好权限与参数校验。
- 仅应用内使用:通常
4. Activity 数据传递与结果回传
4.1 向目标页面传值
java
// 传递数据到 Second 页面
Intent intent = new Intent(this, SecondActivity.class);
// 获取输入框里的数据
String string = etData.getText().toString();
if (string != null && string.length() > 0) {
// 传递给 Second
intent.putExtra("key_data", string);
intent.putExtra("key_int", 666);
}
// 传递一个实现了 Serializable 序列化接口的自定义类
// 注意这种用法需要让 User 类实现 Serializable 接口
User user = new User("老孙", "31", 123456);
intent.putExtra("key_user", user);
// 传递 Parcelable 序列化接口的自定义类
// 注意这种用法需要让 Student 类实现 Parcelable 接口
Student student = new Student("老王,", "35", 6664);
intent.putExtra("key_stu", student);
// 跳转页面
startActivity(intent);
4.2 目标页面接收值
java
// 获取一个 intent
Intent intent = getIntent();
// 通过 key 接收 main 页面传递来的参数
String string = intent.getStringExtra("key_data");
Log.i(TAG, "onCreate: string = " + string);
// 通过 key_int 接收 main 页面传递来的参数,如果没接收到,就让 keyInt 默认为 0
int keyInt = intent.getIntExtra("key_int", 0);
// 接收 Serializable 序列化接口的自定义类
User user = (User) intent.getSerializableExtra("key_user");
// 接收实现了 Parcelable 序列化接口的自定义类
Student student = intent.getParcelableExtra("key_stu");
4.3 回传结果给上个页面
Activity 回传结果的核心流程是:目标页通过 setResult(resultCode, dataIntent) 设置返回数据 ,然后结束页面;发起页在回调中读取返回的 Intent。
下面给出两套完整写法:旧方式(已过时) 与 新方式(推荐),以及目标页通用的回传代码。
4.3.1 startActivityForResult(旧方式,已过时)
发起页启动(带请求码 requestCode):
java
//跳转到second页面,并且等待second回传数据
//这个方式在api30后被标记为过时
startActivityForResult(new Intent(this, SecondActivity.class), 9);
发起页接收回传结果(重写 onActivityResult):
java
/**
* 接受其他Activity页面返回的结果
*
* @param requestCode 跳转页面的时候指定的请求码
* @param resultCode 其他页面回传的结果码
* @param data 返回的意图(数据)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//判断是哪里跳回到当前Activity的
if (requestCode == 9 && resultCode == 8) {
//通过key接收second页面返回的参数
int keyCmd = data.getIntExtra("key_cmd", 0);
String hello = data.getStringExtra("key_hello");
Log.i(TAG, "onActivityResult: keyCmd = " + keyCmd);
Log.i(TAG, "onActivityResult: hello = " + hello);
etData.setText("接受到second的数据: " + keyCmd + hello);
}
}
4.3.2 ActivityResultLauncher(新方式,推荐)
先注册 ActivityResultLauncher,可以将其作为主动页的成员变量(不再需要请求码):
java
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult o) {
int resultCode = o.getResultCode();
Intent data = o.getData();
if (resultCode == 8) {
//通过key接收second页面返回的参数
int keyCmd = data.getIntExtra("key_cmd", 0);
String hello = data.getStringExtra("key_hello");
Log.i(TAG, "onActivityResult: keyCmd = " + keyCmd);
Log.i(TAG, "onActivityResult: hello = " + hello);
etData.setText("接受到second的数据: " + keyCmd + hello);
}
}
}
);
启动跳转目标页面并等待回传:
java
//跳转到second页面,并且等待second回传数据
activityResultLauncher.launch(new Intent(this, SecondActivity.class));
4.3.3 目标页面 setResult 回传(两种方式通用)
目标页面回传数据(按钮点击回传示例):
java
findViewById(R.id.btn_back).setOnClickListener(view -> {
int cmd = 777;
String hello = "hello world!";
//设置了一个当前页面的处理结果,并且设置了一些回传的参数
Intent backIntent = new Intent();
backIntent.putExtra("key_cmd", cmd);
backIntent.putExtra("key_hello", hello);
setResult(8, backIntent);
finish();//关闭当前Activity
});
额外提示:发起页收到的
data可能为null(例如用户取消、系统回收等情况),读取 extra 前建议判空,避免空指针。
4.4 Serializable 与 Parcelable:如何选择
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
Serializable |
实现简单 | 性能相对差 | 低频、小对象 |
Parcelable |
性能更好 | 实现更繁琐 | 高频、多字段对象 |
4.4.1 示例类代码:Serializable 与 Parcelable 长什么样
当需要在 Activity 之间传递"自定义对象"时,通常有两种选择:
Serializable:Java 原生序列化,实现成本低,但性能相对一般。Parcelable:Android 推荐序列化方式,性能更好,但写法更繁琐。
下面给出两类"典型对象"的代码形态,便于理解你在 putExtra() / get...Extra() 中传递的对象应该长什么样。
(1)Serializable 示例:User implements Serializable
java
import java.io.Serializable;
public class User implements Serializable {
// 建议显式声明,避免类结构变动导致反序列化失败
private static final long serialVersionUID = 1L;
private String name;//用户名
private String age;//年龄
private int mobile;//手机号
public User(String name, String age, int mobile) {
this.name = name;
this.age = age;
this.mobile = mobile;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public int getMobile() {
return mobile;
}
public void setMobile(int mobile) {
this.mobile = mobile;
}
}
使用方式与本文代码完全一致:
- 传递:
intent.putExtra("key_user", user); - 接收:
User user = (User) intent.getSerializableExtra("key_user");
(2)Parcelable 示例:Student implements Parcelable
java
import android.os.Parcel;
import android.os.Parcelable;
public class Student implements Parcelable {
private String name;//用户名
private String age;//年龄
private int mobile;//手机号
public Student(String name, String age, int mobile) {
this.name = name;
this.age = age;
this.mobile = mobile;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeString(this.age);
dest.writeInt(this.mobile);
}
protected Student(Parcel in) {
this.name = in.readString();
this.age = in.readString();
this.mobile = in.readInt();
}
public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {
@Override
public Student createFromParcel(Parcel source) {
return new Student(source);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public int getMobile() {
return mobile;
}
public void setMobile(int mobile) {
this.mobile = mobile;
}
}
使用方式与本文代码完全一致:
- 传递:
intent.putExtra("key_stu", student); - 接收:
Student student = intent.getParcelableExtra("key_stu");
4.4.2 注意事项:顺序一致、判空与体积限制
为了让"对象传递"在真实项目里稳定运行,建议重点记住三件事:
-
Parcelable 的读写顺序必须一致
writeToParcel()写入字段的顺序,必须与Student(Parcel in)的读取顺序完全一致,否则会读出错位数据甚至崩溃。 -
接收端要做好判空
在结果回传或异常流程下,Intent 可能为
null或缺少某些 key;对象 extra 也可能为null。读取前应判空,避免空指针。 -
Intent extras 体积有限
Intent 传参底层依赖 Binder 事务,有体积上限。不要通过 extras 传递大对象/大图/大列表。
大数据建议改用:文件、数据库、共享单例仓库(如 ViewModel/Repository)或通过 Uri/ID 间接传递。
5. 常见坑清单
- 结果回传时
data可能为空:读取 extra 前应判空,避免崩溃。 - Intent 传参有体积上限:不要传大对象/大图片,必要时改用文件、数据库或共享仓库方案。