Android 架构
- Android 架构模式之 MVC
- Android 架构模式之 MVP
- Android 架构模式之 MVVM
目录
- Android 架构
- 架构设计的目的
- 对 MVVM 的理解
- 代码
- Model
- View
- ViewModel
- Android 中 MVVM 的问题
- 试吃个小李子
- Bean
- Model
- View
- ViewModel
- 效果展示
大家好!
作为 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
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 程序猿,虽然不是一名合格的猿。一路走来磕磕绊绊,借此感谢帮助过我的人,感谢指点、感恩遇见!