浅谈 Android MVC MVP MVVM

491 阅读7分钟

前言:

各位同学大家好 有段时间没有给各位更新文章了, 具体多久我也不清楚 ,最近整理了一下andorid 开发中几种常用的代码架构模式 (这里要跟java的设计模式区分开, 是代码整体架构不是 传统java 23种设计模式)今天就写了一个简单例子分享给大家,那么废话不多说 我们正式开始。

具体使用场景

效果图

image.png

我们这边在输入框 输入我们要查询的账号 然后点击中间button 完成查询 ,然后将结果显示在屏幕上面的textview里面 (这里只是模式的查询效果)然后分析其功能在不同架构上面的实现方式

  • 无框架

image.png

具体代码实现(无框架)

package com.app.mvc_demo.normal;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.app.mvc_demo.R;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;
/***
 *
 *创建人:xuqing
 * 类说明:无架构实现
 *
 *
 */public class NormalActivity extends AppCompatActivity  implements View.OnClickListener{
    private EditText ed_account;
    private TextView text_account;
    private Button getacount_btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal);
        initview();
    }
  private void initview() {
            ed_account=findViewById(R.id.ed_account);
            text_account=findViewById(R.id.account_text);
            getacount_btn=findViewById(R.id.get_account_btn);
            getacount_btn.setOnClickListener(this);
        }
        private String  getUserInput(){
            return ed_account.getText().toString().trim();
        }
        private void  showSuccessPage(Account account){
            text_account.setText("用户账号:"+account.getName()+"用户等级"+account.getLevel());
        }

        private void  showFailedPage(){
            text_account.setText("获取用户数据失败");
        }

        private  void  getAccountData(String accountName, Mcallback mcallback){

            Random random=new Random();
            boolean isSuccess=random.nextBoolean();
            if(isSuccess){
                Account account=new Account();
                account.setLevel(100);
                account.setName(accountName);
                mcallback.onSuccess(account);
            }else{
                mcallback.onFailed();
            }

        }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case  R.id.get_account_btn:
                String userInput=getUserInput();
                getAccountData(userInput, new Mcallback() {
                    @Override
                    public void onSuccess(Account account) {
                        showSuccessPage(account);
                    }
                    @Override
                    public void onFailed() {
                        showFailedPage();
                    }
                });
        }

    }
}

我们观察整个实现的代码 ,我们可以看到为了实现上面的查询账户的功能 在无架构的情况下 我们把所有的逻辑代码都写在一个activity 里面 虽然功能可以实现,但其负担过重,代码复查时繁琐,一旦需要修改,复杂项目极难维护 ,所以一般实战项目开发我们非常不推荐这种做法 除非你只是一个简单的学习demo逻辑很少 可以简单实现。

  • mvc架构

image.png 在mvc 架构里面我们把这个项目分成 model view controller 三层 model 是我们获取数据的具体方法(mvcmodel )view就是我们的xml布局文件 controller 就是我们的activity fragment 等

具体代码实现(mvc 架构)

activity 代码

package com.app.mvc_demo.mvc;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.app.mvc_demo.R;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;

public class MvcActivity extends AppCompatActivity  implements View.OnClickListener{
    private EditText ed_account;
    private TextView text_account;
    private Button getacount_btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal);
        initview();
    }
    private void initview() {
        ed_account=findViewById(R.id.ed_account);
        text_account=findViewById(R.id.account_text);
        getacount_btn=findViewById(R.id.get_account_btn);
        getacount_btn.setOnClickListener(this);
    }
    private String  getUserInput(){
        return ed_account.getText().toString().trim();
    }
    private void  showSuccessPage(Account account){
        text_account.setText("用户账号:"+account.getName()+"用户等级"+account.getLevel());
    }
    private void  showFailedPage(){
        text_account.setText("获取用户数据失败");
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case  R.id.get_account_btn:
                String userInput=getUserInput();
               MvcModel.getAccountData(userInput, new Mcallback() {
                    @Override
                    public void onSuccess(Account account) {
                        showSuccessPage(account);
                    }
                    @Override
                    public void onFailed() {
                        showFailedPage();
                    }
                });
        }
    }
}

mvc model

package com.app.mvc_demo.mvc;
import com.ap.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;

public class MvcModel {
    public  static   void  getAccountData(String accountName, Mcallback mcallback){
        Random random=new Random();
        boolean isSuccess=random.nextBoolean();
        if(isSuccess){
            Account account=new Account();
            account.setLevel(100);
            account.setName(accountName);
            mcallback.onSuccess(account);
        }else{
            mcallback.onFailed();
        }
    }
}

我们看到在mvc 架构模式下我们将获取数据的逻辑 抽离到mvcModel 中实现 我们就会简化掉 activity中的代码逻辑 ,但是缺点仍然存在,在MVC框架下,虽然将获取数据与界面展示分割开来,但对于Controller层,仍然拥有很多权利,随着功能的增多,其代码量也将会大大增长,不利于维护修改。

  • MVP架构模式

