声明:作者原创,转载注明出处。
作者:帅气陈吃苹果
一、MVC
Model:模型,处理业务逻辑。
View:视图,呈现用户界面。
Controller:控制器,处理用户交互。
(图片来源:MVC图片)
二、MVP
Model:模型,处理业务逻辑。
View:视图,呈现用户界面。
Presenter:中间者,负责调控View和Model之间的交互。
MVP是MVC模式经过改良演变而来,二者都是用来分离UI、数据、业务和UI逻辑和的软件开发模式,controller/presenter负责交互的处理,model负责提供数据和逻辑处理,view负责显示和接收数据。
区别是:MVP模式中,View和Model不直接进行交互,而是采用Presenter这个中间者,通过绑定View和Model的接口,进行间接的交互。而在MVC中,View和Model是可以直接进行通信的。
三、MVP For Android
架构的意义之一在于,让应用程序提高可扩展性。
大部分的Android应用采用的都是如下的开发模式:
(图片来源:《Amdrid MVP详解(上)》)
Activity既承担着View显示用户界面的任务,又包含了Controller处理业务逻辑的任务,因此Android中的MVC并不严格。
当项目规模大到一定程度,Activity就会像一个臃肿的胖子,行动不便。
行动不便体现在,当项目需求变更时,由于View和Model之间耦合度过高,导致代码改动变得复杂而庞大,不利于项目的功能扩展,也不便于进行单元测试。
在Android中,UI是线程不安全的,也就是只能在MainThread中才能进行UI更新,所以对View和Model的分离是合理的。
四、示例
这个示例采用我上一篇博客《Bmob后端云初体验》的Demo,使用Bmob后端云实现一个登陆注册的例子。如果你感兴趣可以点击阅读,当然,不读也没多大影响。
首先,先看一下项目结构:
1.MyUser.class
public class MyUser extends BmobObject {
//用户名
private String userName;
//密码
private String userPwd;
public MyUser() {
}
public MyUser(String name, String pwd) {
this.userName = name;
this.userPwd = pwd;
}
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPwd() {
return this.userPwd;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
}
2.IUserView.class
对用户输入进行数据抽象,得到View的接口。
public interface IUserView {
/**
* 获取用户输入的用户名
*/
String getUserName();
/**
* 获取用户输入的密码
*/
String getUserPwd();
/**
* 加载进度对话框
*/
void showLoading();
/**
* 隐藏进度对话框
*/
void hideLoading();
}
3.IUserModel.class
对需要用到的数据进行抽象,得到Model的接口,通过回调的方式进行过程的判断(在这里体现为,当用户注册或登陆时,可分为操作开始、操作成功、因用户原因导致的操作失败、因系统原因导致的操作失败四个过程)。
public interface IUserModel {
/**
* 用户登录
* @param name
* @param pwd
* @param listener
*/
void checkUser(String name,String pwd,OnUserOperationListener listener);
/**
* 用户注册
* @param name
* @param pwd
* @param listener
*/
void registerUser(String name,String pwd,OnUserOperationListener listener);
interface OnUserOperationListener {
/**
* 操作开始
*/
void onOperationBegin();
/**
* 操作成功
*/
void onSuccess();
/**
* 因用户原因,导致操作失败
*/
void onUserFailed();
/**
* 因系统原因,导致操作失败
*/
void onSysFailed();
}
}
4.IUserModelImpl.class
接着对Model接口进行实现,其中涉及到Bmob后端云的数据服务,可以看成是在这里进行业务逻辑的具体操作(包括网络请求、后台进程、数据加载等)。
public class IUserModelImpl implements IUserModel {
/**
* 由model进行具体的业务逻辑操作,检查用户名和密码
* @param name
* @param pwd
*/
@Override
public void checkUser(String name, String pwd, final OnUserOperationListener listener) {
//开始检查过程
listener.onOperationBegin();
BmobQuery<MyUser> userQuery = new BmobQuery<MyUser>();
userQuery.addWhereEqualTo("userName",name);
userQuery.addWhereEqualTo("userPwd",pwd);
userQuery.findObjects(new FindListener<MyUser>() {
@Override
public void done(List<MyUser> list, BmobException e) {
if(e == null) {
if(list.size() == 1) {
listener.onSuccess();
} else {
listener.onUserFailed();
}
} else {
listener.onSysFailed();
}
}
});
}
@Override
public void registerUser( String name, String pwd, final OnUserOperationListener listener) {
listener.onOperationBegin();
MyUser mUser = new MyUser();
mUser.setUserName(name);
mUser.setUserPwd(pwd);
mUser.save(new SaveListener<String>() {
@Override
public void done(String s, BmobException e) {
if(e == null) {;
listener.onSuccess();
} else {
listener.onSysFailed();
}
}
});
}
}
5.UserPresenter.class
然后创建一个中间者Presenter,持有View和Model的引用,通过对View和Model的绑定,将原本在View中的那些繁杂的操作指定给Model去实现,而不是View直接与Model进行交互。
public class UserPresenter extends BasePresenter<IUserView>{
//model
private IUserModel mUserModel;
//view
private IUserView mUserView;
/**
* 实例化view
* @param mUserview
*/
public UserPresenter(IUserView mUserview) {
super();
this.mUserModel = new IUserModelImpl();
this.mUserView = mUserview;
}
/**
* bind view and model for user login
*
* @param context 上下文环境
* @param name 用户名
* @param pwd 密码
*/
public void check(final Context context, String name, String pwd) {
//显示进度对话框
mUserView.showLoading();
if(mUserModel != null) {
mUserModel.checkUser(name, pwd, new IUserModel.OnUserOperationListener() {
@Override
public void onOperationBegin() {
}
@Override
public void onSuccess() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"登录成功!",
Toast.LENGTH_SHORT).show();
Intent intent = new Intent(getApplicationContext(),MainActivity.class);
context.startActivity(intent);
}
@Override
public void onUserFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"用户名或密码错误,请重新输入!",
Toast.LENGTH_SHORT).show();
}
@Override
public void onSysFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"登录失败,请检查网络设置!",
Toast.LENGTH_SHORT).show();
}
});
}
}
/**
* bind view and model for user register
*
* @param context
* @param name
* @param pwd
*/
public void register(final Context context,String name,String pwd) {
//显示进度对话框
mUserView.showLoading();
if(mUserModel != null) {
mUserModel.registerUser(name, pwd, new IUserModel.OnUserOperationListener() {
@Override
public void onOperationBegin() {
}
@Override
public void onSuccess() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"注册成功!",
Toast.LENGTH_SHORT).show();
((Activity) context).finish();
}
@Override
public void onUserFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"用户名或密码不合法,请重新输入!",
Toast.LENGTH_SHORT).show();
}
@Override
public void onSysFailed() {
mUserView.hideLoading();
Toast.makeText(getApplicationContext(),
"你可能长得太丑,网络都看不下去了 ^_^ ",
Toast.LENGTH_SHORT).show();
}
});
}
}
}
6.LoginActivity.class
Activity就是View层中很典型的一个体现,所以要让他实现抽象出来的View接口。在View层中,只与中间者Presenter进行交互。
public class LoginActivity extends BaseActivity<IUserView,UserPresenter> implements View.OnClickListener,IUserView{
private EditText editName;
private EditText editPwd;
private Button btnLogin;
private Button btnToRegister;
//进度对话框
ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//初始化BmobSDK,
Bmob.initialize(this, "4fd01c1c4eaca3d97c85e36494554549");
setContentView(R.layout.activity_login);
initView();
}
/**
* 控件初始化
*/
private void initView() {
editName = (EditText) findViewById(R.id.edit_login_name);
editPwd = (EditText) findViewById(R.id.edit_login_pwd);
btnLogin = (Button) findViewById(R.id.btn_login);
btnToRegister = (Button) findViewById(R.id.btn_to_register);
btnLogin.setOnClickListener(this);
btnToRegister.setOnClickListener(this);
progressDialog = new ProgressDialog(this);
progressDialog.setTitle("登陆");
progressDialog.setMessage("正在登陆...");
progressDialog.setCancelable(false);
}
/**
* 重写按钮的点击事件
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_login:
userLogin();
break;
case R.id.btn_to_register:
toRegister();
break;
default:
break;
}
}
/**
* 去注册
*/
private void toRegister() {
Intent intentReg = new Intent(LoginActivity.this,RegisterActivity.class);
startActivity(intentReg);
}
/**
* 登陆
*/
private void userLogin() {
//初始化中间者
mPresenter = new UserPresenter(this);
//通过中间者进行用户名和密码的检查
mPresenter.check(this,getUserName(),getUserPwd());
}
@Override
public String getUserName() {
return editName.getText().toString();
}
@Override
public String getUserPwd() {
return editPwd.getText().toString();
}
@Override
public void showLoading() {
progressDialog.show();
}
@Override
public void hideLoading() {
progressDialog.hide();
}
@Override
protected UserPresenter createPresenter() {
return new UserPresenter(this);
}
}
因为Presenter是用过View和Model的接口对View、Model进行访问的,它持有他们的引用。
存在这样一种情况,当Activity通过Presenter在Model进行业务逻辑的具体实现操作时,很可能这些操作是耗时的,假设有一个耗时长达5s的操作,而在这5s里,如果Activity被销毁(用户离开此界面),而Presenter还持有对View的引用,就会造成内存泄漏了。
如果不对这个问题进行处理,当一个应用有很多个Activity,假设一个Activity需要进行十个耗时操作,那么将严重降低应用的性能。
所以,需要让Activity继承一个父类BaseActivity,在这个BaseActivity中,当onCreate()方法执行,则关联Presenter,当onDestroy()执行,则解除对Presenter的关联,让Presenter继承一个BasePresenter,当系统内存不足时,优先释放Model,而不是View,这样用户体验才好。就好像一个爱臭美的人要被打时说,有事好商量,别打脸行么。当敌人来势不汹,门面重要。
7.BaseActivity.class
public abstract class BaseActivity<V,T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
//创建Presenter
mPresenter = createPresenter();
//关联View
mPresenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//解除关联
mPresenter.detachView();
}
protected abstract T createPresenter();
}
8.BasePresenter.class
public abstract class BasePresenter<T> {
//当内存不足时,释放内存
protected WeakReference<T> mViewRef;
/**
* bind view with presenter
* @param view
*/
public void attachView(T view) {
mViewRef = new WeakReference<T>(view);
}
public void detachView() {
if(mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
}
protected T getView() {
return mViewRef.get();
}
}
9.RegisterActivity.class
可以看到,在这里,我们的登录界面和注册界面所需要的数据时一样的,那么,可以看出MVP的优势之一:
当项目需求变动,如数据的展现方式不一样而数据本身不存在变动时,我们只需要新建一个Activity或Fragment,在这个Activity或Fragment里同样对Presenter进行关联就可以了,而Presenter层和Model层的代码都不需要变动,这就是可扩展性的体现。
public class RegisterActivity extends BaseActivity<IUserView,UserPresenter> implements IUserView{
private EditText editName;
private EditText editPwd;
private Button btnRegister;
private UserPresenter mUserPresenter;
//进度对话框
ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView();
}
/**
* 控件初始化
*/
private void initView() {
editName = (EditText) findViewById(R.id.edit_register_name);
editPwd = (EditText) findViewById(R.id.edit_register_pwd);
btnRegister = (Button) findViewById(R.id.btn_register);
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userRegister();
}
});
progressDialog = new ProgressDialog(this);
progressDialog.setTitle("注册");
progressDialog.setMessage("正在注册...");
progressDialog.setCancelable(false);
}
/**
* 用户注册,即添加一行数据
*/
private void userRegister() {
mUserPresenter = new UserPresenter(this);
mUserPresenter.register(this,getUserName(),getUserPwd());
}
@Override
public String getUserName() {
return editName.getText().toString();
}
@Override
public String getUserPwd() {
return editPwd.getText().toString();
}
@Override
public void showLoading() {
progressDialog.show();
}
@Override
public void hideLoading() {
progressDialog.hide();
}
@Override
protected UserPresenter createPresenter() {
return new UserPresenter(this);
}
}
10.MainActivity.class
登录之后的主界面就是显示一段文本,没什么特别的。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
总结:
MVP For Android对View层和Model实现了解耦,有利于提高应用的扩展性、健壮性,便于进行单元测试,但增加了很多代码量。
除了MVP,还有MVVM有待学习。
不同的项目有不同的业务需求,要根据具体需求和项目规模进行开发模式的选择。
源码下载:Github下载
个人博客:帅气陈吃苹果