美文网首页android
Android开发之MVVM模式实践(一):ViewModel的

Android开发之MVVM模式实践(一):ViewModel的

作者: 码途有道 | 来源:发表于2020-07-05 14:28 被阅读0次

    前言

    目前Android开发中,常用的几种项目架构模式分别是MVC、MVP和MVVM。当然根据项目的体量和业务的不同,可能还会对这几种模式进行融合,产生其他变种模式,这个我们暂且不谈。我们本篇的主角是最近越来越受欢迎的MVVM。

    随着Google在近两年推出了Jetpack系列的工具库后,MVVM的开发模式越来受开发者欢迎,毕竟是Google的亲儿子。对于开发中的MVVM模式,我们主要是依托DataBindingViewModelLiveData这三者来实现。

    一、对于ViewModel的使用建议

    ViewModel是Jetpack全家桶中的一员,也是构建MVVM模式的重要组成部分。因为ViewModel拥有远比Activity和Fragment还要长的生命周期,所以ViewModel中最好不要持有Activity或者Fragment的引用,否则很容易引起内存泄漏。一般建议在ViewModel只做数据处理,保存Activity/Fragment中的页面数据,在Activity被销毁重建时也能拿到之前的页面数据。此外,Google建议一个Activity/Fragment最好只拥有一个ViewModel,一个ViewModel中可以拥有多个Model实例(即数据逻辑处理的类,比如网络请求数据)。

    eg: 当屏幕旋转时,Activity可能会先被销毁,在重新创建新的实例;而因为ViewModel的生命周期长于Activity,新的Activity中的ViewModel持有的是之前被销毁的Activity的引用(具体原理可看ViewModelProvider源码中对ViewModel的存取处理),这样就会导致内存泄漏。

    二、给ViewModel添加页面的生命周期函数

    在开发中,ViewModel中的方法不可避免的会被在指定的Activity/Fragment的生命周期函数中调用,我们可以在Activity/Fragment的生命周期函数中主动调用ViewModel中的方法,但是我们还有一种更完美的方法,即让ViewModel可以拥有和Activity/Fragment同步的一样的生命周期函数,ViewModel在自己的生命周期函数中主动调用自己的方法即可,与Activity/Fragment更加解耦,也更加方便单元测试。

    让ViewModel拥有和Activity一样的生命周期函数,我们常见的做法如下,在Activity基类中的生命周期中调用ViewModel基类中对应的生命周期函数,达到ViewModel的生命周期函数和Activity的生命周期函数同步。

    class BaseActivity:AppCompatActivity(){
        override fun onResume() {
            super.onResume()
            viewModel.onResume()
        }
    }
    
    class BaseViewModel:ViewModel(){
        fun onResume(){
        }
    }
    

    但是,Google的Jetpack全家桶还给了我们另一种更为简便的实现,直接实现LifecycleObserver接口

    interface ViewModelLifecycle:LifecycleObserver {
    
        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
        fun onAny(owner: LifecycleOwner, event: Lifecycle.Event)
    
        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
        fun onCreate()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun onStart()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun onResume()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun onPause()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onStop()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        fun onDestroy()
    }
    
    
    abstract class BaseViewModel : ViewModel(), ViewModelLifecycle {
        private lateinit var lifcycleOwner: LifecycleOwner
    
        override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
            this.lifcycleOwner = owner
        }
    
        override fun onCreate() {
    
        }
    
        override fun onStart() {
    
        }
    
        override fun onResume() {
    
        }
    
        override fun onPause() {
    
        }
    
        override fun onStop() {
    
        }
    
        override fun onDestroy() {
    
        }
    }
    

    至此,ViewModel就拥有了自己的Lifecycle,而其实我们的Activity/Frament也是默认实现了LifecycleOwner接口的,我们只需要在Activity/Fragment中如下操作即可使ViewModel的生命周期函数与Activity/Fragment生命周期函数同步:

    // Activty/Fragment中的内部方法
    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
    
    // 将实现了LifecycleObserver接口的ViewModol实例作为观察者,添加到Activity/Fragment的生命周期观察者队列中
    // 可以在实例化ViewModel后,即刻调用这个方法
    getLifecycle().addObserver(viewModel)
    
    // 将ViewModel从Activity/Fragment的生命周期观察者队列中移除
    // 一般可以在onDestory()方法中调用这个方法
    getLifecycle().removeObserver(viewModel)
    

    三、为ViewModel添加一些常用事件

    我们在开发中经常会遇到这样的一些场景:

    • 网络请求数据时,发生错误,弹出toast提示
    • 网络请求数据后,数据源为空,显示无数据视图
    • 网络请求数据时,弹出Loading视图,在网络请求结束后关闭
      ......

    对于上述提到的类似操作,我们可以事先在ViewModel中埋下用于通信使用的对应的LiveData,利用LiveData的观察者机制和在页面处于非活跃状态下不会通知UI更新的特性,便捷的通知Activity/Fragment作出对应的UI处理,并且完美的避过内存泄漏。

    1. 常用的UI操作接口
    interface ViewBehavior {
        /**
         * 是否显示Loading视图
         */
        fun showLoadingUI(isShow: Boolean)
    
        /**
         * 是否显示空白视图
         */
        fun showEmptyUI(isShow: Boolean)
    
        /**
         * 弹出Toast提示
         */
        fun showToast(map: Map<String, *>)
    
        /**
         * 不带参数的页面跳转
         */
        fun navigateTo(page: Any)
    
        /**
         * 返回键点击
         */
        fun backPress(arg: Any?);
    
        /**
         * 关闭页面
         */
        fun finishPage(arg: Any?)
    }
    
    1. ViewModel中添加事件LiveData
    abstract class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {
        // loading视图显示Event
        var _loadingEvent = MutableLiveData<Boolean>()
            private set
    
        // 无数据视图显示Event
        var _emptyPageEvent = MutableLiveData<Boolean>()
            private set
    
        // toast提示Event
        var _toastEvent = MutableLiveData<Map<String, *>>()
            private set
    
        // 不带参数的页面跳转Event
        var _pageNavigationEvent = MutableLiveData<Any>()
            private set
    
        // 点击系统返回键Event
        var _backPressEvent = MutableLiveData<Any?>()
            private set
    
        // 关闭页面Event
        var _finishPageEvent = MutableLiveData<Any?>()
            private set
    
        lateinit var application: Application
    
        private lateinit var lifcycleOwner: LifecycleOwner
    
        override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
            this.lifcycleOwner = owner
        }
    
        override fun onCreate() {
    
        }
    
        override fun onStart() {
    
        }
    
        override fun onResume() {
    
        }
    
        override fun onPause() {
    
        }
    
        override fun onStop() {
    
        }
    
        override fun onDestroy() {
    
        }
    
        override fun showLoadingUI(isShow: Boolean) {
            _loadingEvent.postValue(isShow)
        }
    
        override fun showEmptyUI(isShow: Boolean) {
            _emptyPageEvent.postValue(isShow)
        }
    
        override fun showToast(map: Map<String, *>) {
            _toastEvent.postValue(map)
        }
    
        override fun navigateTo(page: Any) {
            _pageNavigationEvent.postValue(page)
        }
    
        override fun backPress(arg: Any?) {
            _backPressEvent.postValue(arg)
        }
    
        override fun finishPage(arg: Any?) {
            _finishPageEvent.postValue(arg)
        }
    
        protected fun showToast(str: String) {
            showToast(str, null)
        }
    
        protected fun showToast(str: String, duration: Int?) {
            val map = HashMap<String, Any>().apply {
                put(
                    FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                    FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_STR
                )
                put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, str)
                if (duration != null) {
                    put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
                }
            }
            showToast(map)
        }
    
        protected fun showToast(@StringRes resId: Int) {
            showToast(resId, null)
        }
    
        protected fun showToast(@StringRes resId: Int, duration: Int?) {
            val map = HashMap<String, Any>().apply {
                put(
                    FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                    FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_RESID
                )
                put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, resId)
                if (duration != null) {
                    put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
                }
            }
            showToast(map)
        }
    
        protected fun backPress() {
            backPress(null)
        }
    
        protected fun finishPage() {
            finishPage(null)
        }
    }
    
    1. Activity中对ViewModel中的事件进行处理
    abstract class BaseBVMActivity<B : ViewDataBinding, VM : BaseViewModel> : BaseBindingActivity<B>(),
        ViewBehavior {
    
        protected lateinit var viewModel: VM
    
        protected fun injectViewModel() {
            val vm = createViewModel()
            viewModel = ViewModelProvider(this, BaseViewModel.createViewModelFactory(vm))
                .get(vm::class.java)
            viewModel.application = application
            lifecycle.addObserver(viewModel)
        }
    
        override fun init(savedInstanceState: Bundle?) {
            injectViewModel()
            initialize(savedInstanceState)
            initInternalObserver()
        }
    
        fun getActivityViewModel(): VM {
            return viewModel
        }
    
        override fun onDestroy() {
            super.onDestroy()
            binding.unbind()
            lifecycle.removeObserver(viewModel)
        }
    
        protected fun initInternalObserver() {
            viewModel._loadingEvent.observeNonNull(this, {
                showLoadingUI(it)
            })
            viewModel._emptyPageEvent.observeNonNull(this, {
                showEmptyUI(it)
            })
            viewModel._toastEvent.observeNonNull(this, {
                showToast(it)
            })
            viewModel._pageNavigationEvent.observeNonNull(this, {
                navigateTo(it)
            })
            viewModel._backPressEvent.observeNullable(this, {
                backPress(it)
            })
            viewModel._finishPageEvent.observeNullable(this, {
                finishPage(it)
            })
        }
    
        protected abstract fun createViewModel(): VM
    
        /**
         *  初始化操作
         */
        protected abstract fun initialize(savedInstanceState: Bundle?)
    }
    

    四、为ViewModel添加Application

    在ViewModel中,我们可能会使用到Context,但是上述我们了解到,在ViewModel中并适合持有Activity/Fragment的引用,所以Google为我们提供了一个AndroidViewModel,源码很简单,如下:

    public class AndroidViewModel extends ViewModel {
        @SuppressLint("StaticFieldLeak")
        private Application mApplication;
    
        public AndroidViewModel(@NonNull Application application) {
            mApplication = application;
        }
    
        /**
         * Return the application.
         */
        @SuppressWarnings("TypeParameterUnusedInFormals")
        @NonNull
        public <T extends Application> T getApplication() {
            //noinspection unchecked
            return (T) mApplication;
        }
    }
    

    上面的AndroidViewModel仅仅是在ViewMdoel中添加了一个Application的引用,我们也完全可以自己实现这一步,让我们的封装更灵活

    abstract class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {
        ......
    
        @SuppressLint("StaticFieldLeak")
        lateinit var application: Application
    
        ......
    }
    
    abstract class BaseBVMActivity<B : ViewDataBinding, VM : BaseViewModel> : BaseBindingActivity<B>(),
        ViewBehavior {
    
        protected lateinit var viewModel: VM
    
        protected fun injectViewModel() {
            val vm = createViewModel()
            viewModel = ViewModelProvider(this, BaseViewModel.createViewModelFactory(vm))
                .get(vm::class.java)
            viewModel.application = application
            lifecycle.addObserver(viewModel)
        }
    }
    

    五、使用工厂模式创建ViewModel

    ViewModel的创建并不止一种形式,我们常用的创建方式,如下

     val viewModel = ViewModelProvider(this).get(vm::class.java)
    

    使用以上的方式创建ViewModel基本可以满足我们一般的需求,但是假如我们需要在实例化ViewModel的时候传入参数,那么我们就必须使用工厂方式来创建ViewModel

    val vm = LoginViewModel(loginRepository)
    viewModel = ViewModelProvider(this,BaseViewModel.createViewModelFactory(vm))
    .get(vm:class.java)
    

    六、ViewModel封装的完整代码

    /**
     * @author: Albert Li
     * @contact: albertlii@163.com
     * @time: 2020/6/7 10:27 PM
     * @description: ViewModel感知生命周期接口
     * @since: 1.0.0
     */
    interface ViewModelLifecycle:LifecycleObserver {
    
        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
        fun onAny(owner: LifecycleOwner, event: Lifecycle.Event)
    
        @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
        fun onCreate()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun onStart()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun onResume()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun onPause()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onStop()
    
        @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        fun onDestroy()
    }
    
    /**
     * @author: Albert Li
     * @contact: albertlii@163.com
     * @time: 2020/6/9 8:01 PM
     * @description: 页面的常用操作
     * @since: 1.0.0
     */
    interface ViewBehavior {
        /**
         * 是否显示Loading视图
         */
        fun showLoadingUI(isShow: Boolean)
    
        /**
         * 是否显示空白视图
         */
        fun showEmptyUI(isShow: Boolean)
    
        /**
         * 弹出Toast提示
         */
        fun showToast(map: Map<String, *>)
    
        /**
         * 不带参数的页面跳转
         */
        fun navigateTo(page: Any)
    
        /**
         * 返回键点击
         */
        fun backPress(arg: Any?);
    
        /**
         * 关闭页面
         */
        fun finishPage(arg: Any?)
    }
    
    /**
     * @author: Albert Li
     * @contact: albertlii@163.com
     * @time: 2020/6/7 10:30 PM
     * @description: ViewModel的基类
     * @since: 1.0.0
     */
    abstract class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {
    
        // loading视图显示Event
        var _loadingEvent = MutableLiveData<Boolean>()
            private set
    
        // 无数据视图显示Event
        var _emptyPageEvent = MutableLiveData<Boolean>()
            private set
    
        // toast提示Event
        var _toastEvent = MutableLiveData<Map<String, *>>()
            private set
    
        // 不带参数的页面跳转Event
        var _pageNavigationEvent = MutableLiveData<Any>()
            private set
    
        // 点击系统返回键Event
        var _backPressEvent = MutableLiveData<Any?>()
            private set
    
        // 关闭页面Event
        var _finishPageEvent = MutableLiveData<Any?>()
            private set
    
        @SuppressLint("StaticFieldLeak")
        lateinit var application: Application
    
        private lateinit var lifcycleOwner: LifecycleOwner
    
        override fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
            this.lifcycleOwner = owner
        }
    
        override fun onCreate() {
    
        }
    
        override fun onStart() {
    
        }
    
        override fun onResume() {
    
        }
    
        override fun onPause() {
    
        }
    
        override fun onStop() {
    
        }
    
        override fun onDestroy() {
    
        }
    
        override fun showLoadingUI(isShow: Boolean) {
            _loadingEvent.postValue(isShow)
        }
    
        override fun showEmptyUI(isShow: Boolean) {
            _emptyPageEvent.postValue(isShow)
        }
    
        override fun showToast(map: Map<String, *>) {
            _toastEvent.postValue(map)
        }
    
        override fun navigateTo(page: Any) {
            _pageNavigationEvent.postValue(page)
        }
    
        override fun backPress(arg: Any?) {
            _backPressEvent.postValue(arg)
        }
    
        override fun finishPage(arg: Any?) {
            _finishPageEvent.postValue(arg)
        }
    
        protected fun showToast(str: String) {
            showToast(str, null)
        }
    
        protected fun showToast(str: String, duration: Int?) {
            val map = HashMap<String, Any>().apply {
                put(
                    FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                    FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_STR
                )
                put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, str)
                if (duration != null) {
                    put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
                }
            }
            showToast(map)
        }
    
        protected fun showToast(@StringRes resId: Int) {
            showToast(resId, null)
        }
    
        protected fun showToast(@StringRes resId: Int, duration: Int?) {
            val map = HashMap<String, Any>().apply {
                put(
                    FlyBaseConstants.FLY_TOAST_KEY_CONTENT_TYPE,
                    FlyBaseConstants.FLY_TOAST_CONTENT_TYPE_RESID
                )
                put(FlyBaseConstants.FLY_TOAST_KEY_CONTENT, resId)
                if (duration != null) {
                    put(FlyBaseConstants.FLY_TOAST_KEY_DURATION, duration)
                }
            }
            showToast(map)
        }
    
        protected fun backPress() {
            backPress(null)
        }
    
        protected fun finishPage() {
            finishPage(null)
        }
    
        companion object {
    
            @JvmStatic
            fun <T : BaseViewModel> createViewModelFactory(viewModel: T): ViewModelProvider.Factory {
                return ViewModelFactory(viewModel)
            }
        }
    }
    
    
    /**
     * 创建ViewModel的工厂,以此方法创建的ViewModel,可在构造函数中传参
     */
    class ViewModelFactory(val viewModel: BaseViewModel) : ViewModelProvider.Factory {
    
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return viewModel as T
        }
    }
    
    

    Github传送门

    https://github.com/albert-lii/Fly-Android

    今后文章将同步更新在作者微信公众号中,欢迎关注


    微信公众号.jpg

    相关文章

      网友评论

        本文标题:Android开发之MVVM模式实践(一):ViewModel的

        本文链接:https://www.haomeiwen.com/subject/rleaxktx.html