当前位置: 首页 > news >正文

Android 架构模式之 MVVM

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架构图。对比来看,我觉得官方给的没有体现出View在数据绑定这块的优势,更符合官方建议的 App 整体架构模型,所以就画了下面这个架构图。大同小异,只是View和ViewModel这块的区别,强化了 xml 的能力,ViewModel 只充当 控制器 角色,也体现了官方推荐的 数据驱动型 UI。

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;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);this.mViewModel = createViewModel();setVariable();}/*** 初始化 ViewModel* @return*/public abstract VM createViewModel();/*** 初始化 xml 中定义的变量*/public abstract void setVariable();@Overrideprotected 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;}@Bindablepublic 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;}@Overridepublic 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);}@Overridepublic 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" /><variablename="vm"type="MainViewModel" /><variablename="banner"type="Banner" /></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"tools:context=".MainActivity"><Buttonandroid: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" /><EditTextandroid: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}" /><TextViewandroid: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> {@Overrideprotected void onCreate(Bundle savedInstanceState) {mBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));setContentView(mBinding.getRoot());super.onCreate(savedInstanceState);}@Overridepublic MainViewModel createViewModel() {return new MainViewModel();}@Overridepublic 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;}@Overrideprotected MainModel createModel() {return new MainModel();}/*** 获取 banner 数据*/public void getNetworkBanner() {if (mModel == null) {return;}// 收到新需求,分发给 model 处理mModel.getNetworkBanner(new ResponseCallback<List<Banner>>() {@Overridepublic void onSuccess(List<Banner> banners) {if (banners != null && banners.size() > 0) {mBanner.update(banners.get(2));}}@Overridepublic void onFail(String msg) {Log.e("network", msg);}});}
}

效果展示

效果展示

附上源码链接

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

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

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • AWS CodeCommit 停服,欢迎大家使用极狐GitLab!
  • Scrapy入门学习
  • Windows IPv6漏洞CVE-2024-38063
  • spring boot 集成es使用
  • (十三)Flink SQL
  • 海南省政协主席李荣灿调研宇乐乐影业
  • 如何使用 Java 将 JSON 数据转换为 YAML 文件
  • python并发与并行(四) ———— 用queue来协调多个线程之间的工作进度
  • 【Qt】基于VTK9.1+VS2019+Qt5.15.2的点云可视化程序开发
  • 【C++ Primer Plus习题】6.9
  • Elasticsearch(面试篇)
  • 第三十九篇-TeslaP40+CosyVoice-安装
  • 云计算实训38——docker网络、跨主机容器之间的通讯
  • STM32(F103ZET6)第十九课:FreeRtos的移植和使用
  • SQLserver使用sql语句创建主键,外键,唯一约束,自增
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • HTTP 简介
  • iOS 颜色设置看我就够了
  • Java精华积累:初学者都应该搞懂的问题
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Python3爬取英雄联盟英雄皮肤大图
  • Quartz初级教程
  • Swoft 源码剖析 - 代码自动更新机制
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • Vue全家桶实现一个Web App
  • 笨办法学C 练习34:动态数组
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 从重复到重用
  • 干货 | 以太坊Mist负责人教你建立无服务器应用
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 解析带emoji和链接的聊天系统消息
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 深入浅出webpack学习(1)--核心概念
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 微信小程序--------语音识别(前端自己也能玩)
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • # .NET Framework中使用命名管道进行进程间通信
  • #vue3 实现前端下载excel文件模板功能
  • #我与Java虚拟机的故事#连载06:收获颇多的经典之作
  • (30)数组元素和与数字和的绝对差
  • (C++)八皇后问题
  • (WSI分类)WSI分类文献小综述 2024
  • (二十三)Flask之高频面试点
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (四)opengl函数加载和错误处理
  • (转)树状数组
  • (自用)仿写程序