吃透 Android Activity:从生命周期到实战落地

目录

一、Activity 基础认知:到底什么是 Activity?

1.1 一句话搞懂 Activity 的核心作用

1.2 Activity 与其他组件的关系

1.3 Activity 的 “容器特性”:任务栈(Task Stack)

二、生命周期深度解析:7 个回调 + 6 个场景,再也不踩坑

2.1 先记牢:7 个核心生命周期回调方法

2.2 用代码实测:生命周期的执行顺序

示例 1:生命周期回调日志打印

2.3 6 个核心场景:生命周期到底怎么执行?

场景 1:首次启动 Activity

场景 2:点击按钮跳转至 SecondActivity

场景 3:从 SecondActivity 按返回键回到原 Activity

场景 4:按 Home 键返回桌面

场景 5:从桌面重新打开应用

场景 6:按返回键退出 Activity

特殊场景:屏幕旋转(配置变更)

2.4 生命周期的核心原则:“资源对称”

三、启动模式全解析:4 种模式 + 实战场景,避免返回键混乱

3.1 4 种启动模式的核心区别

1. standard(默认模式:标准模式)

2. singleTop(栈顶复用模式)

3. singleTask(栈内复用模式)

4. singleInstance(单实例模式)

3.2 启动模式对比表(一目了然)

3.3 实战示例:singleTop 模式的复用逻辑

示例 2:singleTop 模式下的栈顶复用

3.4 动态设置启动模式(Intent Flags)

四、Intent 与 Activity 通信:显式 / 隐式启动 + 数据传递全攻略

4.1 两种启动方式:显式启动 vs 隐式启动

1. 显式启动(常用,精准跳转)

示例 3:显式启动并传递基本数据

2. 隐式启动(灵活,跨应用跳转)

示例 4:隐式启动应用内 Activity

示例 5:跨应用隐式启动(打开浏览器)

4.2 数据传递进阶:传递对象与集合

1. Parcelable(高效,Android 原生支持)

示例 6:Parcelable 序列化传递对象

2. Serializable(简单,Java 原生支持)

4.3 带返回值的跳转:startActivityForResult

示例 7:用 Activity Result API 实现带返回值跳转

五、数据保存与恢复:解决屏幕旋转、内存不足导致的数据丢失

5.1 轻量级数据:onSaveInstanceState

示例 8:用 onSaveInstanceState 保存 EditText 内容

5.2 重量级数据:ViewModel(推荐)

示例 9:用 ViewModel 保存列表数据

5.3 禁止屏幕旋转(临时方案)

六、常见坑点与避坑指南(实战经验总结)

6.1 内存泄漏(最常见问题)

常见原因:

解决方案:

6.2 启动模式误用导致的问题

6.3 隐式启动匹配失败崩溃

6.4 onDestroy () 不执行

七、总结:Activity 核心知识点图谱


class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

一、Activity 基础认知:到底什么是 Activity?

1.1 一句话搞懂 Activity 的核心作用

Activity 是 Android 系统提供的 “用户交互容器”,本质上是一个可视化的界面载体。你打开某信的聊天页、刷某音的视频页、用某宝的下单页,每个独立的交互界面,都是一个 Activity 实例在工作。

简单说:Activity 负责 “展示界面” 和 “处理用户交互” —— 按钮点击、输入文本、滑动屏幕等操作,都需要通过 Activity 来响应;而界面上的文字、图片、按钮等控件,也需要依附于 Activity 才能显示。

1.2 Activity 与其他组件的关系

Android 四大组件(Activity、Service、BroadcastReceiver、ContentProvider)各司其职,Activity 是其中唯一直接面向用户的组件,和其他组件的配合关系很清晰:

  • 与 Service:Activity 负责前台交互,Service 负责后台任务(比如 Activity 触发音乐播放,Service 在后台持续播放);
  • 与 BroadcastReceiver:Activity 可发送广播(比如登录成功后发送广播),也可接收广播(比如接收网络状态变化通知);
  • 与 ContentProvider:Activity 通过 ContentProvider 获取数据(比如读取系统联系人、相册图片)。

1.3 Activity 的 “容器特性”:任务栈(Task Stack)

Android 用 “任务栈” 管理多个 Activity,就像叠盘子一样,遵循 “先进后出” 原则:

  • 启动新 Activity 时,新实例会被 “压入” 栈顶,成为当前显示的界面;
  • 按返回键时,栈顶的 Activity 会被 “弹出” 并销毁,上一个 Activity 回到栈顶;
  • 所有 Activity 都弹出后,任务栈为空,应用退出。

比如打开 “某信→聊天列表→聊天窗口” 的流程:

  1. 启动某信,MainActivity(主界面)入栈,栈内:[MainActivity];
  2. 点击聊天列表,ChatListActivity 入栈,栈内:[MainActivity, ChatListActivity];
  3. 点击某条聊天,ChatActivity 入栈,栈内:[MainActivity, ChatListActivity, ChatActivity];
  4. 连续按 3 次返回键,依次弹出 ChatActivity、ChatListActivity、MainActivity,应用退出。

这个机制很重要,后面讲启动模式时会频繁用到。


二、生命周期深度解析:7 个回调 + 6 个场景,再也不踩坑

Activity 的生命周期是核心中的核心,也是新手最容易出错的地方。很多 bug(比如数据丢失、内存泄漏)都和没理解生命周期有关。

2.1 先记牢:7 个核心生命周期回调方法

Activity 从创建到销毁,会经历一系列固定的 “状态变化”,系统会通过回调方法通知我们。这 7 个方法必须熟记,每个方法的作用和调用时机都很明确:

方法名调用时机核心作用注意事项
onCreate()Activity 首次创建时(仅调用 1 次)初始化布局、绑定控件、初始化数据必须调用 setContentView () 加载布局,避免耗时操作
onStart()Activity 即将可见时启动动画、注册非前台监听器此时界面还未获取焦点,不能交互
onResume()Activity 可见且可交互时启动前台服务、恢复播放、获取焦点进入 “运行状态”,是交互的核心阶段
onPause()Activity 即将失去焦点(部分可见)暂停动画、保存临时数据、释放部分资源执行速度要快,不能阻塞(影响新界面显示)
onStop()Activity 完全不可见时释放大内存资源、注销监听器避免持有重量级对象(如相机、传感器)
onRestart()Activity 停止后重新启动前恢复之前的状态(如刷新数据)仅在 “停止后重启” 时触发(比如返回键回到该页面)
onDestroy()Activity 销毁前(仅调用 1 次)彻底释放所有资源、取消异步任务不能依赖此方法保存数据(系统可能直接杀死进程)

2.2 用代码实测:生命周期的执行顺序

光看表格不够直观,我们写个简单示例,通过日志打印实际执行流程。

示例 1:生命周期回调日志打印
  1. 新建一个 Activity(LifeCycleActivity),重写所有生命周期方法:
public class LifeCycleActivity extends AppCompatActivity {
    private static final String TAG = "LifeCycleTest";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_life_cycle);
        Log.d(TAG, "onCreate:Activity创建");
        // 初始化控件(示例:绑定一个按钮)
        Button btnJump = findViewById(R.id.btn_jump);
        btnJump.setOnClickListener(v -> {
            // 跳转至新Activity
            startActivity(new Intent(this, SecondActivity.class));
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart:Activity即将可见");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume:Activity可交互");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause:Activity即将失去焦点");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop:Activity完全不可见");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart:Activity准备重启");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy:Activity即将销毁");
    }
}
  1. 新建 SecondActivity(仅作为跳转目标,无需复杂逻辑);
  2. 运行程序,观察 Logcat 日志,记录不同操作下的回调顺序。
2.3 6 个核心场景:生命周期到底怎么执行?

结合示例 1 的日志,我们分析 6 个实际开发中最常见的场景,彻底搞懂生命周期的执行逻辑:

场景 1:首次启动 Activity
  • 执行顺序:onCreate () → onStart () → onResume ()
  • 日志输出:
D/LifeCycleTest: onCreate:Activity创建
D/LifeCycleTest: onStart:Activity即将可见
D/LifeCycleTest: onResume:Activity可交互
  • 说明:Activity 从无到有,完成初始化并进入可交互状态。
场景 2:点击按钮跳转至 SecondActivity
  • 执行顺序(当前 Activity):onPause () → onStop ()
  • 日志输出:
D/LifeCycleTest: onPause:Activity即将失去焦点
D/LifeCycleTest: onStop:Activity完全不可见
  • 说明:当前 Activity 被新页面完全覆盖,先失去焦点,再变为不可见。
