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 中使用 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>