ViewModel

本文详细介绍了Jetpack提供的ViewModel组件,探讨其在Android应用开发中的作用与使用方法,包括如何避免内存泄漏及结合LiveData与DataBinding实现数据驱动UI的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ViewModel 简介

我们这里说的ViewModel 是说的是jetpack提供的一个组件库,他跟mvvm里说的VM层是两个不同的概率,不要混淆。
只不过VM层会 只用ViewModel这个组件,ViewModel的作用就是为了数据的稳定性。

ViewModel的基本使用

ViewModel的作用就是在activity的生命周期 在还没走ondestory之前ViewModel的数据都不会丢失,也就是不会清除
但是走了onDestory 也不一定会被清除,下面再原理的时候会解析
当清除的这个时候 ViewModel 会调用 onCleared 的方法,最终把 Viewmodel里面的数据清空

当然,当你需要在一定的逻辑场景的时候想要清除掉ViewMode里面的所有属性数据, 可人为调用 这个onCleared 方法

使用如下

自定义一个类继承 ViewModel 里面存在着数据

class ViewModelData : ViewModel() {

    var  count : Int = 0

    //在一些场景中, 可以重写这个方法,在这个方法让 当前正在加载的业务,取消加载  做到,页面销毁, ViewModel 的数据清空掉 ,数据加载也自动取消的效果
    override fun onCleared() {
        super.onCleared()
    }
}

使用 ViewModel如下

class MainActivity : AppCompatActivity() {

    private lateinit var viewModelData: ViewModelData

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main2)

        //viewModel 实例化需要用此写法 而不是new
        viewModelData = ViewModelProvider(this).get(ViewModelData::class.java)

  
    }

}

上面的是老版本的获取方式,如果你添加了如下的ktx 依赖

    implementation "androidx.activity:activity-ktx:1.5.1"
    implementation "androidx.fragment:fragment-ktx:1.5.2"

那么可以用以下的获取方式;

  val viewmodel : MainViewModule  by viewModels()//这个是注入获取的方式  但是需要添加依赖

Activity与Fragment共享一个ViewModel

Fragment 因为 也继承了 ViewModelStoreOwner,所以他可以自己创建一个自己的 ViewModel,创建方式跟activity 一致

如果你想在 Fragment 中使用 Activity 的 ViewModel,通常意味着你希望共享同一个 ViewModel 实例,而 ViewModel 内存有着数据变量,所以 共享同一个 ViewModel 实例 以便在 Fragment 和 Activity 之间共享数据

class MyFragment : Fragment() {  
    private val viewModel: MyViewModel by viewModels {  
        requireActivity().createViewModelProvider(MyViewModel::class.java)  
    }  
  
    override fun onCreateView(  
        inflater: LayoutInflater, container: ViewGroup?,  
        savedInstanceState: Bundle?  
    ): View? {  
        // Inflate the layout for this fragment  
        return inflater.inflate(R.layout.fragment_my, container, false)  
    }  
  
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
        super.onViewCreated(view, savedInstanceState)  
  
    }  
}

ViewModel 的注意事项

为什么 ViewModel 容易引起内容泄漏?

ViewModel 的生命周期比 Activity 或 Fragment 的生命周期要长,当旋转屏幕的时候, Activity 或 Fragment 会被销毁并且重新创建,但是 ViewModel 还是不变,

也就是说当 Activity 或 Fragment 要被销毁的时候,因为 ViewModel 还在持有 Activity 或 Fragment ,导致 Activity 或 Fragment没有办法被gc 回收,所以导致内存泄漏

容易引起泄漏的操作:

错误的依赖注入:如果你将 ViewModel 与依赖注入库(如 Dagger、Hilt)一起使用,并错误地将 Activity 或 Fragment 的上下文(如 Context 或 LifecycleOwner)注入到 ViewModel 中,那么当 Activity 或 Fragment 被销毁时,这些上下文仍然会被 ViewModel 持有,导致内存泄漏。

持有非静态内部类实例:我们说非静态内部类默认持有外部类的引用,那么如果ViewModel 持有 Activity 或 Fragment 的非静态内部类实例,而因为 这些 非静态内部类实例
有Activity 或 Fragment 引用,那么这也可能导致内存泄漏。

监听器和回调:当 ViewModel 注册为某些外部资源的监听器或回调时(如数据库、网络请求等),如果这些资源在 ViewModel 仍然存在时保持对 ViewModel 的引用,并且 ViewModel 又持有对 Activity 或 Fragment 的引用,那么这也可能导致内存泄漏。

其实总结一句话就是, ViewModel里面的对象引用不要直接或者间接持有 Activity 或 Fragment 的引用