场景 3:从 SecondActivity 按返回键回到原 Activity
  • 执行顺序:onRestart () → onStart () → onResume ()
  • 日志输出:
D/LifeCycleTest: onRestart:Activity准备重启
D/LifeCycleTest: onStart:Activity即将可见
D/LifeCycleTest: onResume:Activity可交互
  • 说明:Activity 从 “停止状态” 恢复,先重启,再可见,最后可交互。
场景 4:按 Home 键返回桌面
  • 执行顺序:onPause () → onStop ()
  • 日志输出:
D/LifeCycleTest: onPause:Activity即将失去焦点
D/LifeCycleTest: onStop:Activity完全不可见
  • 说明:应用进入后台,Activity 并未销毁,只是停止状态。
场景 5:从桌面重新打开应用
  • 执行顺序:onRestart () → onStart () → onResume ()
  • 说明:和 “从 SecondActivity 返回” 逻辑一致,恢复后台应用。
场景 6:按返回键退出 Activity
  • 执行顺序:onPause () → onStop () → onDestroy ()
  • 日志输出:
D/LifeCycleTest: onPause:Activity即将失去焦点
D/LifeCycleTest: onStop:Activity完全不可见
D/LifeCycleTest: onDestroy:Activity即将销毁
  • 说明:Activity 被销毁,资源释放。
特殊场景:屏幕旋转(配置变更)

默认情况下,屏幕旋转会导致 Activity 销毁重建,生命周期执行:onPause () → onStop () → onDestroy () → onCreate () → onStart () → onResume ()

  • 问题:此时 EditText 中的输入内容、临时变量会丢失;
  • 解决方案:后面会讲 onSaveInstanceState () 和 ViewModel 的用法。

2.4 生命周期的核心原则:“资源对称”

这是我踩了很多坑后总结的关键原则:在哪个方法申请的资源,就在对应的方法中释放,避免内存泄漏。

常见的资源对称操作:

申请资源(方法)释放资源(方法)示例操作
onCreate()onDestroy()初始化数据库、创建单例对象
onStart()onStop()注册广播接收器、启动位置监听
onResume()onPause()启动动画、恢复音视频播放

反例:在 onCreate () 中注册广播接收器,却没在 onDestroy () 中注销,会导致 Activity 实例被广播持有,无法回收,造成内存泄漏。


三、启动模式全解析:4 种模式 + 实战场景,避免返回键混乱

Activity 的启动模式决定了新 Activity 实例如何创建、如何加入任务栈,直接影响返回键逻辑和用户体验。比如 “微信主界面无论怎么跳转,返回都能回到主界面”,就是用对了启动模式。

3.1 4 种启动模式的核心区别

Android 提供 4 种启动模式,可通过 AndroidManifest.xml 的android:launchMode属性配置,也可通过 Intent 的 Flags 动态设置(动态设置优先级更高)。

1. standard(默认模式:标准模式)
  • 核心规则:每次启动都创建新实例,不管栈中是否已存在;
  • 任务栈行为:新实例压入 “启动它的 Activity 所在的栈”;
  • 示例:打开新闻 App,多次点击 “详情页”,会创建多个详情页实例,按返回键需依次退出;
  • 适用场景:大多数普通页面(如新闻详情、商品详情);
  • 注意点:非 Activity 上下文(如 Service)启动时,需添加FLAG_ACTIVITY_NEW_TASK,否则报错。
2. singleTop(栈顶复用模式)
  • 核心规则:如果目标 Activity 已在栈顶,直接复用,不创建新实例;若不在栈顶,仍创建新实例;
  • 复用回调:复用时会触发onNewIntent(Intent intent)方法(需重写此方法处理新数据);
  • 示例:搜索页,若当前已在搜索页(栈顶),再次点击搜索按钮,复用当前页面并刷新结果;
  • 适用场景:通知跳转页、搜索结果页、消息详情页(避免栈顶重复创建)。
3. singleTask(栈内复用模式)
  • 核心规则:栈内唯一实例,若栈中已存在该 Activity,会将其上方的所有 Activity 全部弹出,让它回到栈顶并复用;
  • 任务栈行为:默认进入 “启动它的栈”,可通过taskAffinity属性指定新栈;
  • 复用回调:触发onNewIntent(Intent intent)方法;
  • 示例:微信主界面,无论从聊天页、朋友圈页怎么返回,都能直接回到主界面;
  • 适用场景:应用主界面、购物车页面(确保全局唯一,返回逻辑清晰)。
4. singleInstance(单实例模式)
  • 核心规则:全局唯一实例,且独占一个任务栈(栈内只有它自己);
  • 任务栈行为:其他应用启动该 Activity 时,直接复用这个独立栈中的实例;
  • 示例:系统电话界面、闹钟提醒界面(跨应用共享实例,避免重复创建);
  • 适用场景:系统级界面、跨应用共享的功能页(如支付界面)。

3.2 启动模式对比表(一目了然)

启动模式实例数量任务栈特性复用回调典型场景
standard多个压入启动方所在栈新闻详情、商品详情
singleTop多个(栈顶唯一)压入启动方所在栈onNewIntent()搜索结果、通知跳转
singleTask一个(栈内唯一)默认启动方栈,可指定新栈onNewIntent()应用主界面、购物车
singleInstance一个(全局唯一)独占独立栈onNewIntent()系统电话、闹钟提醒

3.3 实战示例:singleTop 模式的复用逻辑

示例 2:singleTop 模式下的栈顶复用
  1. 在 AndroidManifest.xml 中配置启动模式:
<activity
    android:name=".SingleTopActivity"
    android:launchMode="singleTop" /> <!-- 配置singleTop模式 -->
  1. 重写 SingleTopActivity 的onNewIntent方法:
public class SingleTopActivity extends AppCompatActivity {
    private static final String TAG = "SingleTopTest";
    private TextView tvData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_top);
        tvData = findViewById(R.id.tv_data);
        // 接收首次启动的数据
        handleIntent(getIntent());

        // 按钮:再次启动当前Activity
        findViewById(R.id.btn_restart_self).setOnClickListener(v -> {
            Intent intent = new Intent(this, SingleTopActivity.class);
            intent.putExtra("data", "新数据:" + System.currentTimeMillis());
            startActivity(intent);
        });
    }

    // 复用实例时触发,处理新数据
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d(TAG, "onNewIntent:复用栈顶实例");
        handleIntent(intent); // 重新处理新数据
    }

    // 处理Intent数据
    private void handleIntent(Intent intent) {
        if (intent != null && intent.hasExtra("data")) {
            String data = intent.getStringExtra("data");
            tvData.setText(data);
            Log.d(TAG, "接收数据:" + data);
        }
    }
}
  1. 运行测试:
  • 首次启动:执行 onCreate (),显示初始数据;
  • 点击 “重新启动当前 Activity”:栈顶已存在该实例,触发 onNewIntent (),界面更新为新数据,未创建新实例;
  • 日志输出:onNewIntent:复用栈顶实例 + 新数据日志。

3.4 动态设置启动模式(Intent Flags)

除了在 Manifest 中配置,还可以通过 Intent 的 Flags 动态设置启动模式,更灵活:

  • FLAG_ACTIVITY_NEW_TASK:等同于 singleTask 模式(非 Activity 上下文必须用);
  • FLAG_ACTIVITY_SINGLE_TOP:等同于 singleTop 模式;
  • FLAG_ACTIVITY_CLEAR_TOP:清空目标 Activity 上方的所有 Activity(常与 singleTask 配合);
  • FLAG_ACTIVITY_NO_HISTORY:Activity 退出后不保留在任务栈(如登录跳转页)。

示例:动态设置 singleTop 模式:

