ViewModel 是 Android 架构组件 (Android Architecture Components) 中的一个核心组件,它在应用程序的 UI 层和数据层之间扮演着至关重要的角色。它的主要目标是帮助你以生命周期感知的方式管理 UI 相关数据,从而解决旋转屏幕等配置变更导致的数据丢失问题,并更好地分离关注点。
为什么需要 ViewModel?
在没有 ViewModel 之前,开发者经常会遇到以下痛点:
- 配置变更导致数据丢失:当设备旋转、语言切换等配置变更发生时,Activity 或 Fragment 会被销毁并重建。这意味着存储在 Activity/Fragment 中的数据会丢失,除非你手动保存和恢复(例如使用
onSaveInstanceState()和onRestoreInstanceState()),这既繁琐又容易出错。 - 内存泄漏:在 Activity/Fragment 中持有对异步操作(如网络请求、数据库查询)的回调,如果 Activity/Fragment 在操作完成前被销毁,这些回调可能仍然持有对它们的引用,导致内存泄漏。
- 代码耦合与测试困难:Activity/Fragment 往往承载了过多的职责,既处理 UI 逻辑,又直接与数据源交互,导致代码臃肿、难以维护和测试。
ViewModel 的出现就是为了解决这些问题。也是现在流行架构:MVVM/MVI 的必要组件。
ViewModel 的核心特性和优势
-
生命周期感知 (Lifecycle-Aware) 这是 ViewModel 最重要的特性。ViewModel 的生命周期比 Activity 或 Fragment 更长。它会一直存在,直到其关联的 Activity 或 Fragment 被彻底销毁(例如用户退出应用,而不是仅仅旋转屏幕)。这意味着:
- 数据持久化:即使 Activity/Fragment 因配置变更被重建,ViewModel 中的数据也不会丢失,因为它仍然是同一个 ViewModel 实例。
- 避免内存泄漏:你可以在 ViewModel 中启动异步操作,而不用担心它们在 Activity/Fragment 被销毁时会引用已经不存在的 UI 元素,因为 ViewModel 不会直接持有对 View 的引用。
-
UI 数据管理 ViewModel 负责存储和管理 UI 相关数据。它通常与
LiveData或StateFlow(或类似的响应式数据流)配合使用。ViewModel 不直接持有 View 的引用,而是暴露数据,让 UI (Activity/Fragment) 观察这些数据并更新自身。 -
职责分离 (Separation of Concerns) ViewModel 帮助你将 UI 逻辑(Activity/Fragment)与业务逻辑和数据处理(ViewModel)分离。
- Activity/Fragment:专注于显示 UI、处理用户交互事件,并将事件传递给 ViewModel。
- ViewModel:负责从数据源(Repository)获取数据、处理业务逻辑、转换数据以供 UI 使用,并通过
LiveData等将数据暴露给 UI。
-
易于测试 由于 ViewModel 不依赖于 Android Framework 类(除了
Application上下文,如果需要的话),你可以更容易地对 ViewModel 进行单元测试,而无需使用模拟器或设备。
ViewModel 的工作原理
数据流:
- ViewModel 从 Repository 获取数据。Repository 是一个抽象层,负责决定从何处获取数据(网络、数据库等)。
- ViewModel 将数据暴露为
LiveData或StateFlow。 - UI (Activity/Fragment) 观察这些
LiveData或StateFlow,并在数据更新时自动更新 UI。
创建 ViewModel 实例:
- 通常使用
ViewModelProvider来获取 ViewModel 实例。 - Activity/Fragment里推荐的方式是使用 Kotlin 的
by viewModels() - 在Fragment里,如果需要共享Activity的请使用
by activityViewModels
ViewModel 的生命周期:
- 创建:当第一次请求 ViewModel 时创建。
- 存活:与 Activity/Fragment 的生命周期绑定。当 Activity/Fragment 因配置变更被销,例如屏幕旋转)时,ViewModel 不会销毁。
- 销毁:只有当关联的 Activity/Fragment 被彻底销毁(例如用户退出应用,
onCleared()方法被调用)时,ViewModel 才会销毁。
onCleared() 方法是 ViewModel 生命周期中的一个重要回调,你可以在这里执行一些清理工作,例如取消正在进行的异步操作。
ViewModel 与 AndroidViewModel
如果你需要在 ViewModel 中访问 Application 上下文(例如,为了访问资源、SharedPreferences 或数据库),可以使用 AndroidViewModel。它是一个特殊的 ViewModel 子类,构造函数中默认提供了 Application 对象。
区别与特点:
- 访问
Application上下文:这是它的主要优势,允许你在 ViewModel 中进行一些需要应用上下文的操作。 - 谨慎使用:虽然提供了方便,但通常建议 ViewModel 避免直接访问上下文,而是通过 Repository 或其他依赖来获取所需数据。过度使用上下文可能导致测试困难或设计问题。只有当确实需要访问全局应用级别资源时才使用。
- 生命周期安全:它提供的是
Application上下文,而不是 Activity/Fragment 上下文,因此不会导致内存泄漏。
说个特别的:ViewModelEventbus
Eventbus现在已经不推荐使用,而且也不维护了,不论是google的还是green的。那我们想要实现类似的效果应该怎么? 这时候你可以写一个ViewModel,实现post方法和poststick方法,利用viewmodel来实现Eventbus。
- 自定义ViewModelEventbus继承AndroidViewModel吧,毕竟资源利用多
- 记住他的作用域是Android的Application,生命周期与整个安卓绑定。就是在Application里绑定
- 实现post方法:利用shareflow
- 和poststick方法利用stateFlow,粘性事件
后续,我看看可以写一个,专门来说说并且提供代码
彻底理解 ViewModel:作用域与管理机制
ViewModel 的强大之处在于它如何巧妙地结合了 生命周期感知 和 作用域管理,从而解决了 Android 开发中长期存在的数据持久化和内存泄漏问题。
理解 ViewModel 的作用域是理解其工作原理的关键。每个 ViewModel 实例都与一个特定的 LifecycleOwner (通常是 Activity 或 Fragment) 绑定。当这个 LifecycleOwner 首次请求 ViewModel 时,ViewModel 实例就会被创建,并在此 LifecycleOwner 的作用域内存在。
好的,没问题!我们来更彻底地剖析 ViewModel,深入它的 作用域、管理机制 以及其他你可能想知道的细节。
彻底理解 ViewModel:作用域与管理机制
ViewModel 的强大之处在于它如何巧妙地结合了 生命周期感知 和 作用域管理,从而解决了 Android 开发中长期存在的数据持久化和内存泄漏问题。
1. ViewModel 的核心:作用域 (Scope)
理解 ViewModel 的作用域是理解其工作原理的关键。每个 ViewModel 实例都与一个特定的 LifecycleOwner (通常是 Activity 或 Fragment) 绑定。当这个 LifecycleOwner 首次请求 ViewModel 时,ViewModel 实例就会被创建,并在此 LifecycleOwner 的作用域内存在。
ViewModel 的作用域有以下几个关键特征:
-
生命周期更长:ViewModel 的生命周期 超越了配置变更 (如屏幕旋转、键盘可用性变化等) 导致的 LifecycleOwner (Activity/Fragment) 的重建。这意味着,即使你的 Activity 或 Fragment 被销毁并重建,它仍然会接收到 同一个 ViewModel 实例,从而保留了 UI 相关数据。
-
绑定到 LifecycleOwner:ViewModel 并非“永久存在”。它的生命周期始终与它所关联的特定
Activity或Fragment实例的生命周期绑定。 -
Activity 作用域:当你在 Activity 中获取 ViewModel 时,该 ViewModel 的生命周期将与该 Activity 的生命周期绑定。只要 Activity 存活(包括配置变更后的重建),ViewModel 就存活。只有当 Activity 最终被系统销毁(例如用户按返回键退出应用,或系统回收内存)时,ViewModel 才会调用
onCleared()并被销毁。 -
Fragment 作用域:当你在 Fragment 中通过
by viewModels()获取 ViewModel 时,该 ViewModel 的生命周期将与该 Fragment 的生命周期绑定。即使 Fragment 被添加到返回栈中,然后又从返回栈中弹出,该 ViewModel 实例也是同一个。只有当 Fragment 最终与 Activity 解绑并被销毁时,ViewModel 才会调用onCleared()并被销毁。 -
共享 Activity 作用域 (
by activityViewModels()) :当你在 Fragment 中通过by activityViewModels()获取 ViewModel 时,该 ViewModel 实际上是其 宿主 Activity 作用域下的 ViewModel。这意味着,该 ViewModel 的生命周期与宿主 Activity 绑定。这个特性使得在同一个 Activity 中的多个 Fragment 可以共享同一个 ViewModel 实例,实现 Fragment 间的数据通信,而不用关心 Fragment 自身的生命周期变化。
ViewModel 是如何被管理的?
ViewModel 的管理机制比较复杂,这主要依赖于 ViewModelProvider 和底层的 ViewModelStore。
-
ViewModelProvider: 这是你用来获取 ViewModel 实例的入口点(如new ViewModelProvider(this).get(MyViewModel.class)或 Kotlin KTX 的by viewModels())。它负责:- 查找现有实例:
ViewModelProvider会检查其内部的ViewModelStore是否已经有当前 LifecycleOwner 对应类型的 ViewModel 实例。 - 创建新实例:如果不存在,它会使用对应的
ViewModelProvider.Factory来创建一个新的 ViewModel 实例。 - 返回实例:无论是否存在,它都会返回 ViewModel 实例。
- 查找现有实例:
-
ViewModelStore: 这是一个内部的存储机制,实际持有 ViewModel 实例的映射(通常是Map<String, ViewModel>)。- 每个
LifecycleOwner(Activity 或 Fragment) 都会拥有一个ViewModelStore。 - 当
LifecycleOwner因配置变更而被重建时,旧的LifecycleOwner会将它的ViewModelStore实例传递给新的LifecycleOwner。这就是 ViewModel 能够在配置变更后仍然存活的关键! - 只有当
LifecycleOwner被 彻底销毁 (例如Activity.isFinishing()为 true,或 Fragment 与 Activity 分离且不在返回栈中时),该LifecycleStore中的所有 ViewModel 实例才会被清空,并调用它们的onCleared()方法。
- 每个
管理流程总结:
- 第一次请求:当一个 Activity/Fragment 首次请求 ViewModel 时,
ViewModelProvider会发现ViewModelStore中没有该 ViewModel 实例。 - 创建实例:
ViewModelProvider会调用Factory来创建一个新的 ViewModel 实例。 - 存储实例:
ViewModelProvider将这个新创建的 ViewModel 实例存储到当前 Activity/Fragment 关联的ViewModelStore中。 - 配置变更:当 Activity/Fragment 因配置变更被销毁时,它的
ViewModelStore不会被销毁。Android 系统会确保这个ViewModelStore实例在重建后的 Activity/Fragment 实例中仍然可用。 - 重建后请求:当重建后的 Activity/Fragment 再次请求 ViewModel 时,
ViewModelProvider会发现ViewModelStore中已经存在对应的 ViewModel 实例。 - 返回旧实例:
ViewModelProvider直接返回 之前创建的同一个 ViewModel 实例。 - 最终销毁:只有当 Activity 或 Fragment 被完全销毁(而不是仅仅配置变更),与它关联的
ViewModelStore才会一并被清除,并触发其中所有 ViewModel 实例的onCleared()方法。
创建viewModel的实例有哪些方式?
ViewModelProvider
默认构造函数(通过 ViewModelProvider),这是最常见和最简单的方式,适用于 ViewModel 不需要任何自定义参数的情况。
-
由 Android 系统管理:
ViewModelProvider会负责 ViewModel 的生命周期管理。它会检查是否已存在与当前 LifecycleOwner (Activity 或 Fragment) 关联的 ViewModel 实例。如果存在,就返回现有实例;如果不存在,就通过默认构造函数创建一个新实例。 -
局限性:无法直接向 ViewModel 的构造函数传递参数,这使得它无法直接处理依赖注入。
ViewModelProvider.Factory
这是传参使用,当你的 ViewModel 需要接收自定义参数或依赖项(例如 Repository、Application 上下文或其他服务)时,你需要使用 ViewModelProvider.Factory。这是处理 依赖注入 的标准方式。
-
处理依赖注入:这是向 ViewModel 传递依赖项(如 Repository、Service 等)的 标准方式。
-
更灵活:你可以控制 ViewModel 的实例化过程,传入任何你需要的参数。
-
更好的测试性:由于你可以轻松地为 ViewModel 提供模拟依赖,因此更容易对 ViewModel 进行单元测试。
-
更复杂:需要额外编写 Factory 类,增加了代码量,但在有依赖的情况下是值得的。
-
结合依赖注入框架:在大型项目中,通常会结合像 Hilt 或 Dagger 这样的依赖注入框架来自动化 Factory 的创建和依赖的提供,进一步简化代码。
// 1. 定义你的 ViewModel
class MyViewModel(private val userRepository: UserRepository) : ViewModel() {
// ...
}
// 2. 实现一个 Factory
class MyViewModelFactory(private val userRepository: UserRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MyViewModel(userRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
// 3. 在 Activity/Fragment 中使用 Factory
class MyActivity : AppCompatActivity() {
private val userRepository = UserRepository() // 假设你的 Repository 实例
private val myViewModel: MyViewModel by viewModels {
MyViewModelFactory(userRepository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// myViewModel 实例在这里被创建或获取
}
}
// Java 示例
public class MyActivity extends AppCompatActivity {
private MyViewModel myViewModel;
private UserRepository userRepository = new UserRepository(); // 假设你的 Repository 实例
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myViewModel = new ViewModelProvider(this, new MyViewModelFactory(userRepository))
.get(MyViewModel.class);
}
}
by viewModels()与 by activityViewModels()
他们其实是扩展,本质内部都是利用ViewModelProvider和ViewModelProvider.Factory帮你做了,这是快捷方式。
区别与特点:
-
作用域不同:
by viewModels():在 Fragment 中使用时,它会为当前 Fragment 创建或获取一个 ViewModel 实例。这个 ViewModel 的生命周期与当前 Fragment 绑定。by activityViewModels():它会为宿主 Activity 创建或获取一个 ViewModel 实例。这个 ViewModel 的生命周期与宿主 Activity 绑定,而不是 Fragment。
-
数据共享:这是
by activityViewModels()的主要目的。当同一个 Activity 中的多个 Fragment 都通过activityViewModels()请求同一个 ViewModel 时,它们会获得同一个 ViewModel 实例。这样,这些 Fragment 就可以通过这个共享的 ViewModel 来 传递数据或事件,而无需直接引用彼此,降低了耦合度。 -
生命周期:由
activityViewModels()获取的 ViewModel 的生命周期与 Activity 相同。即使 Fragment 被销毁或重建(例如通过返回栈),只要 Activity 仍然存在,这个 ViewModel 实例就依然存活,其中保存的数据也不会丢失。 -
依赖注入:与
by viewModels()类似,如果你需要为通过activityViewModels()获取的 ViewModel 传递依赖,你仍然需要提供一个ViewModelProvider.Factory,或者使用 Hilt 等依赖注入框架。
何时使用 activityViewModels()?
当你需要在同一个 Activity 下的 不同 Fragment 之间进行数据通信或共享状态 时,activityViewModels() 是一个非常简洁高效的解决方案。例如:
- 如果你在Activity里使用就用## by viewModels(),作用域就是当前的activity
- 如果你的fragment不需共享与activity共享数据,就用by viewModels(),作用域就是当前的fragment
- 如果你需要fragment之间或者fragment与activity直接共享,请在fragment里使用## by activityViewModels(),作用域就是宿主activity。