Android MVVM框架 | 青训营笔记

260 阅读3分钟

Android MVVM框架

这是我参与「第四届青训营 」笔记创作活动的第5天

介绍

MVVM 模式将代码划分为三个部分:

  • View: Activity 和 Layout XML 文件,与 MVP 中 View 的概念相同
  • Model: 负责管理业务数据逻辑,如网络请求、数据库处理,与 MVP 中 Model 的概念相同
  • ViewModel: 存储视图状态,负责处理表现逻辑,并将数据设置给可观察数据容器

在实现细节上,View 和 Presenter 从双向依赖变成 View 可以向 ViewModel 发指令,但 ViewModel 不会直接向 View 回调,而是让 View 通过观察者的模式去监听数据的变化,有效规避了 MVP 双向依赖的缺点。

实现

IViewModel

定义ViewModel实现的共同方法,可以在这设置加载,错误处理函数

interface IViewModel {
    fun showLoading()
    fun closeLoading()
}

LifeViewModel

生命周期管理类,可以自己选择加或者不加

open class LifeViewModel : ViewModel(), DefaultLifecycleObserver {
    companion object {
        private const val TAG = "MyLifeViewModel"
    }
​
    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        Log.d(TAG, "onCreate: ")
    }
​
    override fun onStart(owner: LifecycleOwner) {
        super.onStart(owner)
        Log.d(TAG, "onStart: ")
    }
​
    override fun onResume(owner: LifecycleOwner) {
        super.onResume(owner)
        Log.d(TAG, "onResume: ")
    }
​
    override fun onPause(owner: LifecycleOwner) {
        super.onPause(owner)
        Log.d(TAG, "onPause: ")
    }
​
    override fun onStop(owner: LifecycleOwner) {
        super.onStop(owner)
        Log.d(TAG, "onStop: ")
    }
​
    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
        Log.d(TAG, "onDestroy: ")
    }
​
}

BaseViewModel

open class BaseViewModel : LifeViewModel(),IViewModel {
​
    var loadingEvent = MutableLiveData<Boolean>()
​
    override fun showLoading() {
        loadingEvent.postValue(true)
    }
​
    override fun closeLoading() {
        loadingEvent.postValue(false)
    }
}

LoadingDialog

加载LoadingDialog

class LoadingDialog(context: Context, themeId: Int) : Dialog(context, themeId) {
​
    init {
        initView()
    }
​
    private fun initView(){
        setContentView(R.layout.dialog_loading)
        setCanceledOnTouchOutside(true)
        val attributes: WindowManager.LayoutParams = window!!.attributes
        attributes.alpha = 0.5f
        window!!.attributes = attributes
        setCancelable(false)
    }
​
}

BaseActivity

abstract class BaseActivity<VDB : ViewDataBinding, VM : BaseViewModel> : AppCompatActivity() {
​
    private var sViewModel: VM? = null
    private var sViewDateBinding: VDB? = null
    private lateinit var loadingDialog: LoadingDialog
​
    override fun onCreate(savedInstanceState: Bundle?) {
        setStatusBarColor(Color.BLACK)
        super.onCreate(savedInstanceState)
        handlerVDB()
        handlerVM()
        loadingDialog = getLoadingDialog()
        receiveLiveData()
        initData(savedInstanceState)
    }
​
    private fun handlerVDB() {
        sViewDateBinding = DataBindingUtil.setContentView(this, getLayoutId())
        sViewDateBinding?.lifecycleOwner = this
    }
​
    private fun handlerVM() {
        val viewModelClass: Class<BaseViewModel>
        val type = javaClass.genericSuperclass
        viewModelClass = if (type is ParameterizedType) {
            type.actualTypeArguments[1] as Class<BaseViewModel> //获取第1个注解即VM的注解类型
        } else {
            //使用父类的类型
            BaseViewModel::class.java
        }
        sViewModel = ViewModelProvider(this)[viewModelClass] as VM
        if (getVariableId() > 0) {
            if (sViewModel != null) {
                lifecycle.addObserver(sViewModel!!)
            }
            sViewDateBinding?.setVariable(getVariableId(), sViewModel)
        }
    }
​
    abstract fun getLayoutId(): Int
​
    private fun receiveLiveData() {
        sViewModel?.loadingEvent?.observe(this) { aBoolean ->
            if (aBoolean) {
                showLoading()
            } else {
                dismissLoading()
            }
        }
    }
​
    open fun showLoading() {
        if (!loadingDialog.isShowing) {
            loadingDialog.show()
        }
    }
​
    private fun getLoadingDialog(): LoadingDialog =
        LoadingDialog(this, R.style.trans_Dialog)
​
​
    open fun dismissLoading() {
        if (loadingDialog.isShowing) {
            loadingDialog.dismiss()
        }
    }
​
​
    /**
     * 初始化ViewModel的id
     *
     * @return BR的id
     */
    abstract fun getVariableId(): Int
​
    /**
     * 初始化数据,相当于OnCreate方法
     */
    abstract fun initData(savedInstanceState: Bundle?)
​
​
    /**
     * 获取当前Activity的ViewModel
     */
    open fun getViewModel(): VM = sViewModel!!
​
    /**
     * 获取当前Activity的DataBinding
     */
    open fun getDataBinding() : VDB = sViewDateBinding!!
}

