文章目录
ViewModel介绍
ViewModel将页面所需的数据从页面剥离出来,页面只需要处理用户交互和展示数据。是介于View(UI)和Model(数据)之间的桥梁,使得视图和数据既能够分开,又可以保持通信。
ViewModel 独立于配置变化之外,例如activity旋转时页面会重建,生命周期也会结束后重新开始,但是这期间并不会影响ViewModel的生命周期,还是那个ViewModel。
ViewModel的实例化过程是通过ViewModelProvider来完成,ViewModelProvider会判断ViewModel是否存在,弱存在则直接返回,否则新建一个ViewModel。
ViewModel实例化
val timerViewModel = ViewModelProvider(this).get(TimerViewModel::class.java)
ViewModelProvider方法需要一个ViewModelStoreOwner参数,
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
我们用当前页面的上下文this作为ViewModelProvider的参数,这是因为在androidx包下我们的activity最终继承了ComponentActivity,而这个activity已经默认给我们实现了ViewModelStoreOwner接口
ViewModelStoreOwner接口,此接口定义了getViewModelStore方法并返回ViewModelStore对象
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
ViewModelStore对象中维护了ViewModel,可以看出ViewModel实际上是以HashMap<String, ViewModel>的形式缓存在内存中的,所以它是独立于activity的生命周期存在的,所以在使用viewmodel的时候不要传入任何类型的Context或者带有Context的对象引用,这样会导致页面无法被销毁,从而引发内存泄露(activity在销毁时,自身的引用还没有断开无法销毁会引发内存泄露-申请了一块内存,但没有及时释放,而这块内存会一直占用无法再进行分配)
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
如果在ViewModel中必须要使用context,可以使用AndroidViewModel类,它是ViewModel的子类,接收Application作为Context,这也意味着它的生命周期是和application一样的。
ViewModel源码创建
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
使用
依赖
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
viewmodel是一个抽象类,里边维护了一个onCleared()方法,当viewmodel不再被需要,也就是相关联的activity被销毁时,该方法被系统调用。可以执行一些资源释放的操作,但是由于屏幕旋转而导致的activity的重建不会触发此方法,不会影响viewmodel的生命周期
我们写一个viewmodel的实现类TimerViewModel,在里边维护一个定时器,然后在activity中实例化它,来看一下当程序处于前台和后台切换以及横竖屏切换的时候,定时器会不会重置,
class TimerViewModel:ViewModel() {
private var timer:Timer?=null
private var currentSecond:Int = 0
fun startTiming(){
if (timer==null){
currentSecond = 0
timer = Timer()
val timerTask:TimerTask = MyTask()
timer?.schedule(timerTask,1000,1000)
}
}
private inner class MyTask:TimerTask(){
override fun run() {
currentSecond ++
if (onTimeChangeListener!=null){
onTimeChangeListener.onTimeChange(currentSecond)
}
}
}
interface OnTimeChangeListener{
fun onTimeChange(second:Int)
}
private lateinit var onTimeChangeListener:OnTimeChangeListener
fun setOnTimeChangeListener(onTimeChangeListener:OnTimeChangeListener){
this.onTimeChangeListener = onTimeChangeListener
}
/**
* 当viewmodel不再被需要,也就是相关联的activity被销毁时,该方法被系统调用
* 可以执行一些资源释放的操作,但是由于屏幕旋转而导致的activity的重建不会触发此方法,不会影响viewmodel的生命周期
*/
override fun onCleared() {
super.onCleared()
}
}
activity和viewmodel关联
private fun init() {
val timerViewModel = ViewModelProvider(this).get(TimerViewModel::class.java)
timerViewModel.setOnTimeChangeListener(object : TimerViewModel.OnTimeChangeListener {
override fun onTimeChange(second: Int) {
Log.e(TAG, "onTimeChange:$second ")
}
})
timerViewModel.startTiming()
}
结果表明,在activity处于后台以及横竖屏切换时候,viewmodel的生命周期并没有发生改变
ViewModel与onSaveInstanceState()的区别
onSaveInstanceState()方法也可以解决屏幕旋转带来的数据丢失问题,但是onSaveInstanceState()方法只能保存少量的,支持序列化的数据,而ViewModel没有这个限制,它支持页面中所有数据
当页面被彻底销毁时,ViewModel中的数据就不存在了,而onSaveInstanceState()方法没有这个限制。
LiveData介绍
LiveData是一个可被观察的数据类容器,可以将数据包装起来,成为一个被观察者,当数据改变的时候能够被观察者捕获到。
LiveData是一个抽象类,我们通常会使用它的子类MutableLiveData
LiveData能够感知页面的生命周期,检测当前页面是否是被激活状态,只有被激活状态才能收到通知,否则就会销毁,绑定activity过程如下
MutableLiveData用法
private var currentSecond:MutableLiveData<Int> = MutableLiveData()
private fun initComponent() {
val timerViewModel = ViewModelProvider(this).get(TimerViewModel::class.java)
var liveData = timerViewModel.getCurrentSecond()
liveData.observe(this, Observer {
Log.e(TAG, "initComponent::$it ")
})
}