Android 架构模式之 MVVM

打印 上一主题 下一主题

主题 830|帖子 830|积分 2490


  • Android 架构模式之 MVC
  • Android 架构模式之 MVP
  • Android 架构模式之 MVVM

  
各人好!
作为 Android 步伐猿,你认识 MVVM 架构吗。学过了 MVC 架构、MVP 架构,为什么还要继续 MVVM 架构?又是什么缘故原由导致它让人又爱又恨?
架构计划的目标

   通过计划使步伐模块化,模块内 高内聚、模块间 低耦合,提高开辟效率,便于复用及后续维护。
  对 MVVM 的理解


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

  • View 负责吸取用户的输入事件,然后将事件转达给 ViewModel;
  • ViewModel 收到事件后,会进行业务分发,通知 Model 获取数据;
  • Model 区分数据来源,进而通过不同渠道获取数据,拿到数据后返回给 ViewModel;
  • ViewModel 进行后续处理,或者通知 View 更新 UI。
  假如有看过 MVP 架构,会感觉这两个是一样的,不消怀疑,就是一样的,还有 MVC 也是一样的,因为这些都是从 MVC 演变过来的,只是每次演变都是为相识决特定的问题。
区别是实现方式不一样了,MVVM 架构是基于框架实现,变成了基于数据驱动。MVVM 是基于 DataBinding 框架进行数据 单向/双向 绑定,实现了 View 和 Model 的数据同步,这种方式增强了 xml 的能力,使得 Activity/Fragment 可以专职维护 View 的初始化,同时也减少了不少编码任务,这也体现了框架的强大之处。但是这里我们仅引入 DataBinding 库,以最少的引入,来相识 MVVM 架构的思绪,至于那些常用的开辟库,他们只是在 MVVM 架构的底子之上帮我们大大提高了开辟效率、规避可能存在的问题风险。
代码实现

Model

BaseModel.java
  1. public abstract class BaseModel {
  2. }
复制代码
View

BaseActivity.java
  1. public abstract class BaseActivity<B extends ViewDataBinding, VM extends BaseViewModel> extends AppCompatActivity {
  2.     protected B mBinding;
  3.     protected VM mViewModel;
  4.     @Override
  5.     protected void onCreate(@Nullable Bundle savedInstanceState) {
  6.         super.onCreate(savedInstanceState);
  7.         this.mViewModel = createViewModel();
  8.         setVariable();
  9.     }
  10.     /**
  11.      * 初始化 ViewModel
  12.      * @return
  13.      */
  14.     public abstract VM createViewModel();
  15.     /**
  16.      * 初始化 xml 中定义的变量
  17.      */
  18.     public abstract void setVariable();
  19.     @Override
  20.     protected void onDestroy() {
  21.         super.onDestroy();
  22.         if (mViewModel != null) {
  23.             mViewModel.onDestroy();
  24.             mViewModel = null;
  25.         }
  26.     }
  27. }
复制代码
ViewModel

BaseViewModel.java
  1. public abstract class BaseViewModel<M extends BaseModel> {
  2.     protected M mModel;
  3.     public BaseViewModel() {
  4.         this.mModel = createModel();
  5.     }
  6.     protected abstract M createModel();
  7.     public void onDestroy() {
  8.         if (mModel != null) {
  9.             mModel = null;
  10.         }
  11.     }
  12. }
复制代码
上述代码中可以看到,View 中持有 ViewModel 引用,ViewModel 中持有 Model 引用,持有顺序为正向顺序,然后通过 setVariable 函数将 View 和 ViewModel 进行关联,关联后就会通过 DataBinding 在框架层进行数据绑定,代码很简洁,职责分配的也很清楚。
Android 中 MVVM 的问题