BaseFragment

abstract class BaseFragment<VDB : ViewDataBinding, VM : BaseViewModel> : Fragment() {
    private var viewModel: VM? = null
    private var viewDataBinding: VDB? = null
​
    private lateinit var loadingDialog : LoadingDialog
​
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
        return if (viewDataBinding != null) {
            viewDataBinding!!.lifecycleOwner = this
            viewDataBinding!!.root
        } else inflater.inflate(getLayoutId(), container, false)
    }
​
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        handlerVM()
        loadingDialog = LoadingDialog(requireActivity(), R.style.trans_Dialog)
        receiveLiveData()
        initData(savedInstanceState)
    }
​
    private fun handlerVM() {
        val viewModelClass: Class<BaseViewModel>
        val type = javaClass.genericSuperclass
        viewModelClass = if (type is ParameterizedType) {
            type.actualTypeArguments[1] as Class<BaseViewModel> //获取第1个注解即VM的注解类型
        } else {
            //使用父类的类型
            BaseViewModel::class.java
        }
        viewModel = ViewModelProvider(requireActivity())[viewModelClass] as VM //找到Activity对于的VM
        if (viewModel == null) {
            viewModel = ViewModelProvider(this)[viewModelClass] as VM //fragment自己的VM 不是Activity
        }
        if (getVariableId() > 0) {
            if (viewModel != null) lifecycle.addObserver(viewModel!!)
            viewDataBinding?.setVariable(getVariableId(), viewModel)
        }
    }
​
    private fun receiveLiveData() {
        if (viewModel != null){
            viewModel!!.loadingEvent.observe(viewLifecycleOwner){ aBoolean ->
                if (aBoolean) {
                    showLoading()
                } else {
                    dismissLoading()
                }
            }
        }
    }
​
    abstract fun getLayoutId(): Int
​
    open fun getViewModel(): VM {
        return viewModel!!
    }
​
    open fun getViewDataBinding(): VDB {
        return viewDataBinding!!
​
    }
​
    abstract fun initData(savedInstanceState: Bundle?)
​
    /**
     * 初始化ViewModel的id
     *
     * @return BR的id
     */
    abstract fun getVariableId(): Int
​
    open fun showLoading() {
        if (!loadingDialog.isShowing) {
            loadingDialog.show()
        }
    }
​
    open fun dismissLoading() {
        loadingDialog.dismiss()
    }
​
    override fun onDestroy() {
        super.onDestroy()
        viewDataBinding?.unbind()
        viewDataBinding = null
    }
​
}

StateBarUtil

/**
 * 设置状态栏颜色
 */
fun Activity.setStatusBarColor(color: Int) {
    window.statusBarColor = color
}
​
/**
 * 状态栏反色
 */
fun Activity.setAndroidNativeLightStatusBar() {
    val controller = ViewCompat.getWindowInsetsController(window.decorView)
    controller?.isAppearanceLightStatusBars = !isDarkMode()
}
​
/**
 * 获取当前是否为深色模式
 * 深色模式的值为:0x21
 * 浅色模式的值为:0x11
 * @return true 为是深色模式   false为不是深色模式
 */
fun Context.isDarkMode(): Boolean {
    return resources.configuration.uiMode == 0x21
}

使用

//Fragment同理
class TestActivity : BaseActivity<ActivityTestBinding,TestViewModel>() {
​
    //设置Activity的xml布局
    override fun getLayoutId() = R.layout.activity_test
​
    //在xml中设置ViewModel的data,然后在该方法中添加
    override fun getVariableId() = BR.friendViewModel
​
    //相当于onCreate
    override fun initData(savedInstanceState: Bundle?) {
        
    }
​
}
class TestViewModel : BaseViewModel(){
    //写正常的ViewModel逻辑即可
}

缺点

  1. 这个框架每个Activity只绑定了一个ViewModel,如果需要用到多个ViewModel,则需要自己改代码或者直接创建
  2. 没有实现错误处理的捕获,出现错误时依旧会出现闪退现象,需要改进
  3. 还是具有MVVM的一些缺陷,页面逻辑复杂时,会出现大量的MutableLiveData,并且暴露为不可变的LiveData,所以可以尝试结合Flow来使用