• 浅析MVC、MVP、MMVM 架构


    MVC

    模型

    在这里插入图片描述

    • Model

    指数据逻辑和实体模型

    • View

    指布局文件

    • Controllor

    指Activity,既要负责页面的展示和交互,还得负责数据的请求和业务逻辑之类的工作。

    看起来MVC架构很清晰,但是实际的开发中,请求的业务代码往往被丢到了Activity里面,大家都知道layout.xml的布局文件只能提供默认的UI设置,所以开发中视图层的变化也被丢到了Activity里面,再加上Activity本身承担着控制层的责任。所以Activity达成了MVC集合的成就,最终我们的Activity就变得越来越难看,从几百行变成了几千行,维护的成本也越来越高。

    MVP

    MVP和MVC 相比而言,唯一的差别是Model和View之间不进行通讯,都是通过Presenter完成。

    模型

    在这里插入图片描述

    • Model

    实体模型

    • View

    Activity 或者Fragment,负责View的绘制以及与用户交互

    • Presenter

    负责View与Model间的交互与逻辑处理

    Presenter可以使View(Activity)不用直接和Model打交道,View(Activity)只用负责页面的显示和交互,剩下的和Model交互的事情都交给Presenter做,比如一些网络请求、数据的获取等,当Presenter获取到数据后再交给View(Activity)进行展示,这样,Activity的任务就大大减小了。

    优缺点

    优点
    • 分离了视图逻辑和业务逻辑,降低了耦合
    • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
    缺点
    • 接口暴增,类膨胀问题。
    • 内存泄漏问题。由于P和V是互相引用,如果页面销毁时P还有正在进行的任务,那Activity无法回收,就发生了内存泄漏。

    MVP 例子

    具体操作思路:MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。在MVP模式中Activity的功能就是响应生命周期和显示界面,具体其他的工作都丢到了Presenter层中进行完成,Presenter其实是Model层和View层的桥梁。下面的例子解决了内存泄漏问题

    BaseView

    界面需要提供的UI方法中会有很多类似的UI方法,可以把它们提取到一个公共的父类接口中。比如提取显示loading界面和隐藏loading界面的方法,其他的view层接口就可以直接继承BaseView接口,不必重复的写显示和隐藏loading界面方法。

    public interface BaseView {
        void  showLoading();
    
        void hideLoading();
    
        void showMessage(String message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    BasePresenter

    共有的功能:添加view的绑定与销毁。解决内存泄漏问题。

    public abstract class BasePresenter <V extends BaseView>{
    
        private V mView;
    
        /**
         *  绑定 View
         * @param mView
         */
        public void attachMView(V mView){
            this.mView=mView;
        }
    
        /**
         * 解绑View
         */
        public void detachMView(){
            mView=null;
        }
    
        public V getMView() {
            return mView;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    BaseActivity

    共有的功能:Presenter绑定到activity,View的绑定和解绑操作。

    public abstract class BaseActivity<V extends BaseView,P extends BasePresenter<V>> extends AppCompatActivity {
    
        protected P mPresenter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            if (mPresenter==null){
                mPresenter=initPresenter();
            }
    
            mPresenter.attachMView((V) this);
        }
    
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mPresenter!=null){
                mPresenter.detachMView();
            }
        }
        
        // 初始化presenter
        protected abstract P initPresenter();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    登录为例
    LoginView
    public interface LoginView extends BaseView {
    
        void onResultSuccess(User user);
    
        void onResultFail(String errorMessage);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    LoginPresenter
    public class LoginPresenter extends BasePresenter<LoginView> {
    
    
        public void request_login(String name, String pwd) {
            NetWorkManager.getInstance().getApiService().login(name, pwd)
                    .compose(SchedulerProvider.getInstance().applySchedulers())
                    .subscribe(new Observer<ApiResponse<User>>() {
                        @Override
                        public void onSubscribe(@NonNull Disposable d) {
                              if (getMView()!=null){
                                  getMView().showLoading();
                              }
                        }
    
                        @Override
                        public void onNext(@NonNull ApiResponse<User> userApiResponse) {
                            if (getMView()!=null){
                                getMView().hideLoading();
                                if (userApiResponse.getErrorCode()==0){
                                    getMView().onResultSuccess(userApiResponse.getData());
                                }else {
                                    getMView().showMessage(userApiResponse.getErrorMsg());
                                }
                            }
                        }
    
                        @Override
                        public void onError(@NonNull Throwable e) {
                            if (getMView()!=null){
                                getMView().hideLoading();
                                getMView().onResultFail(e.getMessage());
                            }
                        }
    
                        @Override
                        public void onComplete() {
    
                        }
                    });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    LoginActivity
    public class LoginActivity extends BaseActivity<LoginView,LoginPresenter> implements LoginView{
    
        private EditText etName;
        private EditText etPassword;
        private Button btn_login;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
    
            initView();
        }
    
        @Override
        protected LoginPresenter initPresenter() {
            return new LoginPresenter();
        }
    
        private void initView() {
            etName = findViewById(R.id.etName);
    
            etPassword = findViewById(R.id.etPassword);
    
            btn_login = findViewById(R.id.btn_login);
            btn_login.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    login();
                }
            });
        }
    
        private void  login(){
            String username=etName.getText().toString();
            String passWord=etPassword.getText().toString();
            mPresenter.request_login(username,passWord);
        }
    
        @Override
        public void onResultSuccess(User user) {
              showMessage(user.toString());
        }
    
        @Override
        public void onResultFail(String errorMessage) {
    
        }
    
        @Override
        public void showLoading() {
    
        }
    
        @Override
        public void hideLoading() {
    
        }
    
        @Override
        public void showMessage(String message) {
            Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    以上就是以登录为例MVP例子。完整代码

    效果图:
    在这里插入图片描述

    MVVM

    MVVM 通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念。

    模型

    在这里插入图片描述

    • View

    主要是指Activity或者Fragment,负责页面的展示以及View的变化,不参与任何逻辑和数据的处理。

    • ViewModel

    主要负责业务逻辑和数据处理,本身不持有View层引用。

    • Model

    实体类JavaBean。主要负责从本地数据库或者远程服务器来获取数据。

    优缺点

    优点
    • 双向绑定技术,当Model变化时,View-Model会自动更新,View也会自动变化。
    • View的功能进一步的强化,具有控制的部分功能。
    缺点
    • 数据绑定增加Bug调试难度
    • 数据双向绑定不利于View重用

    小结

    MVVM的本质是数据驱动,把解耦做的更彻底,viewModel不持有view。View产生事件,使用 ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知View更新界面,而不是主动调用View的方法。

    MVVM例子

    采用google官方推荐的MVVM 框架:
    在这里插入图片描述

    View 层:BaseActivity

    在BaseActivity 主要处理了初始化viewModel, viewModel 和lifecycle 生命周期绑定 等工作。

    open class BaseActivity <VM : BaseViewModel>: AppCompatActivity(){
    
        lateinit var mViewModel: VM
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            initViewModel()
            createObserve()
        }
    
        /** 提供编写LiveData监听逻辑的方法 */
        open fun createObserve() {
            mViewModel.apply {
                exception.observe(this@BaseActivity) {
                    LogUtil.e("网络请求错误:${it.message}")
                    when (it) {
                        is SocketTimeoutException -> ToastUtil.showShort(
                            this@BaseActivity,
                            "网络超时"
                        )
                        is ConnectException, is UnknownHostException -> ToastUtil.showShort(
                            this@BaseActivity,
                            "网络连接异常"
                        )
                        else -> ToastUtil.showShort(
                            this@BaseActivity, it.message ?: "网络错误"
                        )
                    }
                }
    
                complete.observe(this@BaseActivity){
                    // todo 请求完成工作
                }
            }
        }
    
    
        open fun providerVMClass(): Class<VM>? = null
    
    
        private fun initViewModel() {
            providerVMClass()?.let {
                mViewModel = ViewModelProvider(this).get(it)
                lifecycle.addObserver(mViewModel)
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            lifecycle.removeObserver(mViewModel)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    providerVMClass()方法中通过BaseViewModel子类泛型类型参数获取Class,在通过 ViewModelProviders.of(this).get(it)实例化ViewModel。

    ViewModel 层:BaseViewModel
    open class BaseViewModel :ViewModel(),LifecycleObserver{
        /** 请求异常(服务器请求失败,譬如:服务器连接超时等) */
        val exception = MutableLiveData<Exception>()
        /** 请求完成 */
        val complete=MutableLiveData<Int>()
    
        /**
        * 启动协程,封装了viewModelScope.launch
        *
        * @param tryBlock try语句运行的函数
        *
        */
        fun launch(tryBlock: suspend CoroutineScope.() -> Unit) {
            // 默认是执行在主线程,相当于launch(Dispatchers.Main)
            viewModelScope.launch {
                try {
                    tryBlock()
                } catch (e: Exception) {
                    exception.value = e
                } finally {
                    complete.value=0
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    BaseViewModel 里主要做了协程请求数据状态封装。使用LiveData及时通知数据更新。

    Model(Repository) 层:BaseRepository

    主要是获取ApiService和网络请求订阅容器,方便管理网络请求。

    open class BaseRepository {
        suspend fun <T> apiCall(api: suspend () -> ApiResponse<T>): ApiResponse<T> {
            return withContext(Dispatchers.IO) { api.invoke() }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    以登录为例
    LoginViewModel
    class LoginViewModel :BaseViewModel() {
        // 获取 用户名
        val userName =ObservableField<String>()
        // 获取 密码
        val passWord = ObservableField<String>()
        // 登录结果liveData
        private var  loginResultData=MutableLiveData<ApiResponse<User>>()
        // 对外提供获取登录结果方法
        fun getLoginResult()=loginResultData
    
        fun login(userName:String,pwd:String)=launch{
           // 网络请求
            val loginResult=ApiManager.login(userName,pwd)
            // 传递登录结果值
            loginResultData.postValue(loginResult)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    LoginViewModel中持有数据观察容器LiveData和真正发起网络请求动作,在接收到服务端返回的数据通过loginResultData.postValue(loginResult)通知Observer数据的更改,此处需注意的是,setValue方法只能在主线程中调用,postValue可以在任何线程中调用,如果是在后台子线程中更新LiveData的值,必须调用postValue。

    LoginActivity
    class LoginActivity : BaseActivity<LoginViewModel>() {
    
        private lateinit var mBinding: ActivityLoginBinding
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // 使用 DataBinding 双向绑定
            mBinding=DataBindingUtil.setContentView<ActivityLoginBinding>(this,R.layout.activity_login)
            initView()
            // DataBinding 关联ViewModel
            mBinding.viewModel=mViewModel
        }
    
        override fun providerVMClass(): Class<LoginViewModel>? {
            return LoginViewModel::class.java
        }
    
        private fun initView() {
            mBinding.btnLogin.setOnClickListener {
                // 发起登录请求
                mViewModel.login(mViewModel.userName.get()!!,mViewModel.passWord.get()!!)
            }
             // 登录结果
            mViewModel.getLoginResult().observe(this){
                ToastUtil.showShortInCenter(this@LoginActivity, it.errorMsg)
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    LoginActivity的工作是UI初始化,发请网络请求以及数据观察更新UI。

    activity_login
    
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
    
            <variable
                name="viewModel"
                type="com.xf.mvvmexample.ui.LoginViewModel" />
        data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="15dp"
                android:layout_marginTop="30dp"
                android:layout_marginEnd="15dp"
                android:hint="请输入名字"
                android:text="@={viewModel.userName}"
                android:textSize="15sp" />
    
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="15dp"
                android:layout_marginTop="30dp"
                android:layout_marginEnd="15dp"
                android:inputType="textPassword"
                android:hint="请输入密码"
                android:text="@={viewModel.passWord}"
                android:textSize="15sp" />
    
            <Button
                android:id="@+id/btn_login"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_marginTop="50dp"
                android:textColor="@color/white"
                android:text="登录"
                android:layout_marginEnd="15dp"
                android:layout_marginStart="15dp" />
    
        LinearLayout>
    layout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    使用DataBinding进行数据和UI双向绑定,及时获取数据。以上就是MVVM架构,实现登录功能。完整代码

    效果:
    在这里插入图片描述

  • 相关阅读:
    计算机毕业设计springboot+vue基本微信小程序的智慧社区防疫服务系统
    薪资17K,在字节外包工作是一种什么体验...
    【设计模式】单例模式
    odoo关于计算字段store=True时导致的安装/更新时间较长问题的解决方案
    Java笔记——网络原理03
    BCC源码内容概览(4)
    Dockerfile 安装python3.7到tensorflow1.15.0镜像中
    MySQL针对数据库-基本操作
    Vue2.0 组件components
    git的使用
  • 原文地址:https://blog.csdn.net/xufei5789651/article/details/126485379