Android架构之MVC,MVP,MVVM解析

news2024/10/7 3:28:48

MVC架构

View:Acitivity(View)、Fragment(View)视图,在android里xml布局转成View后,加载到了Activity/Fragment里了。
Controller:Controller对应着Activity/Fragment,绑定UI,处理各种业务。
Model:网络请求数据的获取,数据库存储,更新

1.View 接受用户的请求,然后将请求传递给 Controller
2.Controller 进行业务逻辑处理后,通知 Model去更新(调用Model的相关方法去进行网络请求或者数据库获取数据)
3.Model 数据更新后,通知 View 去更新界面显示(通过接口回调)

举个例子

Model层代码

class UserModel {

    /**
     * 进行登录操作
     */

    private val api by lazy {
        API()
    }

    private val random:Random= Random()

    fun doLogin(callback: OnDoLoginStateChange, account: String, password: String){

        callback.onLoading()

        //开始去调用登录的API
        //api.login

        //有结果,此操作为耗时操作
        //0..1
        val randomValue = random.nextInt(2)

        if(randomValue==0){
            callback.onLoginSuccess()
        }else{
            callback.onLoginFailed()
        }
    }

    fun checkUserState(account: String,block:(Int)->Unit){
        //0表示该账号已经注册
        //1表示该账号没有注册
        block(random.nextInt(2))
    }

    interface OnDoLoginStateChange{
        fun onLoading()

        fun onLoginSuccess()

        fun onLoginFailed()
    }

}

Controller层和View层代码

class MainActivity : AppCompatActivity(), UserModel.OnDoLoginStateChange {

    private val userModel by lazy{
        UserModel()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //设置点击事件
        initListener()
    }

    private fun initListener() {

        LoginBtn.setOnClickListener {
            //去进行登录
            toLogin()
        }


    }

    /**
     * 处理登录逻辑
     */
    private fun toLogin() {
        //做登录的逻辑处理
        val account:String =accountInputBox.text.toString()
        val password:String=passwordInputBox.text.toString()
        //检测账号格式是否正确
        if(TextUtils.isEmpty(account)){
            //提示账号有问题
            return
        }
        //检查密码长度是否正确
        if(TextUtils.isEmpty(password)){
            //提示密码有问题
            return
        }
        //给密码加密
        userModel.checkUserState(account){
            when(it){
                0->{
                    //可用,已经注册了
                    //进行登录,此操作为异步
                    userModel.doLogin(this,account,password)

                    //禁止按钮可以点击,防止重复提交
                    LoginBtn.isEnabled=false

                }
                1->{
                    //表示没有注册,不可用
                    //给出提示
                }
            }
        }
    }

    override fun onLoading() {
        loginTipsText.text="登录中..."

    }

    override fun onLoginSuccess() {
        loginTipsText.text="登录成功..."
    }

    override fun onLoginFailed() {
        loginTipsText.text="登录失败..."
    }
}

Controller和View都在MainActivity。
在MainActivity中的Controller部分toLogin()方法中,调用了userModel.doLogin方法,Controller是持有userModel的引用,所以Controller指向Model。
在调用userModel.doLogin方法的时候靠的是callback接口回调进行通知登录状态,所以Model层也依赖于Controller层的接口。
userModel.doLogin方法执行完后,我们还对LoginBt按钮状态进行设置,因此Controller持有View的引用,Controller指向View。
我们在给LoginBtn设置点击事件实现方法的时候,View也依赖于Controller中给的接口。
View并没有直接持有Model,是通过Controller来操作Model,所以View不依赖于Model。
Model可以通知我们的View进行更新,比如userModel.checkUserState()方法中,会通过接口回调重写的三个状态onLoading(),onLoginSuccess()和onLoginFailed()更新UI。
在这里插入图片描述
优点
view与model隔离,view换了,model不影响。model换其他的数据源,view层也不受影响。一个view可以连接多个model,有些model可以复用。比如说你这个页面需要用户信息,另外一个界面也需要用户信息。

缺点
这里我们主要指Android上的缺点,不适合在Android开发上使用。在Android开发中,View的相关内容和Controller都写到一起了,会让Activity/Fragment越来越臃肿。
在MVC中,Model层处理完数据后,直接通知View层更新,因此MVC耦合性强。

MVP架构

Presenter负责处理业务逻辑
View负责处理界面逻辑
Model负责处理数据操作

在MVP架构中,我们通常涉及到的内容是调用逻辑层的方法和更新UI
问题点就是:怎么调用逻辑层的方法呢?怎么通知UI更新呢

View层持有Presenter层的引用或者通过管理类管理Presenter,总之View可以直接拿到Presenter,这样子,View就可以调用Presenter里的方法了,比如说Presenter层去给我获取这个页面的分类信息。

Presenter层如何去更新UI呢?

当View层去获取/创建Presenter的时候,把接口给到Presenter(因为View层实现了接口),比如说Presenter层获取到分类接口以后,通知接口回调到View层更新UI。
这样子,我们这个来回就完事了。Presenter层再通过Model层去拿数据。

为什么使用MVP架构,有什么好处?

  • 分离了视图逻辑(View)和业务逻辑(Presenter),降低了耦合
  • Activity只处理生命周期的任务,使得Activity中的代码变得更加简洁
  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高了代码的可阅读性
  • Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
  • 把业务逻辑抽象到Presenter中去,而Presenter通常与Activity与Fragment的生命周期进行绑定。当Activity或Fragment销毁时,Presenter也会被销毁,从而释放资源。这样可以避免Presenter持有Activity或Fragment的引用而导致内存泄露。

MVP架构的整个核心流程:

View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。Presenter中同时持有View层的interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,通知Presenter层加载数据,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层在调用View层的接口将加载后的数据展示给用户。

在这里插入图片描述

Model层

/**
 * Model层进行数据处理并回调返回给View进行数据更新
 */
class UserModel {


    companion object{
        const val STATE_LOGIN_LOADING=0
        const val STATE_LOGIN_SUCCESS=1
        const val STATE_LOGIN_FAILED=2
    }



    /**
     * 进行登录操作
     */

    private val api by lazy {
        API()
    }


    private val random:Random= Random()

    fun doLogin( account: String, password: String,block: (Int) -> Unit){

        block(STATE_LOGIN_LOADING)

        //开始去调用登录的API
        //api.login

        //有结果,此操作为耗时操作
        //0..1
        val randomValue = random.nextInt(2)

        if(randomValue==0){
            block(STATE_LOGIN_SUCCESS)
        }else{
            block(STATE_LOGIN_FAILED)
        }
    }

    fun checkUserState(account: String,block:(Int)->Unit){
        //0表示该账号已经注册
        //1表示该账号没有注册
        block(random.nextInt(2))
    }


}

View层

class LoginActivity : AppCompatActivity(), LoginPresenter.OnDoLoginStateChange,
    LoginPresenter.OnCheckUserNameStateResultCallback {

    private val loginPresenter by lazy {
        LoginPresenter()
    }

    private var isUserNameCanBeUse=false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //设置点击事件
        initListener()
    }

    private fun initListener() {

        LoginBtn.setOnClickListener {
            //去进行登录
            toLogin()
        }
            //监听内容变化
        accountInputBox.addTextChangedListener(object :TextWatcher{
            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            }

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            }

            override fun afterTextChanged(p0: Editable?) {
                //检查当前账号是否有注册
                loginPresenter.checkUserNameState(p0.toString(),this@LoginActivity)

            }

        })
    }

    /**
     * 处理登录逻辑
     */
    private fun toLogin() {
        //做登录的逻辑处理
        val account:String =accountInputBox.text.toString()
        val password:String=passwordInputBox.text.toString()
        //给密码加密
        if(!isUserNameCanBeUse){
            //提示用户说当前用户名已经被注册了
            return
        }
        loginPresenter.doLogin(account,password,this)
    }

    override fun onAccountFormatError() {
        loginTipsText.text="账号不可为空..."
    }

    override fun onPasswordEmpty() {
        loginTipsText.text="密码不可为空..."
    }

    override fun onLoading() {
        loginTipsText.text="登录中..."

    }

    override fun onLoginSuccess() {
        loginTipsText.text="登录成功..."
    }

    override fun onLoginFailed() {
        loginTipsText.text="登录失败..."
    }

    override fun onNotExist() {
        //用户名不可用
        loginTipsText.text="该用户名已经注册了"
        isUserNameCanBeUse=false
    }

    override fun onExist() {
        //用户名可用
        loginTipsText.text="该用户名可以使用"
        isUserNameCanBeUse=true
    }
}

Presenter层

/**
 * Presenter层
 */
class LoginPresenter {
    private val userModel by lazy {
        UserModel()
    }

    fun checkUserNameState(account: String, callback: OnCheckUserNameStateResultCallback) {

        userModel.checkUserState(account) {
            when (it) {
                0 -> {
                    callback.onExist()
                }
                1 -> {
                    callback.onNotExist()
                }
            }
        }

    }


    interface OnCheckUserNameStateResultCallback {

        fun onNotExist()

        fun onExist()
    }

    fun doLogin(userName: String, password: String, callback: OnDoLoginStateChange) {
        //检测账号格式是否正确
        if (TextUtils.isEmpty(userName)) {
            //提示账号有问题
            callback.onAccountFormatError()
            return
        }
        //检查密码长度是否正确
        if (TextUtils.isEmpty(password)) {
            //提示密码有问题
            callback.onPasswordEmpty()
            return
        }

        userModel.doLogin(userName, password) {
            when (it) {
                STATE_LOGIN_LOADING -> {
                    callback.onLoading()
                }
                STATE_LOGIN_SUCCESS -> {
                    callback.onLoginSuccess()
                }
                STATE_LOGIN_FAILED -> {
                    callback.onLoginFailed()
                }
            }
        }
    }


    interface OnDoLoginStateChange {


        fun onAccountFormatError()

        fun onPasswordEmpty()

        fun onLoading()

        fun onLoginSuccess()

        fun onLoginFailed()
    }

}

View层中LoginActivity 持有Presenter层中的loginPresenter。View层指向Presenter层
Presenter层中doLogin方法中需要callback进行回调更新LoginActivity 中的数据更新,而callback需要View层的引用。所以Presenter层指向View层。
Presenter层持有UserModel的引用,去获取相应的0,1数字代表的账号信息是否注册,Presenter层指向Model层。
Model层当接收到数据更新时,再通过block函数回调以Lambda表达式将数据传递到LoginPresenter层。
在这里插入图片描述
优点

  • 剥离了View和Controller,解决了复杂的业务Activity过于庞大的问题

缺点

  • 更新UI需要注意线程,UI控件是否已经销毁(在用户可视的生命周期范围内更新UI即可)
    假如我们去请求一个数据,这个时候请求是耗时的,数据回来了,可是界面已经被用户关掉了,数据回来以后,我们得判断UI控件是否还存在。

  • 如果多个地方使用到同一个Presenter,可能会存在一些用不上的接口。比如说我们在做音乐播放器的时候,播放器的逻辑层PlayerPresenter,我们对应需要通知UI的接口有开始播放,暂停播放,缓冲中,播放失败,下一首,上一首,播放模式改变…
    如果我们多个地方使用到这个Presenter。比如说首页,详情页面,播放器页面。播放器页面使用这个Presenter实现这个接口没问题,都用得上。可是我的首页,只需要暂停播放和播放呀,其他的状态并不需要。这就会造成接口浪费。

MVVM架构

MVVM(Model-View-ViewModel) 是一种高级项目架构模式。主要分为3种部分:
Model是数据模型部分
View是界面展示部分,一般是Activity/Fragment
ViewModel可以理解成一个连接数据模型和界面展示的桥梁,从而让业务逻辑和界面展示分离,可以将相关逻辑放在其中处理。通过Jetpack的另外一个组件LiveData。LiveData可以使得ViewModel中的数据变化后,通知到View,它起到一个桥梁的作用。本质上这是一个观察者模式。通过DataBinding框架实现View层和Mode 层的双向绑定。
优点:

  • View和Model双向绑定,一方的改变都会影响另一方,开发者不用再去手动修改UI的数据。不需要findViewByld也不需要butterknife,不需要拿到 具体的View去设置数据绑定监听器等等,这些都可以用DataBinding完成。

  • View和Model的双向绑定是支持生命周期检测的,不会担心页面销毁了还有回调发生,这个由lifeCycle完成。Lifecycle 会自动将其关联的回调进行清理,以避免内存泄漏和不必要的资源消耗。

  • 不会像MVC一样导致Activity中代码量巨大,也不会像MVP一样出现大量的View和Presenter接口,项目结构更加低耦合。

缺点:
由于数据和视图的双向绑定,导致出现问题时不好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。
在这里插入图片描述
我们将UI控制层包含了我们平时写的Activity、Fragment、布局文件等与界面相关的东西。ViewModel层用于持有和UI元素相关的数据,以保证这些数据在旋转屏幕的时候不会丢失,并且还要提供接口给UI层调用以及和仓库层进行通信。仓库层主要的工作是判断调用方请求的数据应该是从本地数据源获取还是从网络数据源中获取,还是从网络数据源获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。
选择:
1、对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
2、对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。

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

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

相关文章

python接口自动化(三十)--html测试报告通过邮件发出去——中(详解)

简介 上一篇,我们虽然已经将生成的最新的测试报告发出去了,但是MIMEText 只能发送正文,无法带附件,因此我还需要继续改造我们的代码,实现可以发送带有附件的邮件。发送带附件的需要导入另外一个模块 MIMEMultipart。还…

java版电子招标采购系统源码之电子招标采购实践与展望-招标采购管理系统

统一供应商门户 便捷动态、呈现丰富 供应商门户具备内外协同的能力,为外部供应商集中推送展示与其相关的所有采购业务信息(历史合作、考察整改,绩效评价等),支持供应商信息的自助维护,实时风险自动提示。…

springboot+MySQL实现4S店车辆管理系统

本系统为了数据库结构的灵活性所以打算采用MySQL来设计数据库,而java技术,B/S架构则保证了较高的平台适应性。本文主要介绍了本系统的开发背景,所要完成的功能和开发的过程,主要说明了系统设计的重点、设计思想。

计算机体系结构基础知识介绍之使用多问题和静态调度来利用 流水线

为了提高处理器的性能,我们需要让每个时钟周期内发出多条指令,而不是只发出一条。这种多发射处理器有三种主要类型: 1. 静态调度的超标量处理器 2. VLIW(非常长指令字)处理器 3. 动态调度的超标量处理器。 这三种类型的…

lua 请求ftp服务器数据,下载文件

1、装入ftp库 2、调用ftp的get()方法 3、get()方法参数格式: 4、将返回到的数据写入文件中 例如,本次获取专利数据系统 http://patdata1.cnipa.gov.cn/ 的ftp站点数据 local ftp require("socket.ftp")--此处我没填端口号 file,err ftp.g…

findfont: Font family ‘Times New Roman‘ not found.

问题 Linux 使用 matplotlib.pyplot 画图时为了使字体和英文论文中的 Times of Roman 一致,通常会用到如下文本格式 font1 {family: Times New Roman, # x and y labelsweight: normal,size: 16}但在实际使用时会出现如下报警信息: findfont: Font …

element ui 导入模块的封装

导入组件的封装 <template><Modal :visible"visible" title"导入" onSave"onSave" onCancal"closeDialog"><template #default><el-upload ref"upload" class"upload-demo"action"ht…

Scala中使用 break 和 continue

Scala中没有 break 和 continue 关键字&#xff0c;但是我们可以用 Breaks 类提供的相应方法来实现对应功能。 在Java中&#xff0c;break continue return的区别 1、break&#xff1a;break不仅可以结束其所在的循环&#xff0c;还可结束其外层循环&#xff0c;但一次只能结束…

Vulnhub: Hackable:II靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.142 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.142 网站的files目录 ftp存在匿名登录&#xff0c;所在目录为网站的files目录 ftp上传反弹shell 提权 目标根目录下的.ru…

预付费智能水表远程控制系统

预付费智能水表远程控制系统是一种基于物联网技术的智能水表管理系统&#xff0c;它通过远程通信技术和云计算平台&#xff0c;实现了对水表的实时监控、数据采集、费用计算、远程控制等功能。该系统不仅可以提高水务公司的管理效率&#xff0c;还可以为用户提供更加便捷、可靠…

[疑难杂症2023-004]停止服务器自动启动的服务,解决端口占用的问题

本文由Markdown语法编辑器编辑完成。 1. 背景 前段时间&#xff0c;在linux上启动一个目录下的docker-compose.yml中的服务时&#xff0c;遇到了一个3000端口被占用的问题. 凭借经验&#xff0c;一般可能是之前的服务没被正常的停止掉&#xff0c;导致该服务占用的端口未被释…

阿里云——网站建设:动态网站建设(知识点)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 写在前面 课程目标 一.简单搭建动态网站 1.网站搭建类型 &#xff08;1&#xff0…

eclipse4.2.1 juno install SWT 1.7

SWT-windowbuilder-1.7-eclipse4.2.1-juno windows WB_v1.7.0_UpdateSite_for_Eclipse4.2.zip

【ESP32-CAM】20元就能搭建简易Web摄像头

图文步骤 在首选项中&#xff0c;增加网址https://dl.espressif.com/dl/package_esp32_index.json 安装esp32资源包 选择ESP32-CAM开发板 选一个USB-TTL的串口工具&#xff0c;按照图示&#xff0c;RX接U0TXD&#xff0c;TX接U0RXD&#xff0c;GND和5V供电&#xff0c;其中G…

3 2^k-分位数组

作者: 赵晓鹏时间限制: 1S章节: 递归与分治 #include <iostream> #include <algorithm> #include <queue> #include <tuple> #include <stack> #pragma GCC optimize(1) #pragma GCC optimize(2) #pragma GCC optimize(3,"Ofast",&quo…

【UGUI学习笔记】渲染层级

文章目录 Camera渲染Canvas渲染Sorting LayerOrder in Layer 图层渲染 Camera渲染 摄像机的渲染层级需要修改Clear Flags属性&#xff0c;这个属性下的四种模式比较复杂&#xff0c;此处只是介绍不同渲染方式下进行的选择。 摄像机的默认模式是Skybox&#xff0c;也就是视距内…

Acwing 850. Dijkstra求最短路 II

Acwing 850. Dijkstra求最短路 II 链接:850. Dijkstra求最短路 II - AcWing题库 /* 题解:堆优化版本的dijkstra 就是优化了 每次寻找当前距离源最近的点的时间 有小顶堆来维护当前为确定的点和源的距离 那么每次找最新确定的点的时候就是堆顶 */ #include<iostream> #i…

阐述kubernetes部署:基础设施安装

基础设施部署 持久卷的建立 请参考&#xff1a;《持久卷的建立》 elasticsearch部署 一、设置远程扩展字典 不使用自定义字典请忽略此步骤 首先更改ES中IK插件的配置&#xff1a; vi/opt/kubernetes/es/IKAnalyzer.cfg.xml 按您的实际设置的秘钥配置secret_value&#xff1a; …

使用echarts+echarts-gl绘制3d地图,实现地图轮播效果

记录一下大屏开发中使用到的echarts-gl大屏的页面根据需求前前后后改了几个版本了&#xff0c;地图的样式也改了又改这里记录一下&#xff0c;因为echarts属性用到的比较多也比较杂&#xff0c;防止以后需要用到忘记了 目录 初始效果 效果图&#xff1a; 适应大屏风格的发光…

SpringBoot项目模块间通信的两种方式

说明&#xff1a;在微服务架构开发中&#xff0c;一个请求是通过模块之间的互相通信来完成的&#xff0c;如下面这个场景&#xff1a; 创建两个子模块&#xff1a;订单模块&#xff08;端口8081&#xff09;、用户模块&#xff08;端口8082&#xff09;&#xff0c;两个模块之…