Android Activity 页面导航基础:Manifest 声明、Intent 显式/隐式跳转与数据传递

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 注意事项:顺序一致、判空与体积限制

为了让"对象传递"在真实项目里稳定运行,建议重点记住三件事:

  1. Parcelable 的读写顺序必须一致
    writeToParcel() 写入字段的顺序,必须与 Student(Parcel in) 的读取顺序完全一致,否则会读出错位数据甚至崩溃。

  2. 接收端要做好判空

    在结果回传或异常流程下,Intent 可能为 null 或缺少某些 key;对象 extra 也可能为 null。读取前应判空,避免空指针。

  3. Intent extras 体积有限

    Intent 传参底层依赖 Binder 事务,有体积上限。不要通过 extras 传递大对象/大图/大列表。

    大数据建议改用:文件、数据库、共享单例仓库(如 ViewModel/Repository)或通过 Uri/ID 间接传递。


5. 常见坑清单

  1. 结果回传时 data 可能为空:读取 extra 前应判空,避免崩溃。
  2. Intent 传参有体积上限:不要传大对象/大图片,必要时改用文件、数据库或共享仓库方案。
相关推荐
小王不爱笑1321 小时前
LangChain4j 项目实战--1:硅谷小智(医疗智能客服)笔记
java
忍者必须死2 小时前
JDK1.7的HashMap的环形链表
java·数据结构·算法·链表
鹿角片ljp2 小时前
短信登录:基于 Session 实现(黑马点评实战)
java·服务器·spring boot·mybatis
北风toto2 小时前
JDK8(JAVA)供应商说明
java·开发语言
清水白石0082 小时前
观察者模式全解析:用 Python 构建优雅的事件系统,让组件彻底解耦
java·python·观察者模式
xiaoccii2 小时前
C++(入门版)
java·c++·算法
上下求索,莫负韶华2 小时前
java-(double,BigDecimal),sql-(decimal,nuermic)
java·开发语言·sql
ID_180079054732 小时前
淘宝商品详情 API 接口 item_get: 高效获取商品数据的技术方案
java·前端·数据库
间彧2 小时前
布隆过滤器详解与Redis+Spring Boot实战指南
java