正因为如此,ViewModel绝对不要持有下列引用
1.view 2.Lifecycle 3.其他任何可能持有Activity Context的类的引用

如果要用Context,也是用Application 这个context,因为他的生命周期是整个应用进程,写法就是继承 AndroidViewModel

class MainViewModule ( application: Application):AndroidViewModel(application){

    private val  mApplication = application

}

注意用非activity的context startActivity 内设置一个Flag NewTask 不然会崩溃

ViewModel 原理:

看下ViewModel的创建方式

var viewModel = ViewModelProvider(this,ViewModerProvider.NewInstanceFactory()).get(ViewModelData::class.java)

第一个参数就是叫做 ViewModelStoreOwner,而这里传入的this,也就是把activity给传进去,
正是因为activity 也是 ViewModerStoreOwner

在这里插入图片描述
而每个ViewModelStoreOwner又有一个 ViewModelStore

在这里插入图片描述
ViewModelStore 里面只是个HasMap ,根据key,存放着ViewModel

在这里插入图片描述
那么也就是说 activity 就是 ViewModerStoreOwner,里面有ViewModelStore 实例对象 ,ViewModelStore 有个HashMap里面根据key存放着各种ViewModel

而activity 又是LifecycleOwner,通过Lifecycle,设置一个观察者,LifecycleEventObserver
当一定条件的时候,就会获取 activity 的 ViewModelStore 实例对象 ,调用 HashMap中的所有ViewModel执行onclear 清除数据操作

这条件就是
收到的生命周期状态是 onDestroy也就是的Event是Lifecycle.Event.ON_DESTROY的时候做判断
并且 isChangingConfigurations是false 才会调用 ViewModel执行onclear

前面说了ViewModel在ondestory之前数据不会清空,但是就算走了onDestoy的时候也不一定会情况就是这里的判断

在这里插入图片描述
这里还多了一层判断,就是 isChangingConfigurations ,也就是看这个 mChangingConfigurations boolean 的情况
意思就是如果配置改变了 mChangingConfigurations = true, 那么就算调用了 ondestory 也不会走到下面clear方法去
清空ViewModel的数据,而 isChangingConfigurations 这个boolean 它默认就是 false,也就是默认情况下, 配置是没有改变的,也就是默认情况下 ondestory 走,数据是会清空的,但是,比如 activity横竖屏切换的时候,它是会走onDestory方法的
但是这个时候 mChangingConfigurations = true 会是true,也就是配置改变了,所以这个时候,就算走了 ondestory 数据是不会被清空的

在这里插入图片描述

在这里插入图片描述
/** true if the activity is being destroyed in order to recreate it with a new configuration */
这段注释的意思是
如果活动被销毁是为了用新的配置重新创建它,那么 mChangingConfigurations 是true,不然是false
好比横竖屏切换的时候就会是true

而当走清空数据方法后,
会调用getViewModelStore这个方法获取ViewModelStore这个实例对象,然后调用clear方法,
把 ViewModelStore 的HashMap的ViewModel调用它的 clear方法去清除数据

在这里插入图片描述

LiveData+Databinding+ViewModel

一般在项目中,要做到数据驱动ui,首先,为了数据不在 ondestory 之前被清除, 数据会放在 ViewModel 类里 ,

接着大多是采用的 利用databinding 的作用,在布局里把界面跟ViewModel 的数据绑定在一起,
(当然也可以 采用 Livedata 去手动设置监听数据界面改变,看情况)

ViewModel 的数据 是可以使用 ObserableFied 也可以使用 MutableLivedata ,但是 数据 用Livedata 的 MutableLivedata ,会更好,

LiveData 作为一种观察者设计模式,具有生命周期感知的特点,可以在 Activity 或 Fragment 生命周期结束时自动解除注册。同时,MutableLiveData 还提供了 setValue() 和 postValue() 方法,可以在主线程或子线程中更新 LiveData 的值,同时保证数据的线程安全。
而 ObserableFied 没有这个功能,不能根据生命周期

不过,如果说你的数据,他是频繁的改变的,比如说是进度条,0到100不断改变,
那么最好还是用 ObserableFied ,防止抖动,节约内存

当 ViewModel 的 LiveData 的数据变化时,会根据Lifecycle 调用的生命周期 在可见的时候 ,LiveData 会通知所有观察者 包括 DataBinding,正是因为观察者里面有 DataBinding 所以 DataBinding 收到数据改变 才会驱动界面去更新数据

所以关键一个点,

Viewmodel 里面放着ObserableFied 数据, ObserableFied 数据改变 , databinding 会 驱动界面也跟着改变

