前言
目前市面上流行的客户端app的架构基本都是基于MVVM 设计的,其实就是为了去更好的使用jetpack 组件,mvvm配合jetpack 去搭建的话,不仅仅在业务上达到了解耦、方便维护和review的效果,如果配合lifeCycle 的使用,更能有效的减少对象生命周期的控制问题导致的内存泄漏。而使用kotlin的初衷,如flow, 能很好的替代RxJava 和jetpack 中的LiveData,如协程能更轻量合理减少的对于线程的创建开销、线程切换负责、线程关闭等一系列繁琐的操作。所以,使用kotlin 和 mvvm +jetpack 组件去搭建的的框架思想源于此。
mvvm 图解
mvvm 几乎就是mvc 的优化版,将mvc中的c 替换成了model,减少了c层逻辑耦合性和代码雍总问题。
从图中可以看到,mvvm 有三个模块,分别是ViewModel、Model、View
ViewModel: 负责管理数据的抽象类,通过ViewBinding 中的setVariable 方法绑定绑定到View层 进行页面更新。
ViewModel 的生命周期会从创建绑定到Activity/Fragment 中开始 一直到Activity/Fragment 的onDestroy 结束,所以,不需要担心数据在
使用过程中丢失。Model: 负责业务逻辑处理,如网络请求、数据库、耗时等操作,通常model 层会和ViewModel 使用同一个类进行封装处理,这样的好处在与,当model中的数据处理完毕拿到结果时,可以通知ViewModel 中需要更新的变量进行更新,再者ViewModel 会通知View 刷新更改页面的值。
View: 负责页面展示,在Activity\Fragment 创建时,通过binding.setVariable()的方式 将ViewModel 绑定到页面布局中。从而打到双向绑定的效果。
导入相关包以及gradle 配置
- 1、kotlin 和viewmodel 相关
api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0' //viewmodel api 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0' api 'androidx.appcompat:appcompat:1.6.0-alpha05' api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1' //协程 api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1' api 'androidx.core:core-ktx:1.8.0' //kt
api 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.2'//rxlifecycler
- 2、子目录下的build.gradle中配置,以下不配置的话,kotlin 会报错。
plugins { id 'org.jetbrains.kotlin.android'}apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'kapt { generateStubs = true}//开启databindingandroid{ buildFeatures{ dataBinding = true } }
- 3、根目录build.gradle 配置
ext.kotlin_version ='1.7.0' dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21" } plugins { id 'com.android.application' version '7.1.3' apply false id 'com.android.library' version '7.1.3' apply false id 'org.jetbrains.kotlin.android' version '1.6.21' apply false// id 'org.jetbrains.kotlin:kotlin-gradle-plugin' version '1.7.0' apply false}
- 4、gradle.properties 中配置
android.useAndroidX=truekotlin.code.style=officialandroid.enableJetifier=true//表示Android插件会通过重写其二进制文件来自动迁移现有的第三方库,以使用AndroidX依赖项;未设置时默认为false;//必要配置项enableJetifier
base 封装
- 1、先看看大致也结构:
BaseActivity\BaseFragment代表View层,也就是直观看到界面组件
BaseViewModel Model层和ViewModel 的组成
IBaseViewModel 实现LifecycleObserver的一个自定义接口,让BaseViewModel 同样能感知组件的生命周期,方便我们在生命周期中处理一下逻辑。
- 2、封装
- BaseActivity
package com.kt.ktmvvm.basicimport android.content.Intentimport android.os.Bundleimport androidx.databinding.DataBindingUtilimport androidx.databinding.ViewDataBindingimport androidx.fragment.app.FragmentActivityimport androidx.lifecycle.ViewModelimport androidx.lifecycle.ViewModelProviderimport com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.BUNDLEimport com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.CLASSimport com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.REQUESTimport com.trello.rxlifecycle2.components.support.RxAppCompatActivityimport java.lang.reflect.ParameterizedTypeabstract class BaseActivity<V : ViewDataBinding, VM : BaseViewModel> : RxAppCompatActivity(), IBaseView { open var binding: V? = null open var viewModel: VM? = null open var viewModelId = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initViewDataBinding(savedInstanceState) //页面接受的参数方法 initParam() //私有的ViewModel与View的契约事件回调逻辑 registerUIChangeLiveDataCallBack() //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册 initViewObservable() } private fun registerUIChangeLiveDataCallBack() { //跳入新页面 viewModel?.getUC()?.getStartActivityEvent()?.observe(this) { params -> params?.let { val clz = params[CLASS] as Class<*>? val intent = Intent(this@BaseActivity, clz)// intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); val bundle = params[BUNDLE] if (bundle is Bundle) { intent.putExtras((bundle as Bundle?)!!) } startActivityForResult(intent, params[REQUEST] as Int) } } viewModel?.getUC()?.getFinishResult()?.observe(this) { integer -> integer?.let { setResult(integer) finish() } } //关闭界面 viewModel?.getUC()?.getFinishEvent()?.observe(this) { finish() } //关闭上一层 viewModel?.getUC()?.getOnBackPressedEvent()?.observe(this) { onBackPressed() } viewModel?.getUC()?.getSetResultEvent()?.observe(this) { params -> params?.let { val intent = Intent() if (params.isNotEmpty()) { val strings: Set<String> = params.keys for (string in strings) { intent.putExtra(string, params[string]) } } setResult(RESULT_OK, intent) } } } private fun initViewDataBinding(savedInstanceState: Bundle?) { //DataBindingUtil类需要在project的build中配置 dataBinding {enabled true }, 同步后会自动关联android.databinding包 binding = DataBindingUtil.setContentView(this@BaseActivity, initContentView(savedInstanceState)) viewModelId = initVariableId() val modelClass: Class<BaseViewModel> val type = javaClass.genericSuperclass modelClass = if (type is ParameterizedType) { type.actualTypeArguments[1] as Class<BaseViewModel> } else { //如果没有指定泛型参数,则默认使用BaseViewModel BaseViewModel::class.java } viewModel = createViewModel(this, modelClass as Class<VM>) //关联ViewModel binding?.setVariable(viewModelId, viewModel) //支持LiveData绑定xml,数据改变,UI自动会更新 binding?.lifecycleOwner = this //让ViewModel拥有View的生命周期感应 lifecycle.addObserver(viewModel!!) //注入RxLifecycle生命周期 viewModel?.injectLifecycleProvider(this) } override fun onDestroy() { super.onDestroy() binding?.unbind() } /** * 创建ViewModel 如果 需要自己定义ViewModel 直接复写此方法 * * @param cls * @param * @return */ open fun <T : ViewModel> createViewModel(activity: FragmentActivity?, cls: Class<T>?): T { return ViewModelProvider(activity!!)[cls!!] } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) viewModel?.onActivityResult(requestCode, resultCode, data) } /** * 提供livedata 或者flow 数据流观察回调 */ override fun initViewObservable() { } /** * 返回vaeriableId */ abstract fun initVariableId(): Int /** * 返回布局id */ abstract fun initContentView(savedInstanceState: Bundle?): Int}
- BaseFragment 和BaseActivity 差不多
package com.kt.ktmvvm.basicimport android.content.Intentimport android.os.Bundleimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport androidx.databinding.DataBindingUtilimport androidx.databinding.ViewDataBindingimport androidx.fragment.app.Fragmentimport androidx.fragment.app.FragmentActivityimport androidx.lifecycle.ViewModelimport androidx.lifecycle.ViewModelProviderimport com.trello.rxlifecycle2.components.support.RxAppCompatActivityimport com.trello.rxlifecycle2.components.support.RxFragmentimport java.lang.reflect.ParameterizedTypeabstract class BaseFragment<V : ViewDataBinding, VM : BaseViewModel> : RxFragment(), IBaseView { open var binding: V? = null open var viewModel: VM? = null open var viewModelId = 0 @Deprecated("Deprecated in Java") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initParam() } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = DataBindingUtil.inflate<ViewDataBinding>( inflater, initContentView(inflater, container, savedInstanceState), container, false ) as V? return binding?.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) //私有的初始化Databinding和ViewModel方法 initViewDataBinding() //私有的ViewModel与View的契约事件回调逻辑 registerUIChangeLiveDataCallBack() //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册 initViewObservable() } private fun registerUIChangeLiveDataCallBack() { //跳入新页面 viewModel?.getUC()?.getStartActivityEvent()?.observe(this) { params -> params?.let { val clz = params[BaseViewModel.Companion.ParameterField.CLASS] as Class<*>? val intent = Intent(activity, clz)// intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); val bundle = params[BaseViewModel.Companion.ParameterField.BUNDLE] if (bundle is Bundle) { intent.putExtras((bundle as Bundle?)!!) } this@BaseFragment.startActivityForResult( intent, params[BaseViewModel.Companion.ParameterField.REQUEST] as Int ) } } viewModel?.getUC()?.getFinishResult()?.observe(this) { integer -> integer?.let { activity?.setResult(integer) activity?.finish() } } //关闭界面 viewModel?.getUC()?.getFinishEvent()?.observe(this) { activity?.finish() } //关闭上一层 viewModel?.getUC()?.getOnBackPressedEvent()?.observe(this) { activity?.onBackPressed() } viewModel?.getUC()?.getSetResultEvent()?.observe(this) { params -> params?.let { val intent = Intent() if (params.isNotEmpty()) { val strings: Set<String> = params.keys for (string in strings) { intent.putExtra(string, params[string]) } } activity?.setResult(RxAppCompatActivity.RESULT_OK, intent) } } } private fun initViewDataBinding() { viewModelId = initVariableId() viewModelId = initVariableId() val modelClass: Class<BaseViewModel> val type = javaClass.genericSuperclass modelClass = if (type is ParameterizedType) { type.actualTypeArguments[1] as Class<BaseViewModel> } else { //如果没有指定泛型参数,则默认使用BaseViewModel BaseViewModel::class.java } viewModel = createViewModel(this, modelClass as Class<VM>) //关联ViewModel binding?.setVariable(viewModelId, viewModel) //支持LiveData绑定xml,数据改变,UI自动会更新 binding?.lifecycleOwner = this //让ViewModel拥有View的生命周期感应 lifecycle.addObserver(viewModel!!) //注入RxLifecycle生命周期 viewModel?.injectLifecycleProvider(this) } open fun <T : ViewModel> createViewModel(fragment: Fragment?, cls: Class<T>?): T { return ViewModelProvider(fragment!!)[cls!!] } /** * 返回variableid */ abstract fun initVariableId(): Int /** * 返回布局id */ abstract fun initContentView( inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle? ): Int}
- BaseViewModel 封装
package com.kt.ktmvvm.basicimport android.app.Activityimport android.app.Applicationimport android.content.Intentimport android.os.Bundleimport androidx.lifecycle.*import com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.BUNDLEimport com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.CLASSimport com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.REQEUST_DEFAULTimport com.kt.ktmvvm.basic.BaseViewModel.Companion.ParameterField.REQUESTimport com.kt.ktmvvm.net.ExceptionUtilimport com.trello.rxlifecycle2.LifecycleProviderimport kotlinx.coroutines.CoroutineExceptionHandlerimport kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.launchimport java.lang.ref.WeakReferenceopen class BaseViewModel(application: Application) : AndroidViewModel(application), IBaseViewModel { private var mLifecycle: WeakReference<LifecycleProvider<*>>? = null private var uc: UIChangeLiveData? = null //生命周期管理 override fun onAny(owner: LifecycleOwner?, event: Lifecycle.Event?) { } override fun onCreate() { } override fun onDestroy() { } override fun onStart() { } override fun onStop() { } override fun onResume() { } override fun onPause() { } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { } /** * 注入RxLifecycle生命周期 * * @param lifecycle */ fun injectLifecycleProvider(lifecycle: LifecycleProvider<*>?) { mLifecycle = WeakReference(lifecycle) } /** * 跳转页面 * * @param clz 所跳转的目的Activity类 */ fun startActivity(clz: Class<out Activity?>?) { startActivity(clz, null) } /** * @param clz clz 所跳转的目的Activity类 * @param code 启动requestCode */ fun startActivity(clz: Class<out Activity?>?, code: Int) { startActivity(clz, null, code) } /** * 跳转页面 * * @param clz 所跳转的目的Activity类 * @param bundle 跳转所携带的信息 */ private fun startActivity(clz: Class<out Activity?>?, bundle: Bundle?) { startActivity(clz, bundle, REQEUST_DEFAULT) } /** * 跳转页面 * * @param clz 所跳转的目的Activity类 * @param bundle 跳转所携带的信息 */ fun startActivity(clz: Class<out Activity?>?, bundle: Bundle?, requestCode: Int) { val params: MutableMap<String, Any?> = HashMap() params[CLASS] = clz params[REQUEST] = requestCode params[BUNDLE] = bundle uc?.getStartActivityEvent()?.postValue(params as Map<String, Any>) } fun startActivityForFragment(clz: Class<out Activity?>, bundle: Bundle, requestCode: Int) { val params: MutableMap<String, Any> = HashMap() params[CLASS] = clz params[REQUEST] = requestCode params[BUNDLE] = bundle uc?.getStartActivityForFragment()?.postValue(params) } /** * 关闭界面 */ fun finish() { uc?.getFinishEvent()?.postValue(null) } /** * 携带code的 finish */ fun finishFragmentResult() { uc?.getResultFragment()?.postValue(null) } /** * 返回上一层 */ fun onBackPressed() { uc?.getOnBackPressedEvent()!!.postValue(null) } fun getUC(): UIChangeLiveData? { if (uc == null) { uc = UIChangeLiveData() } return uc } companion object { class UIChangeLiveData : SingleLiveEvent<Any?>() { private var startActivityEvent: SingleLiveEvent<Map<String, Any>>? = null private var finishEvent: SingleLiveEvent<Void>? = null private var onBackPressedEvent: SingleLiveEvent<Void>? = null private var setResultEvent: SingleLiveEvent<Map<String, String>>? = null private var finishResult: SingleLiveEvent<Int>? = null private var startActivityForFragment: SingleLiveEvent<Map<String, Any>>? = null private var setResultFragment: SingleLiveEvent<Map<String, Any>>? = null fun getResultFragment(): SingleLiveEvent<Map<String, Any>> { return createLiveData(setResultFragment).also { setResultFragment = it } } fun getStartActivityForFragment(): SingleLiveEvent<Map<String, Any>> { return createLiveData(startActivityForFragment).also { startActivityForFragment = it } } fun getFinishResult(): SingleLiveEvent<Int> { return createLiveData(finishResult ).also { finishResult = it } } fun getStartActivityEvent(): SingleLiveEvent<Map<String, Any>> { return createLiveData(startActivityEvent).also { startActivityEvent = it } } fun getSetResultEvent(): SingleLiveEvent<Map<String, String>> { return createLiveData(setResultEvent).also { setResultEvent = it } } fun getFinishEvent(): SingleLiveEvent<Void> { return createLiveData(finishEvent).also { finishEvent = it } } fun getOnBackPressedEvent(): SingleLiveEvent<Void> { return createLiveData(onBackPressedEvent).also { onBackPressedEvent = it } } private fun <T> createLiveData(liveData: SingleLiveEvent<T>?): SingleLiveEvent<T> { var mLive: SingleLiveEvent<T>?=liveData liveData?.let { return mLive!! }?:let { mLive= SingleLiveEvent() } return mLive!! } override fun observe(owner: LifecycleOwner, observer: Observer<in Any?>) { super.observe(owner, observer) } } object ParameterField { const val CLASS = "CLASS" const val CANONICAL_NAME = "CANONICAL_NAME" const val BUNDLE = "BUNDLE" const val REQUEST = "REQUEST" const val REQEUST_DEFAULT = 1 } }}
使用方式
- 1、创建model
package com.kt.ktmvvmimport android.app.Applicationimport android.util.Logimport android.widget.Toastimport androidx.databinding.ObservableFieldimport androidx.lifecycle.viewModelScopeimport com.kt.ktmvvm.basic.BaseViewModelimport com.kt.ktmvvm.download.DownloadActivityimport com.kt.ktmvvm.jetpack.coordinatorlayout.CoordinatorActivityimport com.kt.ktmvvm.jetpack.room.RoomActivityimport com.kt.ktmvvm.jetpack.viewpager.ViewPager2Activityimport com.kt.ktmvvm.net.ApiExceptionimport com.kt.ktmvvm.net.DataServiceimport kotlinx.coroutines.launchimport kotlin.math.logopen class MainViewModel(application: Application) : BaseViewModel(application) { companion object { val TAG: String? = MainViewModel::class.simpleName } /** * 跳转viewpager2 */ open fun goViewPager2() { startActivity(ViewPager2Activity::class.java) } /** * 进入room数据库 */ open fun goRoom() { startActivity(RoomActivity::class.java) } /** * 进入coordinator */ open fun goCoordinator() { startActivity(CoordinatorActivity::class.java) } /** * 进入下载器 */ open fun goDownloadManager() { startActivity(DownloadActivity::class.java) }}
- 2.创建布局
<?xml version="1.0" encoding="utf-8"?><layout xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="model" type="com.kt.ktmvvm.MainViewModel" /> </data> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" tools:context=".MainActivity"> </androidx.appcompat.widget.LinearLayoutCompat></layout>
布局中需要用 layout 和data 包裹并导入 model路径,如上.
- 3.创建activity
package com.kt.ktmvvmimport android.os.Bundleimport android.view.LayoutInflaterimport androidx.databinding.DataBindingUtilimport com.kt.ktmvvm.basic.BaseActivityimport com.kt.ktmvvm.databinding.ActivityMainNewBindingclass MainActivity : BaseActivity<ActivityMainNewBinding, MainViewModel>() { override fun initParam() { } override fun initVariableId(): Int { return BR.model } override fun initContentView(savedInstanceState: Bundle?): Int { return R.layout.activity_main_new }}
代码已上传github:https://github.com/ljlstudio/KtMvvm
以上就是使用kotlin 语言编写的MVVM 设计模式架构,使用起来非常方便,也很好维护,只需维护model中的业务逻辑即可。
下一篇:《Kotlin系列》之协程搭配Retrofit+OkHttp3网络请求封装