Lifecycle库可以有效避免内存泄漏和解决常见的Android生命周期难题。
1.引言
ViewModel属于lifecycle(生命周期感知型组件)中的一员,通常与LiveData、DataBinding一起使用,它们是MVVM架构的重要成员。ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。
2.ViewModel是什么
ViewModel是一个用来管理UI数据的组件。
管理UI数据?UI数据不是写在Activity和Fragment中吗?是的。
啊不是。。。自ViewModel发布,Google官方建议将应用所有的UI数据保存在ViewModel中,而不是Activity中。那么使用ViewModel来管理UI数据有哪些好处呢?
我们知道,当应用退回到桌面,或者旋转屏幕时,Activity很可能会被销毁重建,页面的状态将会丢失。为了数据的保存和恢复,常规的做法是通过onSavaInstanceState()和onRestoreInstanceState()实现,但这种方式仅适合保存少量可以被序列化、反序列化的数据,不能保存比较大的数据或不方便序列化,反序列化的数据。
那么,有没有一个类,在Activity发生这些变更时,依旧保留这个类的实例,当Activity重建后拿到的还是这个实例,那么只要把UI数据统一放到这个类中,就能保持页面的状态不会丢失了。
有的,这个类就是ViewModel。下图说明了ViewModel在Activity经历屏幕旋转而后结束的过程中所处的各种生命周期状态。
这些基本状态同样适用于Fragment的生命周期。从图中可以看到,ViewModel是在Activity真正结束时才被清理掉。
3.ViewModel的使用
添加依赖
dependencies {
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
}
创建ViewModel很简单,仅需要继承ViewModel类,像这样
class MainViewModel : ViewModel() {
var count = 0
}
通过ViewModelProvider框架拿到ViewModel实例,而不是new一个ViewModel实例,这种机制可以让你的ViewModel独立于配置更改:
class MainActivity : AppCompatActivity() {
private lateinit var mViewModel : MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
tv.text = mViewModel.count.toString()
btn.setOnClickListener {
mViewModel.count++
tv.text = mViewModel.count.toString()
}
}
}
ViewModelProvider的构造函数需要传入ViewModelStoreOwner的实例,不用担心,AppCompatActivity和Fragment已经间接或直接的实现了它。现在,count的值不会因为屏幕旋转而清零了:
现在我们知道,ViewModel可以实例贯穿着Activity的整个生命周期,那么是否可以借助ViewModel来实现Fragment之间的数据共享与通信呢?
答案是肯定的,这样做的好处是,Activity不需要执行任何操作,也不需要对此通信有任何了解。在ViewModel之前,常规的做法都是Actiivty和Fragment之间定义相应的接口或者使用EventBus等组件。说实话,这两种方式都不太友好。
还是刚才的例子,我们来改造一下:
class MainViewModel : ViewModel() {
var count = MutableLiveData<Int>(0)
}
如果对LiveData不太熟悉,这里就先把它当成Observable吧。
然后我们在MainActivity中定义上下两个Fragment:
class TopFragment : Fragment() {
private lateinit var mViewModel : MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fr_top,container,false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
mViewModel.count.observe(viewLifecycleOwner){ count->
tv.text = count.toString()
}
}
}
class BottomFragment : Fragment() {
private lateinit var mViewModel : MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fr_bottom,container,false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
btn.setOnClickListener {
mViewModel.count.value = mViewModel.count.value?.plus(1)
}
}
}
需要注意的是,ViewModelProvider传入的是activity而不是当前的fragment,道理很简单,我们要实现Fragemnt之间的数据共享或者通讯,必须要拿到相同的ViewModel实例,如果传入this,那就不是同一个ViewModel对象了。
MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<fragment
android:id="@+id/frTop"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.example.jetpack.TopFragment" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#EEEEEE"/>
<fragment
android:id="@+id/frBottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.example.jetpack.BottomFragment" />
</LinearLayout>
在这个例子中,BottomFragment用于更新count的值并发布,TopFragment用于监听count的值并显示,实现效果:
就这样,TopFragment和BottomFragment完成了他们之间的通信,MainActivity却浑然不知。。。到这里,我们可以总结出ViewModel的两个作用:
- 保存UI数据
- Fragment通信
4.注意事项
在使用ViewModel时需要注意,由于ViewModel对象存在的时间比视图存在的时间长,所以不能持有对视图的引用(Context),否则可能会导致内存泄漏。如果你的ViewModel需要Application的上下文,可以继承AndroidViewModel。另外,如果你需要在ViewModel对象销毁前做回收工作,可以重写ViewModel的onCleared()方法。
5.总结
ViewModel的存在意义并不仅仅是为了管理UI数据,它能够有效的划分职责,正如它的名字一样,ViewModel提供了一个View(视图)和Model(数据模型)之间的桥梁,使得视图和数据能够分离开,也能够保持通信。
更多安卓知识体系,请关注公众号: