浅析MVC、MVP、MVVM 架构

MVC

模型

浅析MVC、MVP、MVVM 架构_第1张图片

  • 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);
}

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;
    }
}

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();
}

登录为例

LoginView
public interface LoginView extends BaseView {

    void onResultSuccess(User user);

    void onResultFail(String errorMessage);
}
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() {

                    }
                });
    }
}
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();
    }

}

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

效果图:
浅析MVC、MVP、MVVM 架构_第2张图片

MVVM

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

模型

浅析MVC、MVP、MVVM 架构_第3张图片

  • 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 框架:
浅析MVC、MVP、MVVM 架构_第4张图片

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

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

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

以登录为例

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

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

}

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>

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

效果:
浅析MVC、MVP、MVVM 架构_第5张图片

你可能感兴趣的:(AndRoid,基础与进阶,mvc,mvp,mvvm)