image.png 在mvp架构中 我们把整个项目代码的逻辑分为 model view Presenter model model 是我们获取数据的具体方法 (mvpmodel )view 是我们的xml文件 原来在mvc 中的controller 处理繁杂的逻辑我们都放到 Presenter 中进行

具体代码(MVP)

package com.app.mvc_demo.mvp;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.app.mvc_demo.R;
import com.app.mvc_demo.bean.Account;


public class MvpActivity extends AppCompatActivity implements View.OnClickListener,IMVPView {
    private EditText ed_account;
    private TextView text_account;
    private Button getacount_btn;
    private  MVPPersenter mvpPersenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal);
        initview();
        mvpPersenter=new MVPPersenter(this);
    }
    private void initview() {
        ed_account=findViewById(R.id.ed_account);
        text_account=findViewById(R.id.account_text);
        getacount_btn=findViewById(R.id.get_account_btn);
        getacount_btn.setOnClickListener(this);
    }
    public String  getUserInput(){
        return ed_account.getText().toString().trim();
    }
    public void  showSuccessPage(Account account){
        text_account.setText("用户账号:"+account.getName()+"用户等级"+account.getLevel());
    }
    public void  showFailedPage(){
        text_account.setText("获取用户数据失败");
    }
    @Override
    public void onClick(View v) {
        String userInput=getUserInput();
         mvpPersenter.getData(userInput);
    }
}

mvpmodel

package com.app.mvc_demo.mvp;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;

public class MVPModel {
    public   void  getAccountData(String accountName, Mcallback mcallback){
        Random random=new Random();
        boolean isSuccess=random.nextBoolean();
        if(isSuccess){
            Account account=new Account();
            account.setLevel(100);
            account.setName(accountName);
            mcallback.onSuccess(account);
        }else{
            mcallback.onFailed();
        }
    }
}

IMVPView

IMvpview
package com.app.mvc_demo.mvp;
import com.app.mvc_demo.bean.Account;
public interface IMVPView {
    String  getUserInput();
    void  showSuccessPage(Account account);
    void  showFailedPage();
}

MVPPersenter

package com.app.mvc_demo.mvp;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;

public class MVPPersenter {
    private  IMVPView imvpView;
    private MVPModel mvpModel;

    public MVPPersenter(IMVPView imvpView) {
        this.imvpView = imvpView;
        mvpModel=new MVPModel();
    }
   public void   getData(String  accountName){
        mvpModel.getAccountData(accountName, new Mcallback() {
            @Override
            public void onSuccess(Account account) {
                imvpView.showSuccessPage(account);
            }
            @Override
            public void onFailed() {
                imvpView.showFailedPage();

            }
        });
   }
}

在mvp架构中 我们发现我们会在activtiy 直接操作那些逻辑 而是在MVPPersenter 中持有mvpmodel的引用然后在 MVPPersenter 的构造方法中传入 IMVPView的对象 我们在mvpModel 的回调方法里面在用imvpView 讲拿到的数据结果回调到activity 或者fragment 中,然后我们在activity 或者fragment中实现imvpvidew中方法即可获取数据结果

在使用MVP框架时,View层与Model层不通信,都通过 Presenter层传递,并且Presenter层与具体的View是没有直接关联的,而是通过定义好的接口进行交互,这就可能会导致有大量的接口生成,代码复杂繁琐,难维护。

  • 1 因此,在使用MVP时最好按照一定规范去做:
  • 2 接口规范化(封装父类接口以减少接口的使用量)
  • 3使用第三方插件自动生成MVP代码
  • 4 对于一些简单的界面,可以选择不使用框架
  • 5根据项目复杂程度,部分模块可以选择不使用接口

MVVM架构

image.png 在mvvm 架构中 我们将整个项目分为 model view viewmodel model model 是我们获取数据的具体方法 (mvpmodel ) view 是我们的xml文件 viewmodel 处理业务逻辑数据更新 MVVM框架实现了数据与视图的绑定(DataBinding),当数据变化时,视图会自动更新;反之,当视图变化时,数据会自动更新。

我们要使用mvvm 首先我们要学会使用 DataBinding

DataBinding 使用步骤

  • 1启用DataBinding
  • 2修改布局文件为DataBinding布局
  • 3数据绑定

启用DataBinding

image.png 在build.gradle里面添加依赖代码配置开启 databinding支持

    dataBinding {
        enabled = true
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

修改布局文件为DataBinding布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.app.mvc_demo.mvvm.MVVMViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".normal.NormalActivity">
        <EditText
            android:id="@+id/ed_account"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:hint="请输入要查询的账号">

        </EditText>
        <Button
            android:id="@+id/get_account_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获取账号信息"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="80dp"
            android:onClick="@{viewModel.getData}"
            >
        </Button>
        <TextView
            android:id="@+id/account_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:text="@{viewModel.result}"
            android:hint="账号信息未获取"
            >
        </TextView>
    </LinearLayout>
</layout>

我们在布局文件中鼠标放在最外层布局 然后按住alt+enter 键就可以修改为databinding布局 image.png

MVVM框架使用步骤:

  • 1提供View、ViewModel以及Model三层
  • 2 将布局修改为DataBinding布局
  • 3View与ViewMedel之间通过DataBinding进行通信
  • 4获取数据并展示在界面上 再更深层次学习,可以使用LiveData+ViewModel

具体代码实现(mvvm )

mvvmactivity

package com.app.mvc_demo.mvvm;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import com.app.mvc_demo.R;
import com.app.mvc_demo.databinding.ActivityMvvmBinding;
/***
 *
 * 创建人:xuqing
 * 类说明:mvvmactivity 
 *
 *
 */
public class MVVMActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmBinding  binding= DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        MVVMViewModel mvvmViewModel=new MVVMViewModel(getApplication(),binding);
        binding.setViewModel(mvvmViewModel);
    }

}
修改布局setContentView 为
 ActivityMvvmBinding  binding= DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
实例化mvvmmodel 并且set到databinding上面
MVVMViewModel mvvmViewModel=new MVVMViewModel(getApplication(),binding);
binding.setViewModel(mvvmViewModel);

MVVMModel

package com.app.mvc_demo.mvvm;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;

public class MVVMModel {

    public   void  getAccountData(String accountName, Mcallback mcallback){
        Random random=new Random();
        boolean isSuccess=random.nextBoolean();
        if(isSuccess){
            Account account=new Account();
            account.setLevel(100);
            account.setName(accountName);
            mcallback.onSuccess(account);
        }else{
            mcallback.onFailed();
        }
    }
}

xml 布局文件


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.app.mvc_demo.mvvm.MVVMViewModel" />

    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".normal.NormalActivity">


        <EditText
            android:id="@+id/ed_account"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:hint="请输入要查询的账号">

        </EditText>
        <Button
            android:id="@+id/get_account_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获取账号信息"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="80dp"
            android:onClick="@{viewModel.getData}"
            >

        </Button>
        <TextView
            android:id="@+id/account_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:text="@{viewModel.result}"
            android:hint="账号信息未获取"
            >
        </TextView>
    </LinearLayout>
</layout>

MVVMViewModel

package com.app.mvc_demo.mvvm;
import android.app.Application;
import android.util.Log;
import android.view.View;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import com.app.mvc_demo.databinding.ActivityMvvmBinding;

public class MVVMViewModel extends BaseObservable {
    private static final String TAG = "MVVMViewModel";
    private  MVVMModel mvvmModel;
    private ActivityMvvmBinding binding;
    private String  result;
    public  MVVMViewModel(Application application,ActivityMvvmBinding binding){
        mvvmModel=new MVVMModel();
        this.binding=binding;

    }
    public  void  getData(View view){
       String userInput = binding.edAccount.getText().toString();

        mvvmModel.getAccountData(userInput, new Mcallback() {
            @Override
            public void onSuccess(Account account) {
                String info=account.getName()+"+|"+account.getLevel();
                Log.e(TAG, "onSuccess: info "+info );
                setResult(info);
            }
            @Override
            public void onFailed() {
                setResult("获取数据失败");
            }
        });
    }
    @Bindable
    public String getResult() {
        return result;
    }
    public void setResult(String result) {
        this.result = result;
        notifyPropertyChanged(com.app.mvc_demo.BR.result);
    }

}

我们可以看到在使用了databinding布局之后 我们可以直接就在xml文件 view 调用viewmodel里面的方法 然后我们在viewmodel中持有mvvmmodel的引用 我们实现了mvvmmodel中方法 然后我们通过
notifyPropertyChanged 来刷新xml文件 view 中显示效果. 使用了mvvm框架+上databinding可以·直接view调用viewmodel中的方法 大大简化了我们activity的逻辑代码 但是有些必须在activity中实现的代码 例如申请权限之类我们可以通过 可以使用LiveData+ViewModel 去处理这个同学们可以自己去研究 我这边就不张开讲了

最后总结

在安卓开发中我们应该都有接触到这三方代码架构模式 个人觉得具体用那种代码架构看项目 个开发项目组 我是做游戏SDK开发 就一个 所以我个人在开发中 首选mvc 应为mvvm要引入很多三方库不友好 mvp要封装很多接口 项目本身逻辑少 所以我也不去用 要是开发app同学可以优先使用mvvm或者mvp 在代码整个解耦方面和后期维护 要比mvc要友好很多,最后希望我的文章能帮助到各位解决问题 ,以后我还会贡献更多有用的代码分享给大家。各位同学如果觉得文章还不错 ,麻烦给关注和star,小弟在这里谢过啦!

项目地址:

码云 :gitee.com/qiuyu123/mv…