不幸的是,在 MVVM 架构中一旦出现了问题,会是噩梦般的存在,很难发现缘故原由,以致没有提示,以是我们在编写代码的时候务必勤于调试,完成一个小功能点就看一下效果,免得写了很多功能,最后一路报错,那样在排错的时候会无从动手、毫无思绪。
ViewModel 中会定义大量的数据绑定对象,以及 getter/setter 方法,会导致 ViewModel 越来越臃肿,可以思量进一步提取操作。
Google 建议

   

  • 编写数据驱动型 UI
  • xml 与 Activity/Fragment 通过 databinding 进行关联
  • Activity/Fragment 尽可能保持精简
  • ViewModel 充当毗连器
  • 通过 协程 处理耗时操作
  • 尽可能利用 依赖项注入 最佳实践,主要是构造函数注入。
  注意事项

   

  • 避免 ViewModel 持有 任何与生命周期相关的范例的引用。如,Activity, Fragment, Context 或 Resources。假如某元素需要在 ViewModel 中利用 Context,则应重新严格评估其是否位于正确的层级中。
  • AndroidViewModel 中持有 applicationContext,但官方 建议 利用 ViewModel,而非 AndroidViewModel,不应在 ViewModel 中利用 Application。正确做法是将依赖项移至界面层或数据层。
  try a demo

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

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

   继承自 BaseObservable,是被观察者角色,View 充当观察者。
在需要关注的属性的 getter/setter 上通过 @Bindable 和 notifyPropertyChanged(BR.xx) 进行绑定
  IUpdateListener.java
  1. public interface IUpdateListener<T> {
  2.     /**
  3.      * 获取到新数据后,用于更新与 xml 绑定的实体类的属性值
  4.      * @param t
  5.      */
  6.     void update(T t);
  7. }
复制代码
Banner.java
  1. public class Banner extends BaseObservable implements IUpdateListener<Banner> {
  2.     private String desc;
  3.     private int id;
  4.     private String imagePath;
  5.     private int isVisible;
  6.     private int order;
  7.     private String title;
  8.     private int type;
  9.     private String url;
  10.     public String getDesc() {
  11.         return desc;
  12.     }
  13.     public void setDesc(String desc) {
  14.         this.desc = desc;
  15.     }
  16.     public int getId() {
  17.         return id;
  18.     }
  19.     public void setId(int id) {
  20.         this.id = id;
  21.     }
  22.     public String getImagePath() {
  23.         return imagePath;
  24.     }
  25.     public void setImagePath(String imagePath) {
  26.         this.imagePath = imagePath;
  27.     }
  28.     public int getIsVisible() {
  29.         return isVisible;
  30.     }
  31.     public void setIsVisible(int isVisible) {
  32.         this.isVisible = isVisible;
  33.     }
  34.     public int getOrder() {
  35.         return order;
  36.     }
  37.     public void setOrder(int order) {
  38.         this.order = order;
  39.     }
  40.     @Bindable
  41.     public String getTitle() {
  42.         return title;
  43.     }
  44.     public void setTitle(String title) {
  45.         this.title = title;
  46.         notifyPropertyChanged(BR.title);
  47.     }
  48.     public int getType() {
  49.         return type;
  50.     }
  51.     public void setType(int type) {
  52.         this.type = type;
  53.     }
  54.     public String getUrl() {
  55.         return url;
  56.     }
  57.     public void setUrl(String url) {
  58.         this.url = url;
  59.     }
  60.     @Override
  61.     public void update(Banner banner) {
  62.         setDesc(banner.desc);
  63.         setId(banner.id);
  64.         setImagePath(banner.imagePath);
  65.         setIsVisible(banner.isVisible);
  66.         setOrder(banner.order);
  67.         setTitle(banner.title);
  68.         setType(banner.type);
  69.         setUrl(banner.url);
  70.     }
  71.     @Override
  72.     public String toString() {
  73.         return "Banner{" +
  74.                 "desc='" + desc + '\'' +
  75.                 ", id=" + id +
  76.                 ", imagePath='" + imagePath + '\'' +
  77.                 ", isVisible=" + isVisible +
  78.                 ", order=" + order +
  79.                 ", title='" + title + '\'' +
  80.                 ", type=" + type +
  81.                 ", url='" + url + '\'' +
  82.                 '}';
  83.     }
  84. }
