前言
写Android也有一段时间了,始终没有找到一种优雅流畅的Android架构模式,前不久看了google关于mvp架构的范例,甚好,建议仔细观摩一下:https://github.com/googlesamples/android-architecture。这篇文章也是参考了google的写法加上自己的理解和实践写出来的,供大家参考参考,如有不当的地方欢迎指正。
什么是MVPVM?
MVPVM=Model+View+Presenter+ViewModel
在没有使用类似MVP架构的时候,逻辑一般都直接写在了Activity或者Fragment里,导致View层很臃肿,业务逻辑、UI操作和数据耦合到了一起,结构混乱。现在,View层要处理的逻辑全部委托给Presenter处理,View层专注实现UI,Presenter专注实现业务逻辑,它们之间通过View Interface和Presenter Interface交互。在google推出databinding后,View Interface的部分功能可以转移到ViewModel中去,进一步降低View层的臃肿。
- View层:实现View Interface,对外提供showDialog、showToast之类的方法
- ViewModel层:以databinding为基础,对外提供控制xml界面的方法
- Presenter层:实现Presenter Interface,处理业务逻辑
- Model层:服务器数据对应数据模型类
实践
把下面这个页面(用户信息页面)以MVPVM模式写出来
项目结构如下:
业务流程如下:
View层触发了一个请求用户信息的event,然后View层将这个event交给Presenter层来处理,Presenter向服务器请求数据,Presenter拿到数据(数据被封装到了model里)后进行逻辑处理,然后根据需求操纵ViewModel进行UI更新。
1.Model层——UserInfoModel
以后台返回数据格式是json为例,这里的Model层就是一一对应的后台返回的数据。
public class UserInfoModel implements Serializable {
/**
* head : string
* headBackground : string
* name : string
* sex : int 1:男 2:女
* nationality : int 1:中国 2:美国
* specialty : string
* advantage : string
* createTime : long
*/
@SerializedName("head")
private String head;
@SerializedName("headBackground")
private String headBackground;
@SerializedName("name")
private String name;
@SerializedName("sex")
private int sex;
@SerializedName("nationality")
private int nationality;
@SerializedName("specialty")
private String specialty;
@SerializedName("advantage")
private String advantage;
@SerializedName("createTime")
private long createTime;
//下面是各个字段的get和set方法,不列出来了
...
}
2.ViewModel层——UserInfoViewModel
ViewModel相当于操作xml的代言人,任何xml显示的更新都要通过ViewModel来进行,要注意在写ViewModel的时候要完全按照xml来写,比如一个TextView要显示和隐藏,我会在ViewModel里定义一个int字段来表示;TextView要显示内容,我会在ViewModel里定义一个String字段来表示。ViewModel和xml之间用databinding绑定起来,操作ViewModel就相当于操作xml。如果对databinding不熟悉的请参考databinding google官方文档(不用科学上网也能看哦,Google给中国开发者的福利):
https://developer.android.google.cn/topic/libraries/data-binding/index.html
public class UserInfoViewModel extends BaseObservable {
private int headBackgroundRes;
private int headImageRes;
private String name;
private String sex;
private String nationality;
private String specialty;
private String advantage;
private String createTime;
@Bindable
public int getHeadImageRes() {
return headImageRes;
}
public void setHeadImageRes(int headImageRes) {
this.headImageRes = headImageRes;
notifyPropertyChanged(BR.headImageRes);
}
@Bindable
public int getHeadBackgroundRes() {
return headBackgroundRes;
}
public void setHeadBackgroundRes(int headBackgroundRes) {
this.headBackgroundRes = headBackgroundRes;
notifyPropertyChanged(BR.headBackgroundRes);
}
...
}
看一下xml中如何使用ViewModel的:
可以发现大部分字段ViewModel和Model是能对上的,有的人会图简单把Model和ViewModel合二为一,不过不建议这么做,不要在Model或者ViewModel中写任何逻辑,因为ViewModel可以把前后端分离,也就是说,只要后台接口定好了,Presenter就可以使用Model、View和ViewModel将整个业务逻辑写完。即使最后后台字段变化,影响的也只是Presenter中的处理逻辑,如果将Model和ViewModel合在一起,当后台字段变化,除了修改Presenter、Model外,xml和ViewModel也得修改,得不偿失。
3.Presenter层——UserInfoPresenter
Presenter层是处理业务逻辑的核心,它处理由View层转移过来的事件,逻辑处理完毕后操作View和ViewModel更新UI。IUserInfo描述了Presenter和View层的接口定义,Presenter和View层就是通过这些接口进行交互的。
public interface IUserInfo {
interface IView {
void updateTitle(String title);//更新页面的标题
void showDialog(String content);//显示一个dialog
void closeDialog();//关闭dialog
}
interface IPresenter {
void onViewInit();//页面初始化后执行
}
}
Presenter和View层只关心对方提供了那些接口,而不关心对方的具体实现细节,我可以通过接口的不同实现来实现不同的UI和不同的业务逻辑。
public class UserInfoPresenter implements IUserInfo.IPresenter {
private IUserInfo.IView mView;
private UserInfoViewModel mViewModel;
public UserInfoPresenter(IUserInfo.IView iView, UserInfoViewModel viewModel) {
this.mView = iView;
this.mViewModel = viewModel;
}
@Override
public void onViewInit() {
mView.updateTitle("用户信息");
requestData();
}
private UserInfoModel mockTrump() {
UserInfoModel trump = new UserInfoModel();
trump.setHead("xxx.jpg");
trump.setHeadBackground("xxxx.jpg");
trump.setName("川普");
trump.setNationality(2);
trump.setSex(1);
trump.setSpecialty("表情包");
trump.setAdvantage("漂亮的女儿");
trump.setCreateTime(System.currentTimeMillis());
return trump;
}
private void requestData() {
mView.showDialog("正在获取数据");
//不建议这么用Handler,这里只是模拟网络请求的延迟
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mView.closeDialog();
updateUi(mockTrump());
}
}, 2000);
}
private void updateUi(UserInfoModel model) {
mViewModel.setName(model.getName());
mViewModel.setHeadImageRes(R.mipmap.ic_head);
mViewModel.setHeadBackgroundRes(R.mipmap.bg_trump);
String sex;
switch (model.getSex()) {
case 1:
sex = "男";
break;
case 2:
sex = "女";
break;
default:
sex = "不详";
}
mViewModel.setSex(sex);
String nationality;
switch (model.getNationality()) {
case 1:
nationality = "中国";
break;
case 2:
nationality = "美国";
break;
default:
nationality = "地球";
}
mViewModel.setNationality(nationality);
mViewModel.setAdvantage(model.getAdvantage());
mViewModel.setSpecialty(model.getSpecialty());
mViewModel.setAdvantage(model.getAdvantage());
mViewModel.setCreateTime(new SimpleDateFormat("yyyy.MM.dd HH:mm").format(new Date(model.getCreateTime())));
}
}
在页面初始化完成后控制权就已经由View转移到Presenter,Presenter根据逻辑执行了一次获取用户数据的网络请求,收到服务器返回的数据后通过ViewModel更新UI。
4.View层——UserInfoActivity
UserInfoActivity实现了IView接口,所以本例中View层就是UserInfoActivity,它初始化了Presenter和databinding,并持有Presenter的引用,当需要处理业务逻辑时会调用Presenter来处理。如在本例中,UserInfoActivity实现如何更新title,和如何显示和关闭一个dialog。
public class UserInfoActivity extends AppCompatActivity implements IUserInfo.IView {
ActivityUserInfoBinding binding;
UserInfoPresenter presenter;
ProgressDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_user_info);
UserInfoViewModel viewModel = new UserInfoViewModel();
presenter = new UserInfoPresenter(this, viewModel);
binding.setViewModel(viewModel);
presenter.onViewInit();
}
@Override
public void updateTitle(String title) {
this.setTitle(title);
}
@Override
public void showDialog(String content) {
if (dialog == null) {
dialog = new ProgressDialog(this);
}
dialog.setMessage(content);
dialog.show();
}
@Override
public void closeDialog() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
}
最后附上demo地址:https://github.com/wunianhub/Android-MVPVM