Jetpack之ViewModel和LiveData的用法

文章目录

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的生命周期
Jetpack之ViewModel和LiveData的用法
我们写一个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的生命周期并没有发生改变

Jetpack之ViewModel和LiveData的用法

ViewModel与onSaveInstanceState()的区别

onSaveInstanceState()方法也可以解决屏幕旋转带来的数据丢失问题,但是onSaveInstanceState()方法只能保存少量的,支持序列化的数据,而ViewModel没有这个限制,它支持页面中所有数据
当页面被彻底销毁时,ViewModel中的数据就不存在了,而onSaveInstanceState()方法没有这个限制。

LiveData介绍

LiveData是一个可被观察的数据类容器,可以将数据包装起来,成为一个被观察者,当数据改变的时候能够被观察者捕获到。
LiveData是一个抽象类,我们通常会使用它的子类MutableLiveData
LiveData能够感知页面的生命周期,检测当前页面是否是被激活状态,只有被激活状态才能收到通知,否则就会销毁,绑定activity过程如下
Jetpack之ViewModel和LiveData的用法

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 ")
        })
    }

示例代码-view_model分支

上一篇:Jetpack-MVVM-高频提问和解答,快来收藏!


下一篇:我们究竟还要学习哪些Android知识?实战篇