复制代码
Model

   哀求接口,获取 banner 数据
  MainModel.java
  1. public class MainModel extends BaseModel {
  2.     public void getNetworkBanner(ResponseCallback<List<Banner>> callback) {
  3.         // 收到需求,请求接口数据
  4.         NetworkRepository.getInstance().requestBanners(callback);
  5.     }
  6. }
复制代码
View

   包含一个 Button,点击哀求接口数据
包含一个 TextView,用于回显数据
包含一个 EditText,用于检察数据绑定 UI 效果
注:xml 的变动是很重要的部分,它的功能增强了很多
  activity_main.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     xmlns:tools="http://schemas.android.com/tools">
  5.     <data>
  6.         <import type="com.villen.mvvm.MainViewModel" />
  7.         <import type="com.villen.mvvm.bean.Banner" />
  8.         <variable
  9.             name="vm"
  10.             type="MainViewModel" />
  11.         <variable
  12.             name="banner"
  13.             type="Banner" />
  14.     </data>
  15.     <LinearLayout
  16.         android:layout_width="match_parent"
  17.         android:layout_height="match_parent"
  18.         android:gravity="center"
  19.         android:orientation="vertical"
  20.         tools:context=".MainActivity">
  21.         <Button
  22.             android:id="@+id/btn_banner_info"
  23.             android:layout_width="wrap_content"
  24.             android:layout_height="wrap_content"
  25.             android:onClick="@{(view) -> vm.getNetworkBanner()}"
  26.             android:text="@string/get_network_info" />
  27.         <EditText
  28.             android:id="@+id/et_banner_info"
  29.             android:layout_width="wrap_content"
  30.             android:layout_height="wrap_content"
  31.             android:hint="@string/hint_change_data"
  32.             android:text="@={banner.title}" />
  33.         <TextView
  34.             android:id="@+id/tv_banner_info"
  35.             android:layout_width="wrap_content"
  36.             android:layout_height="wrap_content"
  37.             android:text="@{banner.title}" />
  38.     </LinearLayout>
  39. </layout>
复制代码
MainActivity.java
  1. public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> {
  2.     @Override
  3.     protected void onCreate(Bundle savedInstanceState) {
  4.         mBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));
  5.         setContentView(mBinding.getRoot());
  6.         super.onCreate(savedInstanceState);
  7.     }
  8.     @Override
  9.     public MainViewModel createViewModel() {
  10.         return new MainViewModel();
  11.     }
  12.     @Override
  13.     public void setVariable() {
  14.         mBinding.setVm(mViewModel);
  15.         mBinding.setBanner(mViewModel.getBanner());
  16.     }
  17. }
复制代码
ViewModel

   通过 model 获取数据
通知 UI 更新
  MainViewModel.java
  1. public class MainViewModel extends BaseViewModel<MainModel> {
  2.     private Banner mBanner;
  3.     /**
  4.      * 获取实体类对象,用于 xml 中数据绑定
  5.      */
  6.     public Banner getBanner() {
  7.         if (mBanner == null) {
  8.             mBanner = new Banner();
  9.         }
  10.         return mBanner;
  11.     }
  12.     @Override
  13.     protected MainModel createModel() {
  14.         return new MainModel();
  15.     }
  16.     /**
  17.      * 获取 banner 数据
  18.      */
  19.     public void getNetworkBanner() {
  20.         if (mModel == null) {
  21.             return;
  22.         }
  23.         // 收到新需求,分发给 model 处理
  24.         mModel.getNetworkBanner(new ResponseCallback<List<Banner>>() {
  25.             @Override
  26.             public void onSuccess(List<Banner> banners) {
  27.                 if (banners != null && banners.size() > 0) {
  28.                     mBanner.update(banners.get(2));
  29.                 }
  30.             }
  31.             @Override
  32.             public void onFail(String msg) {
  33.                 Log.e("network", msg);
  34.             }
  35.         });
  36.     }
  37. }
复制代码
效果展示


附上源码链接
致谢:
感谢 wanandroid 提供的开放API
参考:
Android DataBinding 从入门到进阶,看这一篇就够

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

吴旭华

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表