Android 架构模式之 MVVM

Android 架构

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

目录

大家好!

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

架构设计的目的

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

对 MVVM 的理解

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

上图是 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

java 复制代码
public abstract class BaseModel {

}

View

BaseActivity.java

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

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

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

Banner.java

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

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

java 复制代码
<?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

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

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 从入门到进阶,看这一篇就够

相关推荐
雪碧聊技术23 天前
vue基础之3:模板语法、数据绑定
vue.js·v-model·数据绑定·v-bind·模板语法·插值语法·指令语法
无敌喜之郎2 个月前
Angular数据绑定详解
前端·javascript·angular·数据绑定
hcgeng3 个月前
Android DataBinding的使用
android·gitee·databinding
△曉風殘月〆5 个月前
WPF MVVM模式图片占用问题
wpf·mvvm·数据绑定·imagesource
tekin5 个月前
gin框架 POST 请求参数绑定 JSON数据ShouldBind 使用注意事项 - 结构体必须定义json标签
json·gin·数据绑定·gin框架·shouldbind
martian6655 个月前
学懂C#编程:常用框架学习(三)———学会并深入理解WPF核心之MVVM模式
学习·c#·wpf·mvvm·数据绑定
SAP-nkGavin6 个月前
SAPUI5基础知识9 - JSON Module与数据绑定
sap·sapui5·数据绑定·fiori
Amd7947 个月前
Vue.js条件渲染与列表渲染指南
性能优化·前端开发·状态管理·数据绑定·路由配置·列表渲染·vuejs
科学的发展-只不过是读大自然写的代码10 个月前
wpf 数据绑定 数据转换
ui·wpf·类型转换·数据绑定