Intent intent = new Intent(this, TargetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // 动态设置
startActivity(intent);

四、Intent 与 Activity 通信:显式 / 隐式启动 + 数据传递全攻略

Activity 之间的跳转和数据传递,全靠 Intent(意图)实现。Intent 就像 “信使”,负责携带跳转目标和数据。

4.1 两种启动方式:显式启动 vs 隐式启动

1. 显式启动(常用,精准跳转)

直接指定目标 Activity 的全类名,明确告诉系统要启动哪个 Activity,适用于应用内跳转。

示例 3:显式启动并传递基本数据
// 发送方(MainActivity)
findViewById(R.id.btn_jump).setOnClickListener(v -> {
    // 1. 创建Intent,指定上下文和目标Activity
    Intent intent = new Intent(MainActivity.this, ReceiveActivity.class);
    // 2. 携带数据(键值对形式,支持基本数据类型、String、Parcelable等)
    intent.putExtra("name", "张三");
    intent.putExtra("age", 25);
    intent.putExtra("isStudent", true);
    // 3. 启动Activity
    startActivity(intent);
});

// 接收方(ReceiveActivity)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_receive);
    // 获取Intent中的数据(第二个参数为默认值,防止数据为null)
    Intent intent = getIntent();
    String name = intent.getStringExtra("name", "未知");
    int age = intent.getIntExtra("age", 0);
    boolean isStudent = intent.getBooleanExtra("isStudent", false);
    // 显示数据
    TextView tvResult = findViewById(R.id.tv_result);
    tvResult.setText("姓名:" + name + "\n年龄:" + age + "\n是否学生:" + isStudent);
}
2. 隐式启动(灵活,跨应用跳转)

不指定具体的 Activity 类名,而是通过 “Action、Category、Data” 等条件匹配,适用于跨应用跳转(如打开浏览器、拨打电话)或应用内组件解耦。

隐式启动的核心是 “匹配规则”:Intent 必须满足目标 Activity 在 Manifest 中声明的<intent-filter>条件。

示例 4:隐式启动应用内 Activity
  1. 在 Manifest 中为目标 Activity 配置 intent-filter:
<activity android:name=".ImplicitActivity">
    <intent-filter>
        <!-- Action:自定义动作(需唯一,建议用包名前缀) -->
        <action android:name="com.example.action.VIEW_DETAIL" />
        <!-- Category:默认类别(必须添加,否则匹配失败) -->
        <category android:name="android.intent.category.DEFAULT" />
        <!-- Data:数据格式限制(可选,如指定URL Scheme) -->
        <data android:scheme="app" android:host="detail" />
    </intent-filter>
</activity>
  1. 发送方通过条件匹配启动:
findViewById(R.id.btn_implicit_jump).setOnClickListener(v -> {
    Intent intent = new Intent();
    // 设置Action(与Manifest中一致)
    intent.setAction("com.example.action.VIEW_DETAIL");
    // 设置Data(与Manifest中scheme、host一致)
    intent.setData(Uri.parse("app://detail?id=123"));
    // 启动(建议先判断是否有Activity匹配,避免崩溃)
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    } else {
        Toast.makeText(this, "无匹配的Activity", Toast.LENGTH_SHORT).show();
    }
});
示例 5:跨应用隐式启动(打开浏览器)

无需配置,直接使用系统预设的 Action:

// 打开百度网页
findViewById(R.id.btn_open_browser).setOnClickListener(v -> {
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW); // 系统预设Action:查看内容
    intent.setData(Uri.parse("https://www.baidu.com")); // 网页URL
    startActivity(intent);
});

// 拨打电话(需添加权限)
// 1. 在Manifest中添加权限:<uses-permission android:name="android.permission.CALL_PHONE" />
// 2. 代码:
findViewById(R.id.btn_make_call).setOnClickListener(v -> {
    Intent intent = new Intent(Intent.ACTION_CALL);
    intent.setData(Uri.parse("tel:10086"));
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
        Toast.makeText(this, "请授予拨打电话权限", Toast.LENGTH_SHORT).show();
        return;
    }
    startActivity(intent);
});

4.2 数据传递进阶:传递对象与集合

基本数据类型满足不了复杂需求时,需要传递对象(如 User、Product),Android 支持两种序列化方式:Parcelable(推荐)和 Serializable。

1. Parcelable(高效,Android 原生支持)
示例 6:Parcelable 序列化传递对象
  1. 创建 User 类并实现 Parcelable 接口(可通过 Android Studio 自动生成):
public class User implements Parcelable {
    private String name;
    private int age;
    private String address;

    // 构造方法
    public User(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    // 从Parcel中读取数据(顺序需与writeToParcel一致)
    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
        address = in.readString();
    }

    // 必须实现的方法(直接返回0即可)
    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];
        }
    };

    // 写入Parcel(顺序需与读取顺序一致)
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(address);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    // getter/setter方法
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getAddress() { return address; }
}
  1. 传递与接收对象:
// 发送方
User user = new User("李四", 30, "北京市海淀区");
Intent intent = new Intent(this, UserDetailActivity.class);
intent.putExtra("user", user); // 直接传递Parcelable对象
startActivity(intent);

// 接收方
User user = getIntent().getParcelableExtra("user");
if (user != null) {
    tvUserInfo.setText("姓名:" + user.getName() + "\n年龄:" + user.getAge() + "\n地址:" + user.getAddress());
}
2. Serializable(简单,Java 原生支持)

实现 Serializable 接口即可,无需额外代码,但效率比 Parcelable 低,适合简单对象:

public class Product implements Serializable {
    private String productName;
    private double price;
    // 构造方法、getter/setter
}

// 传递:intent.putExtra("product", new Product("手机", 2999));
// 接收:Product product = (Product) getIntent().getSerializableExtra("product");

4.3 带返回值的跳转:startActivityForResult

场景:从 AActivity 跳转到 BActivity,BActivity 处理完成后,返回数据给 AActivity(如选择图片后返回图片路径、登录后返回用户信息)。

注意:AndroidX 中推荐使用Activity Result API(替代旧的startActivityForResult),更简洁且无内存泄漏风险。

示例 7:用 Activity Result API 实现带返回值跳转
  1. 发送方(AActivity):
public class AActivity extends AppCompatActivity {
    private TextView tvResult;
    // 1. 注册ActivityResult回调
    private ActivityResultLauncher<Intent> launcher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                // 3. 接收返回结果
                if (result.getResultCode() == RESULT_OK) {
                    Intent data = result.getData();
                    if (data != null) {
                        String selectResult = data.getStringExtra("select_result");
                        tvResult.setText("选择结果:" + selectResult);
                    }
                }
            }
    );

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        tvResult = findViewById(R.id.tv_result);

        // 2. 触发跳转
        findViewById(R.id.btn_jump_to_b).setOnClickListener(v -> {
            Intent intent = new Intent(this, BActivity.class);
            launcher.launch(intent); // 用注册的launcher启动
        });
    }
}
  1. 接收方(BActivity):
public class BActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);

        // 选择数据后返回
        findViewById(R.id.btn_select).setOnClickListener(v -> {
            Intent resultIntent = new Intent();
            resultIntent.putExtra("select_result", "选中了第3条数据");
            // 设置返回结果(RESULT_OK表示成功)
            setResult(RESULT_OK, resultIntent);
            finish(); // 关闭当前Activity
        });
    }
}

五、数据保存与恢复:解决屏幕旋转、内存不足导致的数据丢失

实际开发中,Activity 可能因屏幕旋转、系统内存不足被销毁,导致临时数据(如 EditText 输入内容、列表滚动位置)丢失。这部分要掌握两种核心解决方案。

5.1 轻量级数据:onSaveInstanceState

系统在 Activity 异常销毁前(如屏幕旋转、内存不足),会自动调用onSaveInstanceState(Bundle outState)方法,允许我们保存临时数据。

示例 8:用 onSaveInstanceState 保存 EditText 内容
public class SaveStateActivity extends AppCompatActivity {
    private EditText etInput;
    private static final String KEY_INPUT = "key_input";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_save_state);
        etInput = findViewById(R.id.et_input);

        // 恢复数据( savedInstanceState不为null表示异常销毁后重建)
        if (savedInstanceState != null) {
            String inputText = savedInstanceState.getString(KEY_INPUT, "");
            etInput.setText(inputText);
            // 恢复光标位置
            etInput.setSelection(inputText.length());
        }
    }

    // 保存数据(异常销毁前调用)
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // 保存EditText中的输入内容
        outState.putString(KEY_INPUT, etInput.getText().toString());
    }
}

5.2 重量级数据:ViewModel(推荐)

onSaveInstanceState 适合保存少量简单数据(如 String、int),对于复杂数据(如列表数据、网络请求结果),推荐用 ViewModel。ViewModel 的生命周期与 Activity 的配置变更无关,屏幕旋转时不会被销毁,数据自然保留。

示例 9:用 ViewModel 保存列表数据
  1. 添加依赖(AndroidX):
dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
}
  1. 创建 ViewModel 类:
public class MyViewModel extends ViewModel {
    // 保存列表数据(ViewModel销毁时才会丢失)
    private MutableLiveData<List<String>> dataList = new MutableLiveData<>();

