Android 架构模式之 MVVM

news2024/9/25 3:27:46

Android 架构

  1. Android 架构模式之 MVC
  2. Android 架构模式之 MVP
  3. Android 架构模式之 MVVM

目录

  • Android 架构
  • 架构设计的目的
  • 对 MVVM 的理解
  • 代码
    • Model
    • View
    • ViewModel
  • Android 中 MVVM 的问题
  • 试吃个小李子
    • Bean
    • Model
    • View
    • ViewModel
    • 效果展示

大家好!

作为 Android 程序猿,你熟悉 MVVM 架构吗。学过了 MVC 架构、MVP 架构,为什么还要继续 MVVM 架构?又是什么原因导致它让人又爱又恨?

架构设计的目的

通过设计使程序模块化,模块内 高内聚、模块间 低耦合,提高开发效率,便于复用及后续维护。

对 MVVM 的理解

MVVM 架构图

上图是 MVVM 的架构图,我们都知道,MVVM架构中 M 代表 Model(模型)、V 代表 View(视图)、VM 代表 ViewModel(视图模型)。它们的职责分别是:

  1. View 负责接收用户的输入事件,然后将事件传递给 ViewModel;
  2. ViewModel 收到事件后,会进行业务分发,通知 Model 获取数据;
  3. Model 区分数据来源,进而通过不同渠道获取数据,拿到数据后返回给 ViewModel;
  4. ViewModel 进行后续处理,或者通知 View 更新 UI。

如果有看过 MVP 架构,会感觉这两个是一样的,不用怀疑,就是一样的,还有 MVC 也是一样的,因为这些都是从 MVC 演变过来的,只是每次演变都是为了解决特定的问题,区别就是实现方式不一样了,MVVM 变成了基于数据驱动。
由于 MVVM 是基于 DataBinding 进行数据双向绑定,来实现的 View 和 Model 的数据同步,这种方式增强了 xml 的能力,使得 Activity/Fragment 可以专职维护 View 的初始化,同时也减少了不少编码任务,这也体现了框架的强大之处。但是这里我们仅引入 DataBinding 库,以最少的引入,来了解 MVVM 架构的思路,至于那些常用的开发库,他们只是在 MVVM 架构的基础之上帮我们大大提高了开发效率、规避可能存在的问题风险。

代码

Model

BaseModel.java

public abstract class BaseModel {

}

View

BaseActivity.java

public abstract class BaseActivity<B extends ViewDataBinding, VM extends BaseViewModel> extends AppCompatActivity {

    protected B mBinding;
    protected VM mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.mViewModel = createViewModel();
        setVariable();
    }

    /**
     * 初始化 ViewModel
     * @return
     */
    public abstract VM createViewModel();

    /**
     * 初始化 xml 中定义的变量
     */
    public abstract void setVariable();

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mViewModel != null) {
            mViewModel.onDestroy();
            mViewModel = null;
        }
    }
}

ViewModel

BaseViewModel.java

public abstract class BaseViewModel<M extends BaseModel> {

    protected M mModel;

    public BaseViewModel() {
        this.mModel = createModel();
    }

    protected abstract M createModel();

    public void onDestroy() {
        if (mModel != null) {
            mModel = null;
        }
    }
}

上述代码中可以看到,View 中持有 ViewModel 引用,ViewModel 中持有 Model 引用,持有顺序为正向顺序,然后通过 setVariable 函数将 View 和 ViewModel 进行关联,关联后就会通过 DataBinding 在框架层进行数据绑定,代码很简洁,职责分配的也很清楚。

Android 中 MVVM 的问题

不幸的是,在 MVVM 架构中一旦出现了问题,会是噩梦般的存在,很难发现原因,甚至没有提示,所以我们在编写代码的时候务必勤于调试,完成一个小功能点就看一下效果,免得写了很多功能,最后一片红,会无从下手。
ViewModel 中会定义大量的数据绑定对象,以及 getter/setter 方法,会导致 ViewModel 越来越臃肿,可以考虑进一步提取操作。

试吃个小李子

