Android中的MVC、MVP、MVVM架构你清楚不?(附实现代码)

news2024/9/29 18:41:21

01 架构介绍

先来看一下MVC、MVP、MVVM的架构图。

从这些架构图中,可以看到每种架构都有3个模块以及数据流动方向箭头。

模块

在系统架构中,首先要做的就是把系统整体按照一定的原则划分成模块。

数据流动

模块划分之后,模块之间的通信,就是数据的流动。在Android中,流动数据包括两部分,事件和数据。

架构

模块和模块之间的数据通信方式构成不同的架构。在这3种架构中,都是把系统整体划分成了3个模块:视图层,数据层,业务层。 他们之间的区别在于,模块之间的通信方式(数据流动方向)不一致。

  • MVC是视图层接收到事件后调用到业务层处理业务逻辑,业务层调用数据层处理数据,数据层再调用视图层更新页面。
  • MVP是视图层接收到事件后调用到业务层处理,业务层调用数据层处理数据,数据层处理数据后回调给业务层,业务层再回调给视图层更新页面。(数据层已不再持有视图层,他们之间通过业务层(Presenter)交互,具体使用接口实现,使数据层和视图层解耦。
  • MVVM在MVP的基础上实现了视图层和业务层的双向数据绑定(data binding),不再通过接口的方式交互,ViewModel不在和Presenter一样持有视图层,使视图层和业务层解耦。

02 具体实现

MVC

视图层:在MVC架构中, Android的xml布局文件和Activity/Fragment文件被划分为View视图层。 因为xml作为视图层功能太弱,只能够实现页面的布局,不能够实现页面数据和事件的处理。需要和Activity一起才能够构成一个完整的视图层。

业务层:大多数的MVC架构开发的安卓项目, 并没有把Controller业务层独立出来,而是将业务层也在Activity/Fragment中实现。这导致了Activity/Fragment的代码非常臃肿,这就是MVC的缺点之一。 在本例中,我们会将业务层独立出来,实现一个标准的MVC架构。

数据层:数据层Model指的是,数据管理模块,这包括了数据的获取,处理。存储等。 MVP、MVVM的架构中的Model也是一样。后面不再赘述。

代码结构

xml代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_gallery_outer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

    <EditText
        android:id="@+id/tv_account"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="20dp"
        android:layout_marginRight="16dp"
        android:gravity="center"
        android:hint="输入用户名" />

    <EditText
        android:id="@+id/tv_pwd"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:gravity="center"
        android:hint="输入密码" />

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:gravity="center"
        android:text="登录" />

</LinearLayout>

Activity代码

public class MVCActivity extends AppCompatActivity {

    TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
         tvResult = findViewById(R.id.tv_result);
        TextView tvAccount = findViewById(R.id.tv_account);
        TextView tvPwd = findViewById(R.id.tv_pwd);
        Button btnLogin = findViewById(R.id.btn_login);

        MVCController mvcController = new MVCController();

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mvcController.login(tvAccount.getText().toString(),tvPwd.getText().toString(), MVCActivity.this);
            }
        });
    }

    public void loginSuccess(){
        tvResult.setText("登录结果:登录成功");
    }

    public void loginFail(){
        tvResult.setText("登录结果:登录失败");
    }

}

Controller代码

public class MVCController {

    MVCModel mvcModel;

    public MVCController() {
        mvcModel = new MVCModel();
    }

    public void login(String account, String pwd, MVCActivity loginActivity) {
        mvcModel.login(account, pwd, loginActivity);
    }
}

Model代码

public class MVCModel {

    public void login(String account, String pwd, MVCActivity loginActivity){

        if (account == null || account.length()==0) {
            loginActivity.loginFail();
        }

        if (pwd == null || pwd.length()==0) {
            loginActivity.loginFail();
        }

        if ("user123".equals(account) && "pwd123".equals(pwd)){
            loginActivity.loginSuccess();
        }

    }

}

实现代码说明

在Activity中监听登录按钮的事件,接收到事件之后,调用Controller的登录方法处理登录逻辑,在Controller的登录方法中调用Model请求网络数据(这里是模拟)判断是否登录成功,Model拿到登录结果后,调用Activity的方法刷新页面数据,展示登录结果。

优缺点

优点:通过划分模块的方式,将系统分成了3个模块,视图层,业务层和数据层。 代码开发实现不再是只在一个代码文件中,一定程度便于程序开发。

缺点:但是三个模块之间还存在很强的耦合关系。 不利于业务需求的更变和代码维护工作。

MVP

MVP架构是基于MVC的改进,将MVC的中Controller独立出来作为Presenter。 xml和Activity还是作为视图层, 视图层接收到页面数据,调用Presenter进行业务逻辑处理,Presenter调用Model进行数据处理,Model回传数据给Presenter,Presenter回传数据给View。数据的回传通过接口回调的方式来实现。

代码结构

IModel接口代码

public interface IModel {
    public boolean login(String account, String pwd);
}

IView接口代码

public interface IView {
    public void loginSuccess();
    public void loginFail();
}

Activity代码

public class MVPActivity extends AppCompatActivity implements IView {

    TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        tvResult = findViewById(R.id.tv_result);

        TextView tvAccount = findViewById(R.id.tv_account);
        TextView tvPwd = findViewById(R.id.tv_pwd);
        Button btnLogin = findViewById(R.id.btn_login);

        MVPPresenter presenter = new MVPPresenter();
        presenter.setiView(this);
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.login(tvAccount.getText().toString(), tvPwd.getText().toString());
            }
        });
    }

    public void loginSuccess() {
        tvResult.setText("登录结果:登录成功");
    }

    public void loginFail() {
        tvResult.setText("登录结果:登录失败");
    }
}

Model代码

public class MVPModel implements IModel {
    public boolean login(String account, String pwd) {

        if (account == null || account.length() == 0) {
            return false;
        }

        if (pwd == null || pwd.length() == 0) {
            return false;
        }

        if ("user123".equals(account) && "pwd123".equals(pwd)) {
            return true;

        }
        return false;
    }
}

Presenter代码

public class MVPPresenter {

    MVPModel model;

    public MVPPresenter() {
        model = new MVPModel();
    }

    IView iView;

    public void setiView(IView iView) {
        this.iView = iView;
    }

    public void login(String account, String pwd) {
        boolean loginResult = model.login(account, pwd);

        if (loginResult){
            iView.loginSuccess();
        }else {
            iView.loginFail();
        }
    }
}

实现代码说明

定义了两个接口,IView和IModel, Activity和Model分别实现了这两个接口。 在Presenter中持有这两个实例。Presenter调用Model处理数据后,通过Iview的接口方法回调给Activity刷新页面。

优缺点

从上面的代码可以看到,三个模块之间的通信是通过接口实现的,在实际开发,定义的接口和方法会非常多。 导致很简单的一个页面功能也需要实现多个接口和方法。

优点就是通过Presenter,把MVC中的Controller代码抽出来了,并且Presenter作为View和Model通信的桥梁,完成了Model和View的解耦。

MVVM

MVVM在MVP的基础上加入了双向绑定,使View能够感知ViewModel中的数据变化,ViewModel能够感知View数据的变化。

代码结构

xml代码

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.domain.android.study.notes.architecture.mvvm.MVVMViewModel" />

    </data>

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ll_gallery_outer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_result"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.result}"
            android:layout_gravity="center" />

        <EditText
            android:id="@+id/tv_account"
            android:layout_width="match_parent"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_height="40dp"
            android:hint="输入用户名"
            android:gravity="center"
            android:text="@={viewModel.account}"

            android:layout_gravity="center"
            android:layout_marginTop="20dp" />

        <EditText
            android:id="@+id/tv_pwd"
            android:layout_width="match_parent"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_height="40dp"
            android:hint="输入密码"
            android:text="@={viewModel.pwd}"
            android:gravity="center"
            android:layout_gravity="center" />

        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_height="40dp"
            android:text="登录"
            android:gravity="center"
            android:layout_gravity="center" />

    </LinearLayout>
</layout>

Activity代码

public class MVVMActivity extends AppCompatActivity {

    MVVMViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
        viewModel = ViewModelProviders.of(this).get(MVVMViewModel.class);
        binding.setVariable(BR.viewModel, viewModel);
        binding.setLifecycleOwner(this);
        binding.btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewModel.login();
            }
        });

    }

}

ViewModel代码

public class MVVMViewModel  extends ViewModel {

    public ObservableField<String> account = new ObservableField<>("");
    public ObservableField<String> pwd = new ObservableField<>("");
    public ObservableField<String> result = new ObservableField<>("");

    MVVMModel mvvmModel;
    public MVVMViewModel() {
         mvvmModel = new MVVMModel();
    }

    public void login(){
        boolean loginResult = mvvmModel.login(account.get(), pwd.get());
        result.set(loginResult ? "登录结果:成功" :"登录结果:失败");
    }

}

Model代码

public class MVVMModel {
    public boolean login(String account, String pwd) {
        if (account == null || account.length() == 0) {
            return false;
        }

        if (pwd == null || pwd.length() == 0) {
            return false;
        }
        if ("user123".equals(account) && "pwd123".equals(pwd)) {
            return true;
        }
        return false;
    }
}

注意

在本例MVVM架构实现中,用到了Android提供的data binding这个数据双向绑定框架。需要在APP模块的gralde文件中添加以下配置开启:

 android {
 ...
 dataBinding {
        enabled true
    }
  ...
    }

实现代码说明

通过Android提供的数据双向绑定库data binding 将Acitvity/xml视图层与ViewModel绑定。在xml布局文件中,通过@{}来表示单向绑定或者@={}来表示双向绑定。Activity接受到视图层的登录点击事件后,调用ViewModel处理登录业务逻辑,ViewModel通过双向数据绑定拿到到视图层输入的账号密码数据,调用Model处理数据,Model处理数据后,回传给ViewModel, ViewModel的数据改变,View感知后刷新页面。

注意

data binding通过观察者模式实现。 内部具体实现也是通过调用notify通知数据变化给观察者,notify调用了观察者实现的接口方法。

优缺点

优点:经过数据双向绑定之后,我们不在需要想MVP中写那么多接口回调方法区实现视图层和业务层的交互。业务层也不再持有视图层的引用。

缺点:通过这种方式进行数据双向绑定后,xml中会多出一些标签、表达式、甚至和业务有点的简单计算代码。这不利于业务的逻辑的查看。并且由于双向绑定是data binding实现的。在这个过程中, 如果出现bug导致数据没有被感知改变,不方便排错,因为xml不能debug调试。

03 总结

MVC、MVP、MVVM大体上都是把系统划分成3个模块:视图层、业务层、数据层。 但是他们的通信方式、数据流动方向不一致,形成了不同的架构。 其后面产生的架构都是为了更好的解耦,解决已有架构的不足。每个架构都有自己的优缺点,没有最好的架构,只有最合适的架构。


Android 知识点归整

Android 性能调优系列https://qr18.cn/FVlo89
Android 车载学习指南https://qr18.cn/F05ZCM
Android Framework核心知识点笔记https://qr18.cn/AQpN4J
Android 音视频学习笔记https://qr18.cn/Ei3VPD
Jetpack全家桶(含Compose)https://qr18.cn/A0gajp
Kotlin 入门到精进https://qr18.cn/CdjtAF
Flutter 基础到进阶实战https://qr18.cn/DIvKma
Android 八大知识体系https://qr18.cn/CyxarU
Android 中高级面试题锦https://qr18.cn/CKV8OZ

后续如有新知识点,将会持续更新,尽请期待……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工程监测多通道振弦模拟信号采集仪VTN的MODBUS 通讯协议

