Android Activity 生命周期、启动模式与性能优化全解析

📅 2026/7/29 8:13:44
Android Activity 生命周期、启动模式与性能优化全解析
1. Activity 是什么为什么它是 Android 开发的基石如果你刚开始接触 Android 开发或者已经写过一些简单的“Hello World”应用那么“Activity”这个词你一定不陌生。它几乎是每个 Android 应用的门面是你与用户交互的主舞台。但你真的理解它了吗我见过不少开发者能照着教程写出一个能跑的 Activity但一旦遇到页面跳转数据丢失、屏幕旋转后应用崩溃、或者想实现一个复杂的多页面流程时就一头雾水四处搜索“碎片化”的解决方案。这往往是因为对 Activity 的生命周期、启动模式、数据传递等核心机制缺乏系统性的理解。简单来说Activity 是一个包含用户界面的组件用于与用户进行单屏交互。你可以把它想象成话剧中的一幕场景。一个应用就像一整部话剧可能由多个场景Activity组成例如登录场景、主列表场景、详情编辑场景。用户通过在这些场景间切换完成完整的应用体验。作为开发者你的核心工作之一就是设计好每一个“场景”并管理好它们之间的切换逻辑和数据流转。理解 Activity不仅仅是知道如何创建一个类继承android.app.Activity更重要的是要掌握其背后的设计哲学和运行机制这是构建稳定、流畅、符合用户预期的 Android 应用的基础。无论你是想实现一个精致的单页应用还是一个拥有复杂导航结构的商业级 AppActivity 都是你绕不开的核心课题。2. Activity 生命周期从诞生到消亡的完整旅程理解 Activity 的生命周期是避免应用崩溃、内存泄漏和提升用户体验的关键。它描述了 Activity 从创建到销毁过程中系统会回调的一系列方法。你可以把这些方法看作是 Activity 一生中的重要节点系统会在这些节点通知你“现在 Activity 可见了”、“现在它被部分遮挡了”、“现在它完全不可见了你可以释放一些资源了”。你的任务就是在正确的节点做正确的事。2.1 生命周期回调方法全解析Android 系统通过一系列定义好的回调方法来管理 Activity 的生命周期。我们按顺序来拆解onCreate()这是生命周期的第一个方法也是你必须重写的方法。当系统首次创建 Activity 时例如用户点击应用图标启动或者从一个 Activity 跳转到新的 Activity会调用此方法。在这个方法里你应该完成所有静态的初始化工作加载布局setContentView、绑定视图控件findViewById、初始化必要的数据结构。但请注意onCreate只应负责“创建”视图而不应进行与用户交互相关的操作因为此时 Activity 对用户还不可见。注意onCreate方法接收一个Bundle类型的参数savedInstanceState。这个参数至关重要它用于在 Activity 被系统意外销毁例如屏幕旋转、内存不足后重建时恢复之前的状态。如果savedInstanceState不为 null说明这是一个重建过程你需要从中取出之前保存的数据来恢复 UI 状态。onStart()在onCreate()之后onStart()被调用。此时Activity 对用户即将可见但还没有出现在前台并开始与用户交互。你可以在这里注册一些需要实时更新的监听器例如广播接收器但更常见的做法是在onResume中做这件事。onResume()这是 Activity 进入“活跃”状态的标志。调用此方法后Activity 位于 Activity 栈的栈顶并获取用户输入焦点用户可以与之交互。这里是你启动动画、开启摄像头预览、连接传感器服务或恢复音频播放的最佳位置。任何需要持续运行或响应用户操作的代码通常在这里初始化。onPause()当系统准备启动或恢复另一个 Activity 时当前 Activity 的onPause()会被调用。这意味着当前 Activity 正在失去焦点但依然部分可见例如启动了一个透明或非全屏的 Activity。在此方法中你应该暂停或调整那些不需要在后台持续运行的操作例如动画、摄像头、传感器等以节省资源。但注意onPause()的执行时间必须非常短因为它会阻塞下一个 Activity 的启动。onStop()当 Activity 对用户完全不可见时onStop()被调用。例如用户跳转到了一个新的全屏 Activity或者按下了 Home 键回到桌面。此时Activity 不再可见可以释放大量用户界面相关的资源。你可以在这里注销在onStart中注册的、不需要在后台运行的监听器或者保存一些待提交的数据到数据库。onDestroy()这是 Activity 被销毁前收到的最后一个回调。调用此方法有两种情况一是 Activity 被主动结束例如调用了finish()二是由于配置变更如屏幕旋转或内存不足系统临时销毁了 Activity 实例以重建。你应该在这里释放所有占用的资源确保没有内存泄漏例如注销所有全局监听器、关闭数据库连接等。2.2 生命周期状态迁移与实战场景上面是主线剧情但 Activity 的生命周期并非总是单向的。用户的一个操作可能触发状态的来回切换。理解这些迁移路径才能写出健壮的代码。可见生命周期onStart()到onStop()在这两个方法之间Activity 对用户是可见的可能在前台也可能被部分遮挡。我们可以在这个区间管理那些与 UI 可见性相关的资源。例如在onStart中开始从网络加载数据但不要阻塞主线程在onStop中停止加载。前台生命周期onResume()到onPause()这是 Activity 真正与用户交互的黄金时间。任何需要独占用户注意力或系统资源如摄像头的操作都应严格限定在此区间内进行和暂停。一个经典的场景是屏幕旋转。当用户旋转设备时默认情况下当前的 Activity 会被销毁onPause-onStop-onDestroy然后系统会根据新的屏幕配置横屏/竖屏重新创建一个 Activity 实例onCreate-onStart-onResume。如果你在 Activity 中持有了一些临时数据比如用户正在输入的文本没有妥善处理旋转后这些数据就会丢失。解决方案就是利用onSaveInstanceState(Bundle outState)方法在onStop()之前通常紧接在onPause之后将需要保存的数据存入outStateBundle 中然后在onCreate或onRestoreInstanceState中取出恢复。实战心得很多新手会把数据保存和恢复的逻辑写在onPause里这并不完全正确。onPause可能被频繁调用比如弹出权限申请对话框而onSaveInstanceState是系统专门为状态保存设计的回调它只在 Activity可能被销毁时调用时机更准确。对于轻量级、与 UI 相关的瞬时状态如滚动位置、复选框状态使用onSaveInstanceState对于需要持久化的数据如用户编辑的文章则应该在onPause或onStop中保存到数据库或文件中。3. Activity 的启动与通信构建页面间的桥梁一个应用很少只有一个 Activity。如何启动新的页面并在页面间传递数据是日常开发中最频繁的操作。3.1 显式 Intent 与隐式 Intent启动一个 Activity核心工具是Intent意图。Intent 分为显式和隐式两种。显式 Intent明确指定要启动的 Activity 的类名。这是最常用、最直接的方式用于启动你自己应用内的组件。// Kotlin 示例 val intent Intent(this, TargetActivity::class.java) intent.putExtra(key_name, value_data) startActivity(intent) // Java 示例 Intent intent new Intent(MainActivity.this, TargetActivity.class); intent.putExtra(key_name, value_data); startActivity(intent);通过putExtra方法你可以附加一些基本类型或可序列化的数据到 Intent 中传递给目标 Activity。隐式 Intent不指定具体的组件类名而是声明一个要执行的动作Action以及动作所操作的数据Data和类型Type。系统会找到所有能处理此 Intent 的组件可能是你自己应用内的也可能是其他应用的让用户选择。// 打开一个网页 val intent Intent(Intent.ACTION_VIEW, Uri.parse(https://www.example.com)) startActivity(intent) // 分享文本内容 val sendIntent Intent().apply { action Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, This is my text to send.) type text/plain } startActivity(Intent.createChooser(sendIntent, Share via))隐式 Intent 是实现应用间协作的基石。为了让你的 Activity 能响应其他应用的隐式 Intent你需要在AndroidManifest.xml文件中为该 Activity 配置intent-filter。3.2 数据传递不止是 putExtra简单的数据传递用putExtra和getXXXExtra就够了。但对于复杂对象你需要让该对象实现Parcelable或Serializable接口。Parcelable是 Android 特有的设计用于进程间通信效率远高于Serializable是推荐做法。现在有很多注解处理器如Parcelize可以帮你自动生成Parcelable代码大大简化了操作。然而putExtra有其局限性它传递的数据大小是有限制的通常在 1MB 左右不同厂商有差异且不适合传递大量的结构化数据或需要共享的数据。更优雅的方案使用 ViewModel 和共享 ViewModel。对于同一个 Activity 内 Fragment 间的通信或者有紧密关联的 Activity 之间使用共享 ViewModel 是官方推荐的最佳实践。ViewModel 的生命周期比 Activity 长在配置变更时不会被销毁可以持有和管理 UI 相关的数据。通过将 ViewModel 与特定的 Activity 作用域或导航图作用域关联不同的组件可以访问到同一个 ViewModel 实例从而实现数据共享。对于需要返回结果的启动使用startActivityForResult在旧 API 中或新的Activity Result API。新的 API 更模块化、更安全避免了在onActivityResult方法中写一堆requestCode判断的混乱代码。你需要先注册一个结果契约ActivityResultContract然后启动 Activity结果会回调到你注册的 lambda 表达式中。// 使用 Activity Result API 获取图片 val getContent registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? - // 处理返回的 Uri uri?.let { imageView.setImageURI(it) } } // 在某个按钮点击事件中调用 button.setOnClickListener { getContent.launch(image/*) }4. Activity 的启动模式管理任务栈的导航逻辑当你不断启动新的 Activity系统会用一个“返回栈”Back Stack来管理它们。默认情况下每次启动新 Activity 都会在栈顶创建一个新实例。但有时你需要不同的行为比如一个登录页只应有一个实例或者从应用外跳转到某个详情页时不应保留之前的页面栈。这就是启动模式Launch Mode要解决的问题。4.1 四种标准启动模式你可以在AndroidManifest.xml中通过android:launchMode属性为 Activity 指定启动模式。standard标准模式默认模式。每次启动该 Activity都会在启动它的那个任务栈顶创建一个新的实例。因此同一个 Activity 可能有多个实例存在于不同的任务栈中。singleTop栈顶复用模式如果目标 Activity 已经位于启动它的任务栈的栈顶则不会创建新实例而是复用这个栈顶实例并会回调其onNewIntent(Intent)方法。如果目标 Activity 不在栈顶则行为同 standard。这适用于防止快速连续点击同一个按钮导致重复打开同一页面的情况比如消息通知跳转。singleTask栈内复用模式系统会寻找或创建一个新的任务栈具体行为与taskAffinity属性有关并确保在这个任务栈中该 Activity 只有一个实例。如果该实例已存在则系统会把它上面的所有其他 Activity 都销毁使其回到栈顶并回调onNewIntent。这常用于应用的主页或登录页保证这类页面在全局的唯一性。singleInstance单实例模式这是最特殊的一种。具有此模式的 Activity 会独占一个任务栈并且这个任务栈中只有它自己。后续再启动这个 Activity都会复用这个唯一的实例。它通常用于需要与多个应用共享的 Activity比如一个拨号器应用。4.2 Intent Flags 的动态控制除了静态声明你还可以在代码中通过给 Intent 设置 Flag 来动态控制启动行为这比静态声明更灵活。Intent.FLAG_ACTIVITY_NEW_TASK在一个新的任务中启动 Activity。如果该 Activity 已有实例运行在某个任务中则会把那个任务整体切换到前台。Intent.FLAG_ACTIVITY_CLEAR_TOP如果目标 Activity 已经在当前任务栈中则销毁它之上的所有 Activity使其位于栈顶。常与SINGLE_TOP或NEW_TASK结合使用。Intent.FLAG_ACTIVITY_SINGLE_TOP效果等同于singleTop启动模式。Intent.FLAG_ACTIVITY_CLEAR_TASK与NEW_TASK结合使用会在启动新 Activity 前清除与新任务关联的原有任务栈中的所有 Activity。这常用于实现“退出登录回到全新的登录页”的效果。常见问题排查启动模式配置不当最容易导致的问题是“回退栈混乱”。例如从通知栏点击跳转到应用内某个深层页面按返回键时用户期望的是回到应用的主页但实际上却可能直接退出了应用。这时你需要结合taskAffinity和Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK等标志来重新组织任务栈。调试时可以使用adb shell dumpsys activity activities命令来查看当前系统中所有任务栈和 Activity 的状态这是分析此类问题的利器。5. Activity 与 Window、View 的关系深入 UI 渲染底层我们常说 Activity 是“界面”但严格来说Activity 本身并不直接绘制任何东西。它是一个控制器Controller负责管理生命周期和事件交互而真正的 UI 绘制是由Window和View体系完成的。5.1 从 Activity 到 View 的渲染链路Activity 持有一个 Window每个 Activity 在attach方法中会被关联一个Window实例具体是PhoneWindow。Window 是一个抽象概念代表一个顶级窗口。Window 管理 DecorViewPhoneWindow内部包含一个DecorView它是整个窗口的根视图。DecorView本身是一个FrameLayout它包含两个部分系统装饰部分如标题栏、ActionBar和内容部分。setContentView 做了什么当你调用setContentView(R.layout.activity_main)时Activity 会委托给它的WindowWindow会使用LayoutInflater将你的布局 XML 文件解析成View对象树并将这棵树添加到DecorView的内容区域一个FrameLayoutid 为android.R.id.content中。View 树的测量与绘制之后ViewRootImpl连接WindowManager和DecorView的纽带会开始整个视图树的遍历过程测量Measure - 布局Layout - 绘制Draw。这个过程最终将像素渲染到屏幕上。理解这个链条有助于你解决一些深层 UI 问题。例如为什么onCreate中获取不到 View 的宽高因为此时View刚刚被创建还未开始测量流程。正确的做法是在onWindowFocusChanged中获取或者使用View.post(Runnable)。5.2 主题、样式与 Window 属性Activity 的外观如是否有标题栏、是否全屏、状态栏颜色很大程度上由Window的属性控制而这些属性又受到应用主题Theme和样式Style的影响。设置全屏可以在主题中设置item nameandroid:windowFullscreentrue/item或者在代码中调用window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)。注意代码设置需要在setContentView之前调用。修改状态栏颜色在 Android 5.0 (API 21) 及以上可以通过window.statusBarColor设置。但要处理好浅色状态栏图标让状态栏文字和图标变黑的问题这需要设置window.decorView.systemUiVisibility或使用WindowInsetsControllerAPI 30。沉浸式模式实现真正的全屏隐藏状态栏和导航栏需要与SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN等标志位打交道并妥善处理用户交互导致的系统栏重新显示。实操心得UI 适配问题尤其是刘海屏、挖孔屏、折叠屏的适配其核心就在于与Window和WindowInsets的交互。从 Android 11 开始官方推荐使用WindowCompat.setDecorFitsSystemWindows(window, false)配合View.setOnApplyWindowInsetsListener来精确控制内容如何避开系统栏区域而不是过去那种简单粗暴的fitsSystemWindows属性。6. 配置变更与数据持久化应对系统“重启”前面提到屏幕旋转会导致 Activity 销毁重建。这属于“配置变更”Configuration Change。除了屏幕旋转还有语言切换、字体大小调整、暗黑模式切换等都可能触发配置变更。默认情况下Activity 会重建以加载新的资源如横屏布局layout-land。6.1 手动处理配置变更如果你不希望 Activity 在特定配置变更时重建可以在AndroidManifest.xml中为该 Activity 配置android:configChanges属性。例如activity android:name.MyActivity android:configChangesorientation|screenSize|keyboardHidden /activity这样当屏幕方向改变时Activity 不会重建而是会回调onConfigurationChanged(Configuration newConfig)方法让你有机会手动调整 UI。警告官方并不推荐广泛使用configChanges。因为你需要手动处理所有因配置变更带来的 UI 更新这很容易出错尤其是涉及到资源 ID 变化时。通常只用于处理一些重建成本极高、且你自信能手动处理好的场景比如相机预览或游戏画面。6.2 使用 ViewModel 和 onSaveInstanceState 保留数据对于配置变更更现代、更可靠的解决方案是结合使用ViewModel和onSaveInstanceState。ViewModel用于持有和管理与 UI 相关的数据。它的生命周期比 Activity 长在配置变更时不会被销毁。因此像从网络加载的用户列表、复杂的表单数据等应该放在 ViewModel 中。当 Activity 重建后新的 Activity 实例可以获取到同一个 ViewModel 实例数据自然得以保留。onSaveInstanceState用于保存轻量的、与 UI 状态相关的瞬时数据例如滚动位置、文本框中的临时输入如果未自动保存到 ViewModel。这些数据会被序列化到 Bundle 中在重建时恢复。注意Bundle 有大小限制通常约 1MB不适合存放大数据或复杂对象。最佳实践模式长期有效、与业务逻辑相关的数据如用户信息、商品列表 - 存储在ViewModel或Repository数据仓库中。瞬时 UI 状态如列表滚动位置、临时未提交的输入 - 通过onSaveInstanceState保存。需要持久化存储的数据如用户设置、草稿 - 保存到SharedPreferences、数据库Room或文件中。这样分层处理既能保证良好的用户体验旋转后数据不丢失又能使代码职责清晰易于维护。7. 性能优化与内存管理打造流畅的 Activity一个卡顿或内存泄漏的 Activity 会严重影响应用口碑。以下是几个关键的优化方向。7.1 避免内存泄漏Activity 是最容易发生内存泄漏的地方之一因为它通常持有大量 View 和 Context 引用。避免非静态内部类/匿名内部类持有 Activity 引用例如在 Activity 中定义一个非静态的Handler或者一个作为监听器的匿名内部类。如果这些对象被长生命周期的对象如一个后台线程持有就会导致 Activity 无法被回收。解决方案使用静态内部类 弱引用WeakReference或者使用 ViewModel 和 LiveData 来管理异步操作。正确注销监听器和回调在onDestroy或相应的生命周期回调中如onPause注销传感器确保注销所有注册到系统或其他长生命周期组件的监听器、广播接收器、回调接口等。谨慎使用 Context对于需要 Application 级别 Context 的地方如单例模式、数据库初始化使用getApplicationContext()而不是 Activity 的this。因为 Application Context 的生命周期与应用一致不会导致 Activity 泄漏。7.2 优化启动速度与 UI 渲染减少 onCreate 负担onCreate方法执行时间直接影响冷启动速度和页面跳转速度。避免在这里进行耗时操作网络请求、大量数据库查询、复杂计算。将这些操作移到后台线程或使用异步初始化框架。优化布局层次过于复杂的 View 层级会显著增加测量和绘制时间。使用Layout Inspector工具检查布局尽量扁平化减少不必要的ViewGroup嵌套。考虑使用ConstraintLayout替代多层嵌套的LinearLayout和RelativeLayout。避免主线程阻塞任何耗时操作16ms都会导致掉帧和卡顿。使用StrictMode工具帮助检测主线程上的磁盘读写和网络访问。将 IO 操作、复杂计算移至工作线程使用RxJava、Kotlin 协程或AsyncTask已废弃了解即可进行线程切换。使用 Traceview 和 Systrace当遇到性能瓶颈时使用这些性能分析工具来定位耗时方法。特别是 Systrace可以帮你从系统层面分析每一帧的渲染情况找到导致卡顿的元凶。一个常见的性能陷阱在ListView或RecyclerView的适配器getView或onBindViewHolder中执行耗时操作或频繁创建对象。这会在快速滚动时引发严重卡顿。务必确保绑定逻辑高效并充分利用视图复用机制和缓存。8. 高级主题与最佳实践掌握了基础和性能之后我们再看一些能提升开发效率和代码质量的高级主题。8.1 使用 Navigation Component 管理 Activity 与 Fragment对于复杂的页面导航手动管理Intent和FragmentTransaction会变得非常混乱。Jetpack Navigation 组件提供了一个可视化工具和一套 API用于管理应用内导航。虽然它最初是为 Fragment 设计但同样支持 Activity。你可以将每个 Activity 看作一个导航图Navigation Graph的入口或目的地。使用 Navigation 可以在 XML 中可视化地定义所有页面目的地和它们之间的跳转关系动作。统一处理深层链接Deep Link。自动处理Up返回和Back系统返回按钮的行为。安全地传递参数通过 Safe Args Gradle 插件生成类型安全的代码。简化过渡动画的配置。对于全新的项目强烈建议使用 Navigation Component 来构建你的导航结构它能极大地减少模板代码和潜在的错误。8.2 处理多窗口模式与画中画从 Android 7.0 (Nougat) 开始系统支持多窗口模式分屏、自由窗口。从 Android 8.0 (Oreo) 开始支持画中画PIP模式。你的 Activity 需要适应这些多任务场景。多窗口生命周期当应用进入多窗口模式或者窗口尺寸发生变化时默认会触发配置变更screenSize和smallestScreenSize改变导致 Activity 重建。你可以通过android:configChanges声明来处理并在onConfigurationChanged中调整 UI 布局。画中画模式视频播放类应用需要支持 PIP。你需要将 Activity 声明为支持 PIP (android:supportsPictureInPicture)并在适当时机如用户点击主页键调用enterPictureInPictureMode()。在 PIP 模式下Activity 会进入onPause状态但应继续播放视频。你需要重写onPictureInPictureModeChanged来调整 UI如隐藏控件只保留视频画面。8.3 测试 Activity可测试性是高质量代码的标志。Activity 测试主要包括单元测试使用 Robolectric 或 Mockito 等框架在 JVM 上测试 Activity 的业务逻辑、ViewModel 交互等无需启动模拟器或真机速度极快。界面测试UI 测试使用 Espresso 框架模拟用户操作点击、输入、滑动并断言 UI 状态是否符合预期。这类测试运行在模拟器或真机上更接近真实用户场景。使用 ActivityScenarioRule这是 AndroidX Test 库的一部分它提供了启动和关闭 Activity 的规则让你能在一个可控的状态下进行测试非常方便。编写测试时要遵循单一职责原则将业务逻辑从 Activity 中抽离到 ViewModel 或 UseCase 中这样 Activity 本身只负责生命周期和 UI 绑定会更容易测试。理解并熟练运用 Activity 的方方面面是成为一名合格的 Android 开发者的必经之路。它看似简单但内涵丰富从基础的 UI 承载到复杂的生命周期管理、任务栈导航、性能优化每一个细节都影响着最终应用的质量。我的建议是不要满足于“能用”多问几个“为什么”多动手实践不同的场景遇到问题多查阅官方文档和源码你的功力自然会在这个过程中稳步提升。