点击按钮,请求 wanandroid 网站的 banner 接口数据,将最后一条数据展示到UI
将 显示控件/输入控件 绑定到同一个 Bean 上,查看数据绑定的效果

代码结构

MVVM 架构的 Demo 是从 MVP 架构那套代码变更过来的,只涉及上述几个文件的变动,文件数/代码量都大大减少了,这里多了一个 IUpdateListener,主要用于定义数据更新的接口,Bean 中会实现更新接口,也可以不带它。

Bean

继承自 BaseObservable,是被观察者角色,View 充当观察者。
在需要关注的属性的 getter/setter 上通过 @Bindable 和 notifyPropertyChanged(BR.xx) 进行绑定

IUpdateListener.java

public interface IUpdateListener<T> {
    /**
     * 获取到新数据后,用于更新与 xml 绑定的实体类的属性值
     * @param t
     */
    void update(T t);
}

Banner.java

public class Banner extends BaseObservable implements IUpdateListener<Banner> {

    private String desc;
    private int id;
    private String imagePath;
    private int isVisible;
    private int order;
    private String title;
    private int type;
    private String url;

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }

    public int getIsVisible() {
        return isVisible;
    }

    public void setIsVisible(int isVisible) {
        this.isVisible = isVisible;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Bindable
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
        notifyPropertyChanged(BR.title);
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public void update(Banner banner) {
        setDesc(banner.desc);
        setId(banner.id);
        setImagePath(banner.imagePath);
        setIsVisible(banner.isVisible);
        setOrder(banner.order);
        setTitle(banner.title);
        setType(banner.type);
        setUrl(banner.url);
    }

    @Override
    public String toString() {
        return "Banner{" +
                "desc='" + desc + '\'' +
                ", id=" + id +
                ", imagePath='" + imagePath + '\'' +
                ", isVisible=" + isVisible +
                ", order=" + order +
                ", title='" + title + '\'' +
                ", type=" + type +
                ", url='" + url + '\'' +
                '}';
    }
}

Model

请求接口

MainModel.java

public class MainModel extends BaseModel {

    public void getNetworkBanner(ResponseCallback<List<Banner>> callback) {
        // 收到需求,请求接口数据
        NetworkRepository.getInstance().requestBanners(callback);
    }
}

View

Button,点击请求接口数据
TextView,用于回显数据
EditText,用于查看数据绑定 UI 效果
注:xml 的变动是很重要的部分,它的功能增强了很多

activity_main.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>

        <import type="com.villen.mvvm.MainViewModel" />

        <import type="com.villen.mvvm.bean.Banner" />

        <variable
            name="vm"
            type="MainViewModel" />

        <variable
            name="banner"
            type="Banner" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <Button
            android:id="@+id/btn_banner_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{(view) -> vm.getNetworkBanner()}"
            android:text="@string/get_network_info" />

        <EditText
            android:id="@+id/et_banner_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="@string/hint_change_data"
            android:text="@={banner.title}" />

        <TextView
            android:id="@+id/tv_banner_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{banner.title}" />

    </LinearLayout>
</layout>

MainActivity.java

public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));
        setContentView(mBinding.getRoot());
        super.onCreate(savedInstanceState);
    }

    @Override
    public MainViewModel createViewModel() {
        return new MainViewModel();
    }

    @Override
    public void setVariable() {
        mBinding.setVm(mViewModel);
        mBinding.setBanner(mViewModel.getBanner());
    }
}

ViewModel

业务处理

MainViewModel.java

public class MainViewModel extends BaseViewModel<MainModel> {

    private Banner mBanner;
    /**
     * 获取实体类对象,用于 xml 中数据绑定
     */
    public Banner getBanner() {
        if (mBanner == null) {
            mBanner = new Banner();
        }
        return mBanner;
    }

    @Override
    protected MainModel createModel() {
        return new MainModel();
    }

    /**
     * 获取 banner 数据
     */
    public void getNetworkBanner() {
        if (mModel == null) {
            return;
        }
        // 收到新需求,分发给 model 处理
        mModel.getNetworkBanner(new ResponseCallback<List<Banner>>() {
            @Override
            public void onSuccess(List<Banner> banners) {
                if (banners != null && banners.size() > 0) {
                    mBanner.update(banners.get(2));
                }
            }

            @Override
            public void onFail(String msg) {
                Log.e("network", msg);
            }
        });
    }
}

