1. 概述:为什么需要这三者?
在 Android 中,一个用户看到的界面通常由 Activity 承载,内部由 Fragment 拆分成多个可复用的模块,而界面需要的数据则由 ViewModel 提供。这三者各司其职:
-
Activity:应用的一个独立界面(窗口),是用户交互的入口。
-
Fragment:Activity 内部的一个 UI 片段,可以独立管理自己的布局和生命周期,方便在大屏或横屏时组装界面。
-
ViewModel :专门用来存储和管理 UI 相关的数据,它的生命周期比 Activity/Fragment 更长,可以在屏幕旋转等配置变更时保留数据,避免重新加载。
简单地说,Activity 是"大管家",Fragment 是"拼图块",ViewModel 是"数据管家"。
2. Activity 详解
2.1 Activity 是什么?
Activity 是 Android 四大组件之一,负责向用户提供一个可以交互的界面。一个应用通常包含多个 Activity,它们之间通过 Intent 跳转,构成完整的用户流程。
2.2 Activity 的主要作用
-
显示布局(setContentView)
-
响应用户交互(点击、滑动等)
-
启动其他 Activity 或服务
-
处理系统回调(如权限请求结果)
2.3 Activity 的生命周期
理解生命周期是避免崩溃和内存泄漏的基础。关键回调方法如下(按调用顺序):
text
onCreate() → onStart() → onResume() → (Activity 运行中)
→ onPause() → onStop() → onDestroy()
→ 当 Activity 回到前台:onRestart() → onStart() → onResume()
-
onCreate():Activity 首次创建时调用。这里通常做初始化,如 setContentView、初始化 ViewModel、恢复保存的数据。
-
onStart() :Activity 变为可见时调用,但还没到达前台。
-
onResume():Activity 准备好与用户交互,位于栈顶。
-
onPause():失去焦点,但仍部分可见(如弹出透明对话框),应该保存轻量数据、停止动画等。
-
onStop():完全不可见,应释放较重资源、取消注册广播等。
-
onDestroy():Activity 被销毁前调用,清理所有资源。
-
onRestart():从 onStop 回到 onStart 时调用。
注意 :当发生配置变更(如旋转屏幕)时,系统会销毁并重建 Activity,依次调用 onPause() → onStop() → onDestroy(),然后重新创建新的 Activity 实例,调用 onCreate() 等。这就是 ViewModel 存在的意义------避免数据在重建时丢失。
2.4 如何使用 Activity
一个最简单的 Activity:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化ViewModel,设置点击监听等
}
}
启动另一个 Activity:
kotlin
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
在 AndroidManifest.xml 中注册:
xml
<activity
android:name=".MainActivity"
android:exported="true"
android:label="主页">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
3. Fragment 详解
3.1 Fragment 是什么?
Fragment 是嵌套在 Activity 中的 UI 模块,可以拥有自己的布局和生命周期。它必须依附于一个 Activity 才能存在,一个 Activity 可以包含多个 Fragment。
3.2 Fragment 的主要作用
-
适应不同屏幕尺寸:在手机上可能是单页面,在平板上左右分屏,左边列表右边详情,可以用两个 Fragment 实现。
-
代码复用:同样的 Fragment 可用于不同的 Activity。
-
模块化:将复杂的 UI 拆分成小块,便于维护。
3.3 Fragment 的生命周期
Fragment 的生命周期与宿主 Activity 的生命周期有紧密的联系。在 Activity 的 onCreate() 中,Fragment 被附加时,会依次调用:
text
onAttach() → onCreate() → onCreateView() → onViewCreated()
→ onStart() → onResume() → (Fragment 活跃)
→ onPause() → onStop() → onDestroyView() → onDestroy() → onDetach()
-
onAttach():Fragment 关联到 Activity 时调用。
-
onCreate():初始化 Fragment。
-
onCreateView():创建并返回 Fragment 的布局视图。这里我们 inflate 布局文件。
-
onViewCreated():在 onCreateView() 之后立即调用,可安全地访问布局中的 view,进行初始化操作。
-
onStart():Fragment 可见。
-
onResume():Fragment 可交互。
-
onDestroyView():当 Fragment 视图被移除(如切换到另一个 Fragment)时调用,可以清理视图相关资源。
-
onDestroy() / onDetach():Fragment 被销毁或与 Activity 解除关联。
重点 :Fragment 的视图(View)生命周期与 Fragment 本身的生命周期是解耦的;onDestroyView() 之后 Fragment 对象可能仍然存活,但 View 已销毁。
3.4 如何使用 Fragment
现代开发推荐使用 Jetpack 的 FragmentContainerView 和 FragmentManager。
1. 创建 Fragment 的布局 fragment_example.xml:
xml
<LinearLayout ...>
<TextView android:id="@+id/tv_text" ... />
<Button android:id="@+id/btn_click" ... />
</LinearLayout>
2. 创建 Fragment 类:
kotlin
class ExampleFragment : Fragment(R.layout.fragment_example) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 可以安全地访问视图了
view.findViewById<Button>(R.id.btn_click).setOnClickListener {
// 处理点击
}
}
}
这里使用 Fragment(R.layout.fragment_example) 构造器传入了布局,系统会自动 inflate。也可以重写 onCreateView() 手动 inflate。
3. 在 Activity 中添加 Fragment :
在 Activity 的布局 XML 中使用 FragmentContainerView:
xml
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="com.example.app.ExampleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
或者在代码中动态添加:
kotlin
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment())
.commit()
4. ViewModel 详解
4.1 ViewModel 是什么?
ViewModel 是 Android Jetpack 架构组件之一,专门用于以生命周期感知的方式存储和管理 UI 相关数据。它能够在配置变更(如屏幕旋转)时保留数据,使得 UI 重建后无需重新加载。
4.2 ViewModel 的主要作用
-
数据持久性 :当 Activity 因配置变更而销毁重建时,ViewModel 对象会被保留,持有的数据不丢失。
-
生命周期感知:ViewModel 自动知道什么时候该清理资源,当关联的 Activity 彻底 finish 时会被清除。
-
分离 UI 和数据:把处理数据的逻辑从 Activity/Fragment 中抽离出来,便于测试和维护。
-
共享数据 :在同一 Activity 的多个 Fragment 间共享数据非常方便(使用
activityViewModels())。
4.3 ViewModel 的生命周期
ViewModel 的生命周期比它的宿主 Activity 或 Fragment 更长:
-
当 Activity 首次创建时,ViewModel 被创建。
-
当屏幕旋转导致 Activity 重建时,同一个 ViewModel 实例会幸存下来,并传递给新创建的 Activity 实例。
-
只有在 Activity 真正被销毁(
finish()或被系统杀死且不重新创建)时,ViewModel 才会调用onCleared()。
这使得在 ViewModel 中持有 UI 状态数据不会因旋转屏幕而丢失,也避免了内存泄漏(不会因 Activity 重建而持有旧 Activity 引用)。
4.4 如何使用 ViewModel
我们通常将 ViewModel 与 LiveData 或 StateFlow 结合使用,暴露可观察的数据给 UI。
1. 定义 ViewModel:
kotlin
class MyViewModel : ViewModel() {
// 使用 MutableLiveData 或 MutableStateFlow
private val _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
}
或者使用 StateFlow:
kotlin
class MyViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count
fun increment() {
_count.update { it + 1 }
}
}
2. 在 Activity/Fragment 中使用 ViewModel :
由于 ViewModel 需要在组件的第一时间初始化,可使用 by viewModels() Kotlin 属性委托(需要添加 fragment-ktx 或 activity-ktx 依赖)。
kotlin
class MainActivity : AppCompatActivity() {
// 通过 viewModels() 委托创建 ViewModel
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 观察数据并更新 UI
viewModel.count.observe(this) { value ->
textViewCount.text = value.toString()
}
buttonIncrement.setOnClickListener {
viewModel.increment()
}
}
}
如果是 Fragment,用法类似:
kotlin
class MyFragment : Fragment(R.layout.fragment_my) {
// 注意:如果在多个 Fragment 之间共享同一个 ViewModel,使用 activityViewModels()
private val viewModel: MyViewModel by viewModels()
...
}
3. 在多个 Fragment 间共享数据 :
使用 activityViewModels(),这两个 Fragment 将获得同一个 ViewModel 实例,共享数据。
kotlin
// 在 FragmentA 和 FragmentB 中都可以这样写
private val sharedViewModel: SharedViewModel by activityViewModels()
4.5 ViewModel 的进阶:配合 SavedStateHandle
当进程被系统杀死并恢复时,ViewModel 也会被销毁,此时可以使用 SavedStateHandle 保存少量数据(类似 onSaveInstanceState)。
kotlin
class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val query = savedStateHandle.getLiveData("query", "")
fun setQuery(newQuery: String) {
savedStateHandle["query"] = newQuery
}
}
这样即使进程意外被杀,数据也能恢复。
5. 三者的协作关系
一个典型的 UI 交互流程如下:
-
Activity 创建 ,通过
by viewModels()获取ViewModel。 -
Fragment 被添加到 Activity ,同样获取
ViewModel(可能是同一个实例,用于数据共享)。 -
Fragment 中的按钮点击 ,调用
ViewModel的方法(如viewModel.increment())。 -
ViewModel 更新内部的数据容器(LiveData/StateFlow)。
-
Activity/Fragment 在
observe()或collect()中收到新数据,自动更新 UI。 -
屏幕旋转 :Activity 和 Fragment 会销毁重建,但
ViewModel存活,数据不丢失,UI 重建后立即显示最新值。 -
用户离开 Activity (按返回键):Activity 真正销毁,ViewModel 调用
onCleared(),清理资源。
6. 完整示例:计数器应用
让我们结合三者实现一个简单计数器,包含一个 Activity 和一个 Fragment。
依赖配置(build.gradle 模块级):
kotlin
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.activity:activity-ktx:1.8.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
1. 定义 ViewModel
kotlin
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
}
2. 创建 Fragment 布局 (fragment_counter.xml)
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/tv_count"
android:textSize="30sp"
android:text="0" />
<Button
android:id="@+id/btn_increment"
android:text="增加" />
</LinearLayout>
3. Fragment 类
kotlin
class CounterFragment : Fragment(R.layout.fragment_counter) {
// 共享 Activity 级别的 ViewModel,确保与 Activity 同步
private val viewModel: CounterViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 观察 LiveData
viewModel.count.observe(viewLifecycleOwner) { count ->
view.findViewById<TextView>(R.id.tv_count).text = count.toString()
}
view.findViewById<Button>(R.id.btn_increment).setOnClickListener {
viewModel.increment()
}
}
}
注意:在 Fragment 中观察 LiveData 时,应该使用 viewLifecycleOwner 而不是 this,以确保观察与 Fragment 视图生命周期绑定,防止内存泄漏。
4. Activity 布局 (activity_main.xml)
xml
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="com.example.app.CounterFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
5. MainActivity
kotlin
class MainActivity : AppCompatActivity() {
// 由于我们只在 Fragment 中使用 ViewModel,Activity 本身甚至不需要获取 ViewModel
// 如果 Activity 自己也需操作数据,同样可获取
private val viewModel: CounterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 可以在 Activity 中观察或调用 ViewModel,UI 会同步更新
}
}
现在,无论你旋转屏幕多少次,计数器的数值都不会丢失,因为 ViewModel 在幕后默默守护着数据。
7. 最佳实践与常见注意事项
-
ViewModel 绝不应该持有 View、Context、Activity 等的引用 :否则会导致内存泄漏或崩溃。如果需要 Context,可以用
AndroidViewModel获取 Application Context。 -
在 Fragment 中观察 LiveData/Flow 时,使用
viewLifecycleOwner:否则在 Fragment 视图销毁而 Fragment 对象存活时,UI 更新会异常。 -
Activity 和 Fragment 只负责 UI 逻辑:数据操作、网络请求等全部交给 ViewModel,ViewModel 通过 LiveData/Flow 暴露结果。
-
不要在 ViewModel 中执行长期存活的协程而没有管理 :可以使用
viewModelScope自动取消协程。 -
配置变更(旋转屏幕)不是丢失数据的借口 :正是 ViewModel 存在的原因;对于进程被杀死的情况,配合
SavedStateHandle保存少量数据。 -
使用
by viewModels()时,确保添加了fragment-ktx或activity-ktx依赖。
8. 总结
-
Activity:提供窗口,管理用户交互,作为 Fragment 的容器。
-
Fragment:模块化 UI,有自己的生命周期,可以嵌套、复用,适应多屏幕。
-
ViewModel:存储 UI 数据,跨配置变更保留,配合可观察数据持有者(LiveData/StateFlow)实现响应式 UI。
三者的关系是:Activity(容器) → Fragment(UI模块) → ViewModel(数据提供者)。理解它们各自的生命周期和责任边界,是写出稳定、干净 Android 应用的基石。
延伸阅读: