前言:
我们开发一个界面大概需要如下几个步骤:
1、 从服务端拉取数据并简单加工和校验
2、 展示基础的页面UI,例如ActionBar、刷新样式、各种动画联动、列表Adaper等等
3、 将数据绑定到界面上,例如绑定一个video对象,或者指定一个User对象,这其逻辑都是很复杂的
4、 对于列表,绑定列表中的每一个item的样式,这部分也是比较复杂的
这些事情该怎么组织,才能让工程的耦合度最低,灵活度最高,有很多方案,我们逐个来探讨
1、MVC架构
M:数据层,负责监听网络接口和数据库接口
V:View,主要指的是res目录下的布局文件,用来显示来源于Model的数据;但activity或者fragment也具有View层的性质
C:controller,主要指的是activity或者fragment,用来处理UI交互以及Model数据的更新
Android原生就是MVC的思路,设计时将activity定位成一个大的Controller,在页面比较简单且都是静态元素的时候是没问题的。但是仔细想,activity如果是Controller,那么Model和View在哪里呢?Model实际上是activity发网络请求得到的(比如点击登录按钮请求网络数据,校验成功后跳转页面),View是activity渲染layout得到的,所以实际上activity本身已经成为了一个超级大包罗万象的东西了,不再是一个只负责逻辑处理的Controller了,因此这些让我们会感觉页面太重了,逻辑复杂,数据流不清晰,耦合度高
问题:activity或者fragment既有View的性质又有Controller的性质,导致activity或者fragment很重,而且View和Model直接交互,耦合度比较高。
2、MVP架构
M:数据层,负责监听网络接口和数据库接口
View:将activity或者fragment看作是veiw,当然也包括静态的布局资源文件
Presenter:相当于中介,负责View和,Model层的交互
为了MVC的问题,诞生了一个叫做MVP的理念,这个理念是将原来的activity里面要做的包络万象的事情,分出来了一部分,这部分就是和界面View相关的业务处理逻辑(注意是含业务处理的,不仅仅是纯绑定),这样分离后,activity负责创建View以及View交互,以及一些无关业务的静态逻辑,并将创建好的View、model还有上下文的环境参数Environment Context都传递给Presenter,由Presenter来负责业务的处理和界面的渲染。并且MVP方案还建议将Model和View都抽离成接口,这样处理的好处有两点:
第一面向接口编程,未来换一种实现会比较简单独立,比如listview换成recyclerview这种事情,第二是将简单的业务逻辑都放在IView和IModel中,比如设置用户头像展示用户名等,这类操作简单通用,可以有效的分离Presenter的大小,使他只专注于复杂的逻辑处理。
以一个简单的登录案例进行分析:
Model层:model层负责拉取服务端数据并校验,校验通过失败都通过Presenter的回调通知给Presenter,这个过程是异步的
public interface LoginModel {
public void login(final User user, final OnLoginCallBack callBack);
}
public class LoginModelImpl implements LoginModel{
@Override
public void login(User user, OnLoginCallBack callBack) {
final String username = user.getUsername();
final String password = user.getPassword();
//省略和服务端校验数据的步骤
new Handler().postDelayed(new Runnable() {
@Override public void run() {
boolean error = false;
if (TextUtils.isEmpty(username) ||TextUtils.isEmpty(password)){
callBack.onError();//model层里面回调listener
error = true;
}
if (!error){
callBack.onSuccess();
}
}
}, 2000);
}
}
View层:接口的方法是供Presenter调用的,在model数据返回之后,无论登陆成功还是失败都要执行相关的UI操作
public interface LoginView {
//login是个耗时操作,我们需要给用户一个友好的提示
void showProgress();
void hideProgress();
//login当然存在登录成功与失败的处理,失败给出提示
void ShowError();
//login成功,也给个提示
void showSuccess();
}
public class MVPDemoActivity extends AppCompatActivity implements View.OnClickListener, LoginView {
private TextView mUsername;
private TextView mPassword;
private Button mLoginButton;
private ProgressDialog mProgressDialog;
private LoginPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mvp_login_main_activity);
initView();
//创建一个presenter对象,当点击登录按钮时,让presenter去调用model层的login()方法,验证帐号密码
presenter = new LoginPresenterImpl(this);
}
private void initView() {
mUsername = findViewById(R.id.username);
mPassword = findViewById(R.id.password);
mLoginButton = findViewById(R.id.login);
mLoginButton.setOnClickListener(this);
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setCancelable(false);
mProgressDialog.setMessage("请稍后");
}
@Override
public void onClick(View v) {
User user = new User();
user.setPassword(mPassword.getText().toString());
user.setUsername(mUsername.getText().toString());
presenter.login(user);
}
@Override
public void showProgress() {
mProgressDialog.show();
}
@Override
public void hideProgress() {
mProgressDialog.dismiss();
}
@SuppressLint("ShowToast")
@Override
public void ShowError() {
Toast.makeText(this,"用户名或者密码有误",Toast.LENGTH_SHORT);
}
@Override
public void showSuccess() {
mProgressDialog.dismiss();
Toast.makeText(this,"login success",Toast.LENGTH_SHORT).show();
}
}
P层:持有View和Model层二者的引用,建立起沟通桥梁
public interface LoginPresenter {//model层接校验完数据之后通知Presenter进行后续操作
void login(User user);
}
public interface LoginPresenter {//由于Presenter要执行View的方法
void login(User user);
}
public class LoginPresenterImpl implements LoginPresenter, OnLoginCallBack {
private LoginView loginView;
private LoginModel loginModel;
public LoginPresenterImpl(LoginView view){
loginView = view;
loginModel = new LoginModelImpl();
}
@Override
public void login(User user) {
if (loginView != null) {
loginView.showProgress();
}
loginModel.login(user, this);
}
@Override
public void onError() {
if (loginView != null) {
loginView.hideProgress();
loginView.ShowError();
}
}
@Override
public void onSuccess() {
if (loginView != null) {
loginView.showSuccess();
}
}
}
总结:
View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有View层的Interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层再调用View层的接口将加载后的数据展示给用户。
3、MVVM架构
MVP架构两个很显著的问题就是:
1、接口太多,导致文件暴增;
2、Presenter类中持有View(activity)的引用,很容易造成内存泄漏
MVVM结构从结构图上来看和MVP差不多,只不过是P变成了VM,但是二者最大的不同就在于,VM和View层的引用时单向持有,而V和P层双向持有引用
VM和V层通过DataBinding工具类进行绑定。
VM层不会持有activity的引用了,而是持有DataBindingImpl的引用,如果在VM层中对View的属性进行更改,则可以调用VM中的DataBindingImpl.textView.setText("..."),View的数据刷新之后,与之绑定的Model类的相关字段也会进行更新;也可以手动更新Model类的信息
(参考链接:(12条消息) Android MVVM 架构详解_lerendan的博客-CSDN博客_android mvvm的理解)