但是 Viewmodel 里面放着MutableLiveData 数据, MutableLiveData 数据改变 , databinding 不会 驱动界面也跟着改变

还要 在 activity 加上一句代码

activityMainBinding.setLifecycleOwner(this)

setLifecycleOwner(this)源码如下

在这里插入图片描述

接着 setLifecycleOwner 会来到这里
在这里插入图片描述
也就是设置 当前的LifecycleOwner 是给到Livedata用的,

因为Livedata 使用它 都需要传个LifecycleOwner过去
目前没有哪句代码有把LifecycleOwner给过去, 单单就只是设置了个vm,vm 里面有个livedata, 界面里面数据绑定了
vm的livedata, activity里面也只是 创建了vm实例 ,设置vm 实例到布局里面

只有 activity 设置了这句话

activityMainBinding.setLifecycleOwner(this)

把 LifecycleOwner 给到livedata, 然后 liveData.observe(LifecycleOwner,this)
当 liveData 的数据改变的时候,会再根据生命周期最后调用 观察者们的 onchange 方法,其中这里的观察者就是 databinding
最后在onChange 方法里面 databinding 去驱动ui

在这里插入图片描述

所以最终就是 LiveData+Databinding+ViewModel

ViewModel

class MainViewModel(application: Application) :AndroidViewModel(application) {

    val liveData:MutableLiveData<String> by lazy{
        MutableLiveData<String>()
    }

    override fun onCleared() {
        super.onCleared()
    }
}

MainActivity

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private lateinit var mainViewModel: MainViewModel

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var activityMainBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)

        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
            
        activityMainBinding.mainViewModel =mainViewModel
        activityMainBinding.setLifecycleOwner(this)
    }

布局

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <variable
            name="mainViewModel"
            type="com.android.myapplication.MainViewModel" />

    </data>


    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:layout_gravity="center"
            android:text="@{mainViewModel.liveData}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:id="@+id/text"/>

    </FrameLayout>
</layout>
### Android ViewModel 使用教程和最佳实践 #### 一、ViewModel简介 `ViewModel` 是一种设计模式,旨在存储和管理与 UI 相关的数据。它允许数据在配置更改(如屏幕旋转)期间保持不变,从而提供更好的用户体验[^1]。 #### 二、基本使用方法 为了创建并使用 `ViewModel` ,开发者通常会继承自 `androidx.lifecycle.ViewModel` 类来定义自己的视图模型类: ```kotlin class MyViewModel : ViewModel() { private val _text = MutableLiveData<String>().apply { value = "This is a sample text" } val text: LiveData<String> = _text } ``` 上述代码展示了如何声明一个简单的 `MyViewModel` 实例,在其中封装了一个可观察的字符串变量 `_text` 。通过将其转换为不可变属性 `text` 对外暴露给Activity 或 Fragment 进行绑定显示[^2]。 #### 三、结合LiveData实现响应式编程 当希望监听来自服务器或其他异步源的变化时,可以利用 `LiveData<T>` 来构建具有生命周期感知能力的数据持有者对象。每当关联的 LifecycleOwner (比如 Activity/Fragment) 处于激活状态(`STARTED`, `RESUMED`)下发生改变,则自动触发更新UI操作而无需手动处理订阅取消等问题: ```kotlin // 在ViewModel内部 val user: MutableLiveData<User> fun fetchUser(userId: String){ // 模拟网络请求... viewModelScope.launch{ val result=repository.getUserByIdAsync(userId).await() user.postValue(result) } } // 在Activity或Fragment里 viewModel.user.observe(this, Observer { newUser -> binding.textViewName.text=newUser.name }) ``` 这里还涉及到协程的作用域管理问题——借助 `viewModelScope` 可以确保所有挂起函数都在合适的上下文中执行,并且会在宿主销毁前完成清理工作防止内存泄漏风险[^3]。 #### 四、遵循单一职责原则(SRP),引入Repository层隔离复杂度 为了避免让 `ViewModel` 承担过多责任,应该把具体的数据获取逻辑交给专门负责该任务的对象去完成—即 Repository 层。这样做不仅有助于提高程序结构清晰度,也方便单元测试阶段模拟不同场景下的输入输出行为而不必依赖真实环境: ```kotlin interface UserRepository { suspend fun getUserById(id:String):Result<User> } object FakeUserRepositoryImpl:UserRepository{ override suspend fun getUserById(id: String)= Result.success(User()) } class UserViewModel(private val userRepository:UserRepository):ViewModel(){ init{...}//注入方式省略 ... } ``` 以上就是关于Android平台下运用MVVM架构风格的一些核心概念和技术要点介绍[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值