    // 设置数据
    public void setDataList(List<String> list) {
        dataList.setValue(list);
    }

    // 获取数据(通过LiveData观察变化)
    public LiveData<List<String>> getDataList() {
        return dataList;
    }
}
  1. 在 Activity 中使用:
public class ViewModelActivity extends AppCompatActivity {
    private MyViewModel viewModel;
    private ListView lvData;
    private ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_model);
        lvData = findViewById(R.id.lv_data);

        // 1. 获取ViewModel实例(与Activity生命周期绑定)
        viewModel = new ViewModelProvider(this).get(MyViewModel.class);

        // 2. 观察数据变化
        viewModel.getDataList().observe(this, list -> {
            if (list != null) {
                adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, list);
                lvData.setAdapter(adapter);
            }
        });

        // 3. 模拟网络请求获取数据(屏幕旋转后数据不会丢失)
        findViewById(R.id.btn_load_data).setOnClickListener(v -> {
            List<String> data = new ArrayList<>();
            data.add("数据1");
            data.add("数据2");
            data.add("数据3");
            viewModel.setDataList(data); // 保存到ViewModel
        });
    }
}

5.3 禁止屏幕旋转(临时方案)

如果不需要适配横竖屏,可直接禁止屏幕旋转,避免 Activity 重建:

<activity
    android:name=".NoRotateActivity"
    android:screenOrientation="portrait" /> <!-- portrait:竖屏,landscape:横屏 -->

六、常见坑点与避坑指南(实战经验总结)

6.1 内存泄漏(最常见问题)

常见原因:
  1. 静态变量持有 Activity 引用(如static Activity mActivity;);
  2. 非静态内部类 / 匿名类持有 Activity(如 Handler、Thread 未及时销毁);
  3. 广播接收器、监听器未注销;
  4. 图片加载框架未取消请求(如 Glide 未调用clear())。
解决方案:
  • 避免用静态变量持有 Activity,如需使用 Context,优先用getApplicationContext()
  • Handler 使用静态内部类 + WeakReference:
// 正确的Handler写法
private static class MyHandler extends Handler {
    private WeakReference<MyActivity> mActivityRef;

    public MyHandler(MyActivity activity) {
        mActivityRef = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        MyActivity activity = mActivityRef.get();
        if (activity != null && !activity.isFinishing()) {
            // 处理消息
        }
    }
}

// 在onDestroy中移除消息
@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}
  • 广播、监听器在对应的生命周期方法中注销;
  • 用 LeakCanary 工具检测内存泄漏(集成后自动提示泄漏位置)。

6.2 启动模式误用导致的问题

  • 问题 1:singleTask 模式导致 Activity 被异常销毁(如栈内已有实例,启动时会销毁上方 Activity);
  • 解决方案:明确场景后使用,避免给普通页面设置 singleTask;
  • 问题 2:standard 模式导致页面重复创建(如多次跳转详情页,返回键需多次点击);
  • 解决方案:根据需求切换为 singleTop 模式。

6.3 隐式启动匹配失败崩溃

  • 原因:Intent 的 Action、Category、Data 与目标 Activity 的 intent-filter 不匹配;
  • 解决方案:启动前用intent.resolveActivity(getPackageManager()) != null判断,避免崩溃。

6.4 onDestroy () 不执行

  • 场景:系统内存不足时,可能直接杀死 Activity 进程,不调用 onDestroy ();
  • 解决方案:不要在 onDestroy () 中保存关键数据(改用 onPause () 或 SharedPreferences),仅用于释放资源。

七、总结:Activity 核心知识点图谱

到这里,Activity 的核心内容已经全部讲完,我们用一张图谱梳理重点:

Activity核心知识点
├── 基础认知:界面载体、任务栈(先进后出)
├── 生命周期:7个回调+资源对称原则
├── 启动模式:4种模式+适用场景+onNewIntent
├── 数据传递:显式/隐式启动+Parcelable/Serializable+Activity Result API
├── 数据保存:onSaveInstanceState(轻量)+ ViewModel(重量)
├── 避坑指南:内存泄漏、启动模式误用、隐式启动匹配

其实 Activity 的学习没有捷径,关键是 “理解 + 实操”—— 把文中的示例逐个敲一遍,结合日志观察执行流程,再在实际项目中应用,很快就能熟练掌握。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值