效果展示

效果展示

附上源码链接

致谢:
感谢 wanandroid 提供的开放API

参考:
Android DataBinding 从入门到进阶,看这一篇就够

写在最后:
很荣幸成为一名 Android 程序猿,虽然不是一名合格的猿。一路走来磕磕绊绊,借此感谢帮助过我的人,感谢指点、感恩遇见!

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

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

相关文章

大数据ETL工具(Sqoop, DataX, Kettle)对比

文章目录 1. ETL简介2. Sqoop2.1 Sqoop简介2.2 Sqoop主要特点 3. DataX3.1 DataX简介3.2 DataX框架设计3.3 DataX的主要特点 4. Kettle4.1 Kettle简介4.2 Kettle的主要特点 5. 工具对比5.1 DataX 与 Sqoop对比5.2 DataX 与 Kettle 6. 总结 1. ETL简介 ETL&#xff08;Extract-…

Mamba来搞图像增强了!高创新,发小论文不愁!

用Mamba做图像增强是个创新性比较高的方向&#xff0c;因为Mamba拥有非常独特的架构设计&#xff0c;能够同时捕获全局和局部的信息&#xff0c;轻松助力模型理解图像的整体结构和上下文&#xff0c;帮助我们确保图像细节的准确恢复和增强。 这种优势让它在保持高效计算的同时…

MySQL主从复制重新初始化单表或者单库的方法

作者介绍&#xff1a;老苏&#xff0c;10余年DBA工作运维经验&#xff0c;擅长Oracle、MySQL、PG、Mongodb数据库运维&#xff08;如安装迁移&#xff0c;性能优化、故障应急处理等&#xff09; 公众号&#xff1a;老苏畅谈运维 欢迎关注本人公众号&#xff0c;更多精彩与您分享…

【C++ Primer Plus习题】3.7

问题: 解答: #include <iostream> using namespace std;const float GALLO_TO_LITRE 3.785; const float KM_TO_MILE 62.14;int main() {float litre 0;float gallo 0;float mile 0;cout << "请输入汽车油耗(每100km消耗的汽油量单位为升):";cin &…

C#下在派生类中引发基类事件的方法与示例

文章目录 基类事件在派生类中的定义及触发方式基类事件的传播机制示例总结 在面向对象编程中&#xff0c;继承是代码复用的一种重要方式。C#作为一种面向对象的编程语言&#xff0c;允许派生类继承基类的属性和方法。基类定义了一系列共有的属性和行为&#xff0c;而派生类则可…

【UE】尝试一种老派的平面假反射做法,与进一步改进效果的思路

在实践中&#xff0c;常常需要为类似荧幕&#xff0c;LED广告牌等平面制作反射。 但会遇到各种问题&#xff0c;例如在使用屏幕空间反射时&#xff0c;平面必须在画面内 平面反射捕获与光线追踪又代价高昂 因此&#xff0c;在一些情况下依然会使用一种历史悠久的反射手法 这种…

树(二叉树)

树 1.1 特性 1.1.1 什么是树 树(Tree)是(n>0)个节点的有限集合T&#xff0c;它满足两个条件&#xff1a; (1) 有且仅有一个特定的称为根&#xff08;Root&#xff09;的节点。 其余的节点可以分为m&#xff08;m≥0&#xff09;个互不相交的有限集合T1、T2、……、Tm&#x…

【Docker】Linux系统以及威联通QNAP部署思源笔记的通用教程

本文首发于 ❄️慕雪的寒舍 本文测试的是旧版本v2.11.4的部署方式&#xff0c;实测当前&#xff08;2024.08.15&#xff09;最新的v3.1.3版本也可以用相同的方式部署。本文的部署方式共写了三种&#xff0c;非qnap的linux系统也可以参考本文部署思源笔记。 阅读本文之前&#…

SpringBoot 集成积木报表

SpringBoot 集成积 前言 积木报表是jeecg的一款开源但代码不开源的一款自定义报表&#xff0c;可以基于网页灵活 调整报表的布局、样式等内容&#xff0c;无需编程&#xff0c;专为企业数据分析、报表制作而设计&#xff1b; 降低管理人员汇总制作报表的门槛&#xff0c;解决…

在表格上,按照单元格数值显示单元格背景进度条

想要实现的效果如下&#xff1a; 单元格背景进度条的大小取决于当前单元格里的数值 TreeList和GridControl的设置方法都是相同的&#xff1a;都是通过给列设置FormatRule来实现的。 相关代码及设置如下&#xff1a; 1、给控件绑定数据源&#xff0c;我的数据源是一个DataTab…

25届网安秋招,信息泄露常问之配置信息泄露

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

基于STM32开发的智能花园灌溉系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化土壤湿度监测与处理灌溉控制与状态指示Wi-Fi通信与远程监控应用场景 家庭花园智能灌溉农业田地的智能灌溉管理常见问题及解决方案 常见问题解决方案结论 1. 引言 随着智能家居技术…

录屏神器!一键搞定视频录制,小白也能轻松上手

在工作当中录制会议内容或者看电影录制精彩瞬间、学习时录制网课的重点部分等等都是需要借助可以实现屏幕录制的工具&#xff0c;让我们的日常更加精彩并且有回忆的记录&#xff0c;今天就来给大家整理了四款好用的录屏工具&#xff0c;实现保存高清、流畅的电脑屏幕的精彩记录…

重定向

重定向原理 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> int main() { close(1); int fd open("myfile", O_WRONLY|O_CREAT, 00644); if(fd < 0){ perror("o…

V-ASSISTANT软件无法设置V90伺服软限位

使用V-ASSISTANT软件配置V90伺服参数时&#xff0c;软限位功能是灰色且未勾选&#xff0c;无法设置软限位&#xff0c;如下图所示&#xff1a; 原因&#xff1a;设置零点位置之后需要在PLC中激活软限位&#xff08;如FB284中ConfigEPOS管脚的Bit2&#xff09; FB284中的Confi…

python学习之路 - pyecharts快速入门

目录 一、pyecharts入门1、pyecharts模块介绍a、概况 2、pyecharts基础入门&#xff08;以折线图为例&#xff09;a、安装依赖b、创建折线图c、常用配置项 3、pyecharts创建柱状图a、创建基本柱状图b、创建反转柱状图c、创建含有时间线的柱状图 4、pyecharts地图可视化a、生成中…

WSL-ubuntu下载安装配置cudnn

下载 安装cuDnn的话需要和CUDA版本对应&#xff0c;可参考官网&#xff1a; cuDNN Archive | NVIDIA Developer 我的cuda是11.8 这个cuDNN8.9.7_Linux直接下载&#xff1a; https://developer.nvidia.com/downloads/compute/cudnn/secure/8.9.7/local_installers/11.x/cudn…

无人机技术的最新进展及未来趋势

一、飞行控制技术的提升 复杂环境下的稳定性&#xff1a;现代无人机在飞行控制系统方面的升级&#xff0c;使其能在复杂环境中稳定飞行&#xff0c;例如强风条件下或狭窄空间内。 智能避障系统&#xff1a;新型无人机配备有高精度的传感器和先进的算法&#xff0c;能够实现自…

vue3快速入门(一)新建项目与安装插件

步骤很详细&#xff0c;直接上教程 在对应路径下&#xff0c;cmd输入npm create vuelatest,然后按图所示进行选择&#xff08;仅供新手参考&#xff09; 安装以下插件 3.重启vscode&#xff08;为了确保插件生效&#xff09; 4.在vscode启动内置终端 输入npm i或pnpm i安装依赖…

浅谈 mysql 单、双引号的3种用法

mysql 单引号和双引号的使用&#xff0c;主要有以下3种情形 1、在引用字符型或日期类型的值时使用。 2、使用 as 创建别名时使用&#xff0c;别名如含特殊字符&#xff0c;则必须使用单引号或双引号。 3、解决数据原文中存在的单双引号问题。 注&#xff1a;本文所指的单、…