工程监测多通道振弦模拟信号采集仪VTN的MODBUS 通讯协议 在 MODBUS 协议下&#xff0c;所有寄存器被定义为“保持寄存器” &#xff08;详见 MODBUS 通讯协议标准说明&#xff09;&#xff0c; 设备支持基于 MODBUS 协议的多个连续寄存器读取、单个寄存器写入两种指令码&#x…

电液伺服阀控制器YY-100

供电电源&#xff1a; 24V DC(18&#xff5e;36V)&#xff1b; 控制输入&#xff1a; -10V&#xff5e;10V DC&#xff1b;最大输出&#xff1a; 70mA &#xff1b;增益 &#xff1a; 调节范围——1&#xff5e;40 mA&#xff08;出厂设置——4 mA&#xff09;&#xff1b; 偏置…

C语言从0到1算法小白训练营——day2

我们学习不仅仅是要把难的学会&#xff0c;也要注重基础&#xff0c;注重内功。 接下来我们继续先从基础知识开始&#xff1a; 1. 字符串字符常量注释 1.1 字符串 如&#xff1a;“abc” ①定义&#xff1a;由双引号引起来的一串字符称为字符串。 ②C语言规定&#xff0c;…

【计算机网络】P1 - 物理层

物理层大纲物理层基本概念数据通信基础两种入网方式传输过程源系统、传输系统与目的系统数据与信号信源、信宿与信道三种通信方式两种传输方式大纲 物理层基本概念 物理层解决如何在传输媒体上&#xff08;同轴电缆&#xff0c;光纤等&#xff09;上传输数据比特流。主要任务为…

detach,主线程终止后子线程会结束吗

此前&#xff0c;我对detach的理解是&#xff0c;当主线程退出后&#xff0c;子线程能够继续存在。实际上&#xff0c;当主线程退出后&#xff0c;子线程也随之结束了。先看一个例子&#xff1a; #include <iostream> #include <thread> #include <unistd.h>…

交叉编译 zlib

交叉编译 zlib 概述 zlib 被设计为一个免费的、通用的、不受法律约束的、即不受任何专利保护的无损数据压缩库&#xff0c;可在几乎任何计算机硬件和操作系统上使用。zlib 数据格式本身可以跨平台移植。与Unix 压缩和 GIF 图像格式中使用的 LZW 压缩方法不同&#xff0c;zlib …

RocketMq使用规范(纯技术和实战建议)

概述&#xff1a; 使用规范主要从&#xff0c;生产、可靠性、和消费为轴线定义使用规范&#xff1b;kafka使用核心&#xff1a;削峰、解耦、向下游并行广播通知&#xff08;无可靠性保证&#xff09;和分布式事务&#xff0c;本规范仅从削峰、解耦、向下游并行广播通知论述&am…

OceanBase 4.0解读:兼顾高效与透明,我们对DDL的设计与思考

关于作者 谢振江&#xff0c;OceanBase 高级技术专家。 2015年加入 OceanBase, 从事存储引擎相关工作&#xff0c;目前在存储-索引与 DDL 组&#xff0c;负责索引&#xff0c;DDL 和 IO 资源调度相关工作。 回顾关系型数据库大规模应用以来的发展&#xff0c;从单机到分布式无…

什么是BOM?与焊盘不匹配,怎么办?

什么是BOM&#xff1f; 简单的理解就是&#xff1a;电子元器件的清单&#xff0c;一个产品由很多零部件组成&#xff0c;包括&#xff1a;电路板、电容、电阻、二三极管、晶振、电感、驱动芯片、单片机、电源芯片、升压降压芯片、LDO芯片、存储芯片、连接器座子、插针、排母、…

成为IT服务台经理需要什么技能

要给员工带来愉快的体验&#xff0c;就必须对你的服务台进行有效的管理。为此&#xff0c;了解为什么服务台经理的角色对于绘制企业组织良好的服务台至关重要。在本指南中&#xff0c;我们将深入探讨他们的角色、能力和贡献&#xff0c;以了解如何顺利处理服务台操作。 IT 服务…

【面试题】前端 移动端自适应?

移动端 h5 开发中有一个绕不开的话题&#xff1a;移动端自适应方案。移动端的设备尺寸不尽相同&#xff0c;要把 UI 设计图较好地展示在移动端上&#xff0c;需要让 h5 页面能自适应设备尺寸。接下来将对移动端自适应的相关概念、方案和其他一些常见问题做个介绍。概念简介大厂…

什么是 Web3?解读未来的去中心化网络:The Decentralized Internet of the Future Explained

目录 互联网的演化 什么是 Web 1.0? 什么是 Web 2.0? Web 2.0 变现与安全性 什么是 Web 3.0? 原生支付 创立公司的新方式 Web3 中的身份 如果你读到这篇文章,那么你已经是当代互联网世界的一员了。我们现在使用的网络和10年前大不相同。所以,互联网是怎么演化的,…

Centos7 安装 Mysql 8.0.32,详细完整教程(好文章!!)

mysql5.7的安装方式参考之前的文章&#xff1a; centos7 安装 Mysql 5.7.27&#xff0c;详细完整教程&#xff08;好文章&#xff01;&#xff01;&#xff09;_HD243608836的博客-CSDN博客 一、检查mysql版本冲突 先检查是否已经存在mysql&#xff0c;若存在卸载&#xff0…

大数据第一轮复习笔记(2)

Spark ./spark-submit --class com.kgc.myspark01.WordCount --master yarn --deploy-mode cluster /opt/myspark01-1.0-SNAPSHOT.jar 1.Client向YARN的ResourceManager申请启动Application Master。Client中创建SparkContext同时初始化中将创建DAGScheduler和TASKScheduler…

固态继电器的五大优势

固态继电器的优点和五个关键优势&#xff0c;现代电气控制系统因二极管、晶体管和晶闸管等固态器件的发明而得到极大的增强。对于加热器和电机等大负载设备&#xff0c;固态继电器可能比传统的机械继电器具有巨大的优势。 虽然并非适用于所有情况&#xff0c;但它们具有许多吸引…

前端——周总结系列五

JS的Map对象 概述 ES6新增的一种数据结构Map&#xff0c;对操作键值对很友好&#xff0c;键值对集合&#xff0c;提供属性和方法供开发者使用。存有键值对&#xff0c;键可以是任何数据类型&#xff1b;按照原始插入顺序存储&#xff08;FIFO&#xff09;原则&#xff1b;具有…

关于ChatGPT,我们到底在担心什么?

“ChatGPT已对教育产生了巨大冲击” “ChatGPT对程序员造成了哪些影响” “ChatGPT会取代人类的哪些工作&#xff1f;” “谷歌宣布推出类ChatGPT产品Bard” “Bing新版本引入ChatGPT” …… 显然&#xff0c;在这段时间内&#xff0c;ChatGPT这个词已经触发了“全民焦虑”。 …

低代码平台调研

一、什么是低代码 首先&#xff0c;我们来看一下低代码的概念。在维基百科上&#xff0c;低代码是这样定义的&#xff0c;它的全称叫做低代码开发平台&#xff0c;它为开发者提供了一种创建应用软件的开发环境&#xff0c;可以通过图形化界面和参数配置的方式来代替传统的纯手…

协方差以及PCA

概念&#xff1a;协方差&#xff08;Covariance&#xff09;在概率论和统计学中用于衡量两个变量的总体误差。而方差是协方差的一种特殊情况&#xff0c;即当两个变量是相同的情况。协方差就是衡量两个变量相关性的变量。当协方差为正时&#xff0c;两个变量呈正相关关系&#…

我用vue开发了一个动态网站--百宝阁 万字长文(spa电商,首页没有做动态,搜索页是动态)

一、前言 学习前端已有大半年了&#xff0c;虽然其中备考软件设计师考试花了两个月&#xff0c;但我还是收获颇丰&#xff0c;从最开始的html,到css&#xff0c;js,在到es6&#xff0c;promise&#xff0c;ajax,node.js、vue、webpack我已经有较为靠谱的编码习惯